TCP 协议是面向连接的基于流的,可靠的传输服务。UDP是无连接的,基于数据报的,不可靠的传输服务,UDP没有粘包,但是会产生丢包。
UDP模型如下:
可以看到,服务器端不用listen,也不用accept。而客户端,也不用connect。
总结UDP的特点如下:
1、无连接
2、基于消息的数据传输服务
3、不可靠
4、一般情况下UDP更加高效
注意点:
1、UDP报文可能会丢失重复
2、UDP报文可能会乱序
3、UDP缺乏流量控制
4、UDP缓冲区写满后,没有流量控制机制,会覆盖缓冲区
5、UDP协议数据报文截断
如果接收到的数据报,大于缓冲区,报文可以被截断,后面的部分会丢失
6、recvfrom返回0,不代表连接关闭,因为UDP是无连接的
sendto可以发送数据0包,只包含UDP头部
7、ICMP异步错误
观察现象:
关闭UDP服务端,如启动UDP客户端,从键盘接收数据后,再发送数据。UDP客户端会阻塞在sendto位置。
说明:
1、UDP发送报文时,只把数据copy到数据缓冲区,在服务器没有起来的情况下可以发送成功。
2、所谓ICMP异步错误是指:发送报文的时候,没有错误,recvfrom接收报文的时候,会收到ICMP应答。
3、异步错误,是无法返回未连接的套接字,UDP也可以调用connect。
8、UDP connect
UDP调用connect,并没有三次握手,只是维护了一个状态信息(和对等方的)
一旦调用connect,就可以使用send函数
简单的UDP回射服务器程序如下:
服务器:
1 #include <netinet/in.h> 2 #include <arpa/inet.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 8 9 void echo_srv(int sock) 10 { 11 char recvbuf[1024] = {0}; 12 struct sockaddr_in peeraddr; 13 socklen_t peerlen; 14 int n; 15 16 while(1) 17 { 18 peerlen = sizeof(peeraddr); 19 memset(recvbuf, 0, sizeof(recvbuf)); 20 21 n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&peeraddr, 22 &peerlen); 23 24 if(n == -1) 25 { 26 if(errno == EINTR) 27 continue; 28 else 29 { 30 perror("recvfrom error"); 31 exit(0); 32 } 33 } 34 else if(n > 0) 35 { 36 int ret = 0; 37 fputs(recvbuf, stdout); 38 ret = sendto(sock, recvbuf, n, 0, (struct sockaddr*)&peeraddr, peerlen); 39 } 40 } 41 42 close(sock); 43 } 44 45 46 int main() 47 { 48 int sock; 49 50 sock = socket(AF_INET, SOCK_DGRAM, 0); 51 52 if(sock < 0) 53 { 54 perror("socket error"); 55 exit(0); 56 } 57 58 struct sockaddr_in servaddr; 59 memset(&servaddr, 0, sizeof(servaddr)); 60 61 servaddr.sin_family = AF_INET; 62 servaddr.sin_port = htons(8002); 63 64 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 65 66 if(bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) 67 { 68 perror("bind error"); 69 exit(0); 70 } 71 72 echo_srv(sock); 73 return 0; 74 }
客户端:
1 #include <netinet/in.h> 2 #include <arpa/inet.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 8 9 10 void echo_cli(int sock) 11 { 12 struct sockaddr_in servaddr; 13 memset(&servaddr, 0, sizeof(servaddr)); 14 15 servaddr.sin_family = AF_INET; 16 servaddr.sin_port = htons(8002); 17 18 servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 19 20 int ret = 0; 21 char sendbuf[1024] = {0}; 22 char recvbuf[1024] = {0}; 23 24 while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 25 { 26 sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, 27 sizeof(servaddr) 28 ); 29 ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL); 30 31 if(ret == -1) 32 { 33 if(errno == EINTR) 34 continue; 35 else 36 { 37 perror("recvfrom error"); 38 exit(0); 39 } 40 } 41 42 fputs(recvbuf, stdout); 43 memset(sendbuf, 0, sizeof(sendbuf)); 44 memset(recvbuf, 0, sizeof(recvbuf)); 45 46 } 47 48 close(sock); 49 } 50 51 52 int main() 53 { 54 int sock; 55 sock = socket(AF_INET, SOCK_DGRAM, 0); 56 57 if(sock < 0) 58 { 59 perror("socket error"); 60 exit(0); 61 } 62 63 echo_cli(sock); 64 65 return 0; 66 }
运行结果如下:
用netstat - na看网络状态如下:
UDP和TCP不一样,不存在11种状态,因此,我们只能看到一个服务器端的套接字,服务器端执行了bind,所以会显示这个套接字。