• socket数据读写


    socket数据读写

    学习《Linux高性能服务器编程》第五章Linux网络编程基础API,为了印象深刻一些,多动手多实践,所以记下这个笔记。这一篇主要记录Linux中socket数据读写的部分,包括TCP数据读写、UDP数据读写和通用数据读写。

    TCP数据读写

    对文件的读写操作readwrite同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据的读写的控制。在TCP中流数据读写的系统调用是:

    #include 
    #include 
    
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    recv读取sockfd上的数据,buflen参数分别指定读缓冲区的位置和大小。

    recv成功读取时返回实际读取到的数据长度,它可能小于我们期望的长度len。因此需要多次调用recv才能读取到完整的数据。recv返回0,意味着对方已经关闭连接。recv出错时返回-1并设置errno

    send发送sockfd上的数据。buflen参数分别指定写缓冲区的位置和大小。

    send成功读取时返回实际读取到的数据长度,出错时返回-1并设置errno

    flags用于控制数据的接收和发送,一般来说设置为0,也可以进行设置,从而进行控制。

    控制参数可以通过man手册进行查看,这里直接截取书上的表格

    image-20220812171929139

    UDP数据读写

    #include 
    #include 
    
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    针对UDP系统提供的是读写函数是recvfromsendto,其中函数recvfromsendto前4个参数和recvsend意义相同,最后两个是发送端/接收端的地址。因为UDP是没有连接的概念,所以调用这两个函数的时候都要指定地址。recvfromsendto的返回值和recvsend也相同,所以不用过多介绍。

    除此之外,recvfromsendto也可以用于TCP使用,只需要把最后两个参数设置为NULL即可。

    通用数据读写函数

    #include 
    #include 
    
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    recvmsgsendmsg的参数中sockfdflags比较简单,复杂一些的参数就是msgmsg的结构如下:

    #include :
    
    struct iovec {                    /* Scatter/gather array items */
       void  *iov_base;              /* Starting address */
       size_t iov_len;               /* Number of bytes to transfer */
    };
    
    struct msghdr {
       void         *msg_name;       /* Optional address */
       socklen_t     msg_namelen;    /* Size of address */
       struct iovec *msg_iov;        /* Scatter/gather array */
       size_t        msg_iovlen;     /* # elements in msg_iov */
       void         *msg_control;    /* Ancillary data, see below */
       size_t        msg_controllen; /* Ancillary data buffer len */
       int           msg_flags;      /* Flags on received message */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    msg_name指向socket地址,对于TCP协议无意义,所以在TCP协议中设置为NULL,而对于UDP等其他协议就说明了发送或者接收的地址。msg_namelen指定socket地址的长度。

    msg_ioviovec类型的指针,根据注释来判断应该是个数组。iovec结构体封装了一块内存的起始位置和长度。msg_iovlen指定这样的iovec结构对象有多少个。

    对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度则由msg_iov指向的数组指定,这称为分散读( scatter read);对于sendmsg而言,msg_iovlen块分散内存中的数据将被一并发送,这称为集中写( gather write)。

    为什么要有分散读和集中写呢,这其实是一个非常方便的使用,方便传输结构不同的数据。比如:发送http应答时,我们可以把前面的请求头请求的文件分为两个buffer,但是最终一起进行写入,减少了拼接带来的麻烦。同理我接收的时候也是想请求头请求的文件分开,所以使用分散读。

    msg_flags成员无须设定,它会复制recvmsg/sendmsg flags参数的内容以影响数据读写过程。recvmsg还会在调用结束前,将某些更新后的标志设置到msg_flags中。

    recvmsg/sendmsg flags参数以及返回值的含义均与sendrecvflags参数及返回值相同。

    msg_control msg_controllen成员用于辅助数据的传送。目前书中并未进行讲解,后续再补充。

    recvmsgsendmsg的例子:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define BUFFER_SIZE 256
    int main(int argc, char const *argv[])
    {
        if (argc <= 2)
        {
            printf("usage: %s ip_address port_number\n", basename(argv[0]));
            return 1;
        }
        const char *ip = argv[1];
        int port = atoi(argv[2]);
    
        sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        assert(sock >= 0);
    
        int ret = bind(sock, (sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    
        if (connfd < 0)
        {
            printf("errno is: %d\n", errno);
        }
        else
        {
            char remote[INET_ADDRSTRLEN];
            printf("connected with ip: %s and port: %d\n",
                   inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
    		
            char buffer1[6];
            char buffer2[BUFFER_SIZE];
            struct msghdr msg;
            bzero(&msg, sizeof(msg));
            //设置集中写
            struct iovec iovec_arry[2];
            iovec_arry[0].iov_base = (void *)buffer1;
            iovec_arry[0].iov_len = sizeof(buffer1);
            iovec_arry[1].iov_base = (void *)buffer2;
            iovec_arry[1].iov_len = sizeof(buffer2);
    
            msg.msg_iov = iovec_arry;
            msg.msg_iovlen = 2;
    
            int n = recvmsg(connfd, &msg, 0);
            assert(n != -1);
            printf(" have recv %d byte msg1 %s and msg2 %s \n", n, buffer1, buffer2);
            close(connfd);
        }
        close(sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    sendmsg:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
        if (argc <= 2)
        {
            printf("usage: %s ip_address port_number\n", basename(argv[0]));
            return 1;
        }
        const char *ip = argv[1];
        int port = atoi(argv[2]);
    
        sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        assert(sock >= 0);
    
        int ret = connect(sock, (struct sockaddr *)&address, sizeof(address));
        assert(ret == 0);
    
        char buffer1[] = "hello";
        char buffer2[] = "world";
    
        struct msghdr msg;
        bzero(&msg, sizeof(msg));
        // 因为是针对TCP,所以msg_name无意义
        msg.msg_name = NULL;
        msg.msg_namelen = 0;
    
        //设置集中写
        struct iovec iovec_arry[2];
        iovec_arry[0].iov_base = (void *)buffer1;
        iovec_arry[0].iov_len = sizeof(buffer1);
        iovec_arry[1].iov_base = (void *)buffer2;
        iovec_arry[1].iov_len = sizeof(buffer2);
    
        msg.msg_iov = iovec_arry;
        msg.msg_iovlen = 2;
    
        int n = sendmsg(sock, &msg, 0);
        assert(n != -1);
        printf(" have send %d byte msg1 %s and msg2 %s \n", n, buffer1, buffer2);
        close(sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    运行结果:

    image-20220813123219330

    需要注意的是recvmsg只有在前面的buffer使用完之后,才会使用后面的buffer。这也是为啥把buffer1的大小设置为6

  • 相关阅读:
    select、poll、epoll三种IO多路复用的区别
    分组卷积和深度可分离卷积
    将vscode打造无敌的IDE(14) tasks.json和launch.json配置详解,随心所欲添加自动化任务
    从权限系统的菜单管理看算法和数据结构
    Java学习笔记
    linux驱动之设备树语法(2)
    数据结构_B树(C语言)
    【计算机组成与体系结构Ⅰ】章节测试(1-3)
    ZZULIOJ:1135: 算菜价
    基于MATLABsimulink的《电路原理》课程仿真实验平台开发
  • 原文地址:https://blog.csdn.net/qq_41474648/article/details/126335809