UDP特点
1.无连接,UDP协议并没有维护端到端的连接状态,而TCP是基于连接的。
2.基于消息的传输服务,不存在粘包问题,我们可以认为这些数据报之间是有边界的。
3.不可靠,包括丢失、重复、乱序、缺乏流量控制。
4.一般情况下UDP更加高效
UDP客户/服务器基本模型
UDP客户/服务器基本模型
客户端代码
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) void echo_cli(int sock) { //初始化一个地址绑定它 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); //指定对方的IP地址 servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //用于说明异步的错误能返回给已连接的套接字 //UDP在调用connect的时候是不做三次握手的 connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)); int ret; char sendbuf[1024]={0}; char recvbuf[1024]={0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { //第一次调用的时候就对地址进行了绑定 //端口的绑定是在第一次调用sendto,connect仅仅只是确认了外出接口的地址 sendto(sock,sendbuf,strlen(sendbuf),0,(struct sockaddr*)&servaddr,sizeof(servaddr)); //如果是有connect的话,就可以连接到地址了 //sendto(sock,sendbuf,strlen(sendbuf),0,NULL,0); /* send也可以用来做发送 send(sock,sendbuf,strlen(sendbuf),0); */ //回射接受时,可以不指定对方的IP,因为上面已经绑定过了 ret=recvfrom(sock,recvbuf,sizeof(recvbuf),0,NULL,NULL); if(ret==-1) { if(errno==EINTR); continue; ERR_EXIT("recvfrom"); } fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sock); } int main(void) { int sock; //创建一个套接字,第二个参数是UDP套接口 if((sock=socket(PF_INET,SOCK_DGRAM,0))<0) ERR_EXIT("socket"); echo_cli(sock); return 0; }
服务端代码
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) void echo_srv(int sock) { //不断接受客户端发送过来的一行数据 char recvbuf[1024]={0}; //客户端的地址信息 struct sockaddr_in peeraddr; socklen_t peerlen; //接受到的字节数 int n; while(1) { peerlen=sizeof(peeraddr); memset(recvbuf,0,sizeof(recvbuf)); n=recvfrom(sock,recvbuf,sizeof(recvbuf),0,(struct sockaddr*)&peeraddr,&peerlen); if(n==1) { if(errno==EINTR) continue; ERR_EXIT("recvfrom"); } //回射回去 else if(n>0) { //打印在服务端 fputs(recvbuf,stdout); sendto(sock,recvbuf,n,0,(struct sockaddr*)&peeraddr,peerlen); } } close(sock); } int main(void) { int sock; //创建一个套接字,第二个参数是UDP套接口 if((sock=socket(PF_INET,SOCK_DGRAM,0))<0) ERR_EXIT("socket"); //初始化一个地址绑定它 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); //本机任意的一个地址 servaddr.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("bind"); //没有三次握手,所以不需要监听 //回射服务器 echo_srv(sock); return 0; }
UDP注意点
数据报的丢失
如果一个客户数据报丢失,客户将永远阻塞于recvfrom调用,等待一个永远不会到达的服务器应答。类似地,如果客户数据报到达服务器,但是服务器的应答丢失了,客户也将永远阻塞于recvfrom调用。防止这样永久阻塞的一般方法是给客户的recvfrom调用设置一个超时。
数据报的重复和乱序
应用层应该维护数据报之间的一些序号
UDP缺乏流量控制
UDP套接字有自己对应的缓冲区,当缓冲区满时,UDP没有流量控制的机制,如果再往里面发送数据,并不是将数据丢掉,而是将数据重新覆盖到原来的缓冲区。
UDP协议数据报文截断
UDP是一个报式套接口,是基于消息的数据传输,并不是字节流的传输服务,这意味当我们发送“ABCD”时,对方要么一次性接收到这个数据,如果一次接收没有完全把它接收走的话,那么这些数据是不会留在缓冲区当中的,会被截断。也就是说接收的时候如果指定的缓冲区的大小小于数据报的大小,这个时候只会接收对应大小的数据。其余的数据会被清除,也就截断了,这就是报式套接口而不是流式套接口。这个特征也就意味着不会出现粘包问题,只不过在接收端要注意接收的长度要大于等于发送端所发送数据报的长度,才不会出现截断现象。
ICMP异步错误
在不启动服务器的前提下启动客户,如果此时在客户端输入一行,那么什么也不发生。客户端永远阻塞在recvfrom调用。
程序阻塞在recvfrom的地方,这个时候对等方并没有启动,但是程序无法捕捉到没有启动这样一个信息,程序仍然阻塞在recvfrom的位置,这是因为产生了一个异步错误,这个错误是不会返回给套接口的。首先sendto是不会出错的,因为UDP是无连接的,它并不维护连接的状态,sendto仅仅只是将应用程序的缓冲区拷贝到套接字的缓冲区当中,只是个拷贝操作,并不代表这个数据已经发送给对方了,而这个拷贝只要套接口有足够的缓冲区空间就能够拷贝成功,所以说这个时候不会出错,但是这个数据报是无法到达对等方的,这时候就会产生ICMP的错误,TCP/IP的协议栈会有一个ICMP报文应答给客户端,这个错误延迟到recvfrom接收的时候才会产生,所以叫做异步的ICMP错误。而这里面recvfrom也不能被通知的,因为TCP/IP规定异步错误不能够返回给未连接的套接字,所以recvfrom也得不到通知,recvfrom会一直阻塞。如何解决这个问题呢?请看下面
UDP connect
UDP也可以调用connect,这个时候异步的错误就会返回给已连接的套接字,这就是UDPconnect的作用,那么UDPconnerct是否意味着真的是建立一个和TCP一样的连接呢?不是这样的,UDP在调用connect时,并不做三次握手,仅仅只是维护了一个信息,sock指定了对方的地址。
对于已连接UDP套接字,与默认的未连接UDP套接字相比,发生了三个变化:
1.我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。写给已连接UDP套接字上的任何内容都自动发送到connect指定的协议地址(例如IP地址和端口号)。
2.我们不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。这样就限制一个已连接套接字仅能与一个对端交换数据报。
3.由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接收任何异步错误。
UDP外出接口的确定
已连接UDP套接字可用来确定用于某个特定目的地的外出接口。这是由connect函数应用到UDP套接字时的一个副作用造成的:内核选定本地地址。这个本地地址通过目的IP地址搜索路由表得到外出接口,然后选用该接口的主IP地址。