上节我们说到了TCP 客户端编写的主要细节, 本节我们来看一下UDP client的几种情况,测试代码如下:
server:
#include <stdio.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/stat.h>#include <sys/types.h>#include <string.h>#define PORT 6666int main(){int serverfd = socket(AF_INET, SOCK_DGRAM, 0);if(serverfd < 0)return -1;struct sockaddr_in serveraddr;serveraddr.sin_port = htons(PORT);serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);bind(serverfd, (const struct sockaddr *)&serveraddr, sizeof(serveraddr));char buff[256] = {0};struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);while(1){recvfrom(serverfd, buff, 256, 0, (struct sockaddr *)&clientaddr, &len);printf("RECV: %s\n", inet_ntoa( clientaddr.sin_addr));printf("TEXT: %s\n", buff);memset(buff, 0, 256);sendto(serverfd, "recvd hello", 20, 0, (const struct sockaddr*)&clientaddr, sizeof(clientaddr));}return 0;}
client:
#include <stdio.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/stat.h>#include <sys/types.h>#include <string.h>#include <unistd.h>#define PORT 6666int main(int argc, char *argv[]){if(argc != 2){fprintf(stderr, "+++ IPaddress\n");return -1;}int clientfd = socket(AF_INET, SOCK_DGRAM, 0);if(clientfd < 0)return -1;struct sockaddr_in serveraddr;serveraddr.sin_port = htons(PORT);serveraddr.sin_family = AF_INET;char *buff = "hello world";size_t n = strlen(buff);char rebuff[256] = {0};if(inet_pton(AF_INET, argv[1], (void *)&serveraddr.sin_addr) == 0){printf("IP address error\n");return -1;}socklen_t len = sizeof(serveraddr);while(1){sleep(2);sendto(clientfd, buff, n, 0, (const struct sockaddr *)&serveraddr, len);recvfrom(clientfd, rebuff, 256, 0, NULL, NULL);printf("SERVER :%s\n", rebuff);}return 0;}
UDP 无连接 不可靠的协议 它并不会向TCP一样会建立连接后再发送数据。
1.server 进程没有打开
Desktop# tcpdump -i lo -A -e -nn tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes12:43:03.656237 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 53: 127.0.0.1.46066 > 127.0.0.1.6666: UDP, length 11E..'..@.@.#$..............&hello world12:43:03.656275 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 81: 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 6666 unreachable, length 47E..C%p..@.V.................E..'..@.@.#$..............&hello world
这里 以127地址 做实验 这里可以看到 若主机server进程没有打开,则client会阻塞在recvfrom处 这里可以在sendto和 recvfrom 之间插入printf进行测试。 这就是UDP的特性, ICMP其实是sendto造成的它是一个异步错误 但是这个ICMP并没有返回到应用程序, client 并不知道。 这里的解决方案就是用connect 这里的connect与TCP里的connect建立三次握手差别很大, 对于UDP它就是为了收取这个内核接到的这个ICMP错误。 内核只是检查是否存在立即可知的错误,记录对端的IP地址和端口号,然后立即返回到调用进程。那么这里就要注意到connect里面传入server的ip和port,那么这里的sendto和recvfrom就不要在传入iP和port, 那么就可以用read和write来代替。 这里还有一点就是因为UDP是无连接的, connect并不会像tcp一样会发送SYN, TCP发送SYN时,若server进程没有开启那么会立即受到RST, 但对于UDP来说 当write 后才会收到主机的ICMP错误。
2.server 主机不存在。
Desktop# tcpdump -i eth0 -A -e -nn host 15.15.12.13tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes13:11:25.331059 00:0c:29:61:1a:9b > 00:1e:2a:67:f3:2e, ethertype IPv4 (0x0800), length 53: 192.168.1.2.44646 > 15.15.12.13.6666: UDP, length 11.f.'".@.@.;............hello world13:11:25.331233 00:0c:29:61:1a:9b > 00:1e:2a:67:f3:2e, ethertype IPv4 (0x0800), length 60: 192.168.1.2.44646 > 15.15.12.13.6666: UDP, length 11.f.'".@.@.;............hello world.......
这里可以看到一直阻塞在recvfrom这里 这里的解决方案就是在recvfrom添加超时中断,alarm 或者 select检测fd是否可读 设立超时。
3.client 正在发送数据, server 进程被kill掉
13:17:29.059557 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 62: 127.0.0.1.6666 > 127.0.0.1.57718: UDP, length 20E..0r.@.@.............v.../recvd hello.......;413:17:31.059842 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 53: 127.0.0.1.57718 > 127.0.0.1.6666: UDP, length 11E..'s.@.@............v....&hello world13:17:31.059922 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 81: 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 6666 unreachable, length 47E..C....@.............qs....E..'s.@.@............v....&hello world
这里可以看到client也收到了ICMP 解决方案同1.
4.client 正在发生数据, server 断电。
13:17:29.059557 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 62: 127.0.0.1.6666 > 127.0.0.1.57718: UDP, length 20E..0r.@.@.............v.../recvd hello.......;413:17:31.059842 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 53: 127.0.0.1.57718 > 127.0.0.1.6666: UDP, length 11E..'s.@.@............v....&hello world
同样这里也会一直阻塞在recvfrom这里 解决方案同2 给recvfrom添加超时 或 select 检查fd的可读是否超时。时间可稍微长一些。