• TCP/IP 协议栈 -- 编写UDP客户端注意细节


    上节我们说到了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 6666
    int 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
    ", inet_ntoa( clientaddr.sin_addr));
            printf("TEXT: %s
    ", 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 6666
    int main(int argc, char *argv[])
    {
        if(argc != 2)
        {
            fprintf(stderr, "+++ IPaddress
    ");
            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
    ");
            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
    ", 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 decode
    listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
    12: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 11
    E..'..@.@.#$...........
    ...&hello world
    12: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 47
    E..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.13
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
    13: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 world
    13: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 20
    E..0r.@.@............
    .v.../recvd hello.......;4
    13: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 11
    E..'s.@.@............v.
    ...&hello world
    13: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 47
    E..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 20
    E..0r.@.@............
    .v.../recvd hello.......;4
    13: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 11
    E..'s.@.@............v.
    ...&hello world

    同样这里也会一直阻塞在recvfrom这里 解决方案同2 给recvfrom添加超时 或 select 检查fd的可读是否超时。时间可稍微长一些。

  • 相关阅读:
    请教提高上百万行数据insert速度的“经典”方法
    poj1411
    poj1422
    poj1661
    poj1664
    poj1405
    交往艺术与沟通技巧[推荐]
    不可不听的10个职场故事
    也谈如何“领导”你的领导
    十个让爱情稳固的经典好习惯
  • 原文地址:https://www.cnblogs.com/MaAce/p/7755697.html
Copyright © 2020-2023  润新知