一、UDP与connect函数的关联性
已连接的UDP套接字与TCP套接字大相径庭,他不会用三路握手的过程。系统内核只是检查是否存在立即可知的错误(例如一个显然不可抵达的目的地),记录对端的IP地址和端口号(取自连接给connect的套接字地址结构),然后立刻返回给调用进程。
对于已连接UDP套接字与默认的未连接UDP套接字相比较而发生的变化:
1)我们不用在指定输出操作的IP地址和对端端口号。我们不能使用sendto,而改用write和send。写到已连接套接字对应的任何内容将会自动发送到connect的套接字上。
2)我们不必要使用recvfrom以获取数据报的发报者,而改用read,recv或recvmsg。在一个已连接的套接字上,内核的输入操作返回的数据报将会只来自哪些connect指定的协议地址的数据报。
3)已连接的UDP套接字中发生的ICMP错误将会返回给进程,而未连接的ICMP错误将不会返回的进程
套接字类型 | write或者send | 不指定地目的地址的sendto | 指定目的地址的sendto |
TCP套接字 | 可以 | 可以 | EISCONN |
UDP套接字,已连接 | 可以 | 可以 | EISCONN |
UDP套接字,未连接 | EDESTADDRREQ | EDESTADDRREQ | 可以 |
对于一个已连接的UDP套接字分析图如下
对于如图中“???”表示来至其他未连接的地址发来的信息将会被内核自动摒弃,并且删除ICMP错误。
------------------------------------------------------------------------------------------------------------------------------------------
当一个已连接的UDP套接字在此调用connect的时候:
1)、断开已有的套接字
2)、连接新的ip地址和端口号
二、UDP中多次Sendto与Connect之间的性能分析
在一个未连接的UDP套接字上进行2次sendto的过程:
1.连接套接字
2.输出第一个数据报
3.断开套接字
4.连接套接字
5.输出第二个数据报
6.断开套接字
当应用进程要给同一目的地址发送多个数据报的时候,显示连接套接字效率更高。
1.连接套接字。
2.输出第一个数据报
3.输出第二个数据报
根据[Paetridge和Pink 1993]指出, 临时连接未连接的套接字大约会消耗每个UDP传输三分之一的开销。
改进代码片段如下:
#include "unp.h"
void do_cli(FILE *fp, int sockfd,
const struct sockaddr* pservaddr, socklen_t len)
{
char recvbuf[MAXLINE], sendbuf[MAXLINE];
int n;
Connect(sockfd, (struct sockaddr *) pservaddr, len);
while(Fgets(sendbuf, MAXLINE, fp) != NULL){
Write(sockfd, sendbuf, strlen(sendbuf));
n = Read(sockfd, recvbuf, MAXLINE);
recvbuf[n] = 0;
Fputs(recvbuf, stdout);
}
}
三、TCP和UDP回射服务器中的应用
模型图如下
源代码:
#include "unp.h"
void sig_chld(int signo)
{
int status;
int pid;
pid = wait(&status);
fprintf(stdout, "\nchild %d terminate\n", pid);
return;
}
int main()
{
int sockfd, n, listenfd, udpfd, maxfd;
struct sockaddr_in servaddr, cliaddr;
const int on = 1;
int childpid = 0;
fd_set rset;
char buf[MAXLINE];
socklen_t len;
int nearby;
bzero((struct sockaddr*) &servaddr, sizeof(servaddr));
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
Bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
udpfd = Socket(AF_INET, SOCK_DGRAM, 0);
bzero((struct sockaddr*) &servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(udpfd, (struct sockaddr *)& servaddr, sizeof(servaddr));
FD_ZERO(&rset);
Signal(SIGCHLD, sig_chld);
maxfd = udpfd;
for(;;){
fprintf(stdout, "abcdefg\n");
FD_SET(listenfd, &rset);
FD_SET(udpfd, &rset);
if((nearby = Select(maxfd + 1, &rset, NULL, NULL, NULL)) < 0){
if(errno == EINTR) continue;
else
fprintf(stderr, "select error\n");
}
if(FD_ISSET(listenfd, &rset)){
len = sizeof(cliaddr);
sockfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &len);
if((childpid = Fork()) == 0){ /* child process */
Close(listenfd); /*重点关注*/
str_echo(sockfd);/*见书unp查询源代码*/
exit(0);
}
Close(sockfd);/*重点关注,一定不能忘记*/
if(--nearby < 0) continue;
}
if(FD_ISSET(udpfd, &rset)){
len = sizeof(cliaddr);
n = Recvfrom(udpfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &len);
buf[n] = 0;
Fputs(buf,stdout);
Sendto(udpfd, buf, n, 0, (const struct sockaddr *) &cliaddr, len);
}
}
}
3-10行:建立一个本程序的子进程信号处理函数
13-37行:分别建立tcp监听套接字(SOCK_STREAM)和UDP套接字(SOCK_DGRAM), 并且捆绑ip和端口
44-50:等待监听套接字的刻度条件, 如果SIGCHLD信号中断了select的调用,那么我们需要对EINTR进行处理
剩下行:监听到套接字处理:
改程序缺点:
1.当正在sig_chld函数正在处理SIGCHLD此信号的时候,如还有多个SIGCHLD发来的时候, 则会被内核忽略。因为信号量在内核中不排队
2.在recvfrom和sendto配对调用之前, 一定要初始化sizeof(struct sockaddr_in)并记录下来。