【计算机网络】网络编程接口 Socket API 解读(6)

这篇具有很好参考价值的文章主要介绍了【计算机网络】网络编程接口 Socket API 解读(6)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

         Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。

        本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。


recv

recv()           遵循 POSIX.1 - 2008

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

       ssize_t recv(int sockfd, void buf[.len], size_t len,
                        int flags);

       ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len,
                        int flags,
                        struct sockaddr *_Nullable restrict src_addr,
                        socklen_t *_Nullable restrict addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

4.接口描述

        recv()、recvfrom()、recvmsg() 调用用来从套接字接收消息,它们都可以用在连接或非连接套接字上。我们首先描述几个系统调用共同的特性,然后在介绍它们的差别。

        recv() 和 read(2) 的唯一区别就是是否有 flags 标记,当 recv() 的标记为 0 时,它基本上等同于 read(2)(具体参见注意部分)。同样的,下面调用:

recv(sockfd, buf, len, flags);

        等同于

recvfrom(sockfd, buf, len, flags, NULL, NULL);

         三个调用都在成功时返回消息的长度,如果提供的 buffer 盛不下消息,那么超出的消息可能会被遗弃,这要取决于接收套接字的类型。

        如果套接字上没有消息,那么接收调用会一直等到有消息到来,除非套接字是非阻塞的(参考 fcntl(2) ),这时会返回  -1 并将 errno 设置为 EAGAIN 或者 EWOULDBLOCK。接收调用正常情况只要有可用数据就会返回,接近请求的数据量,而不是一直等到接收到所有的请求数据量。

        应用程序可以使用 select(2)、poll(2)、epoll(7) 来决定套接字上有更多数据发生的时机。

        flags 参数

        flags 是下面值的位或值:

        MSG_CMSG_CLOEXEC (只有 recvmsg() 可用)

        设置接收文件描述符的异常关闭标记,通过 UNIX 域文件描述符的 SCM_RIGHTS 操作实现。这个标记的用途和 O_CLOEXEC 类似。

        MSG_DONTWAIT

        使能非阻塞操作,如果操作要阻塞,那么调用会报出 EAGAIN、EWOULDBLOCK 错误。这个和设置 O_NONBLOCK 标记类似(通过 fcntl(2) F_SETFL 操作),不过 MSG_DONTWAIT 只对本次调用管用,而 O_NONBLOCK 是设置到了文件描述符上,这样就会影响所有调用进程的所有线程以及其他持有该套接字句柄的进程。

        MSG_ERRQUEQUE

        这个标记指定了排队错误应该被套接字错误队列接收,错误信息会以依赖具体协议的类型来传递(对于 IPv4 是 IP_RECVERR)。用户应该提供足够的 buffer 大小。导致错误的原始报文的载荷大小以正常数据的 msg_iovec 格式传递,导致错误的原始报文的地址以 msg_name 形式提供。

        错误以 sock_extended_err 结构提供:

                  #define SO_EE_ORIGIN_NONE    0
                  #define SO_EE_ORIGIN_LOCAL   1
                  #define SO_EE_ORIGIN_ICMP    2
                  #define SO_EE_ORIGIN_ICMP6   3

                  struct sock_extended_err
                  {
                      uint32_t ee_errno;   /* Error number */
                      uint8_t  ee_origin;  /* Where the error originated */
                      uint8_t  ee_type;    /* Type */
                      uint8_t  ee_code;    /* Code */
                      uint8_t  ee_pad;     /* Padding */
                      uint32_t ee_info;    /* Additional information */
                      uint32_t ee_data;    /* Other data */
                      /* More data may follow */
                  };

                  struct sockaddr *SO_EE_OFFENDER(struct sock_extended_err *);

        ee_errno 包含了排队错误的 errno 值,ee_origin 是错误发源地的代码,其他域都是协议相关的。宏 SO_EE_OFFENDER 作为辅助信息返回错误发生点的网络对象地址。如果地址未知,那么 sockaddr 中的 sa_family 会包含 AF_UNSPEC,其他域为未知值。导致错误的报文的载荷以正常数据传递。

        对于本地错误,不传递地址(可以通过 cmsghdr 的 cmsg_len 值来确认)。 收到错误时,msghdr 会设置 MSG_ERRQUEQUE 标记。错误传递后,套接字错误码会根据下一个队列错误重新生成,在下一个套接字操作发生时传递。

        MSG_OOB

        这个标记请求接收通常不会在正常数据量中接到的带外数据。一些协议会将加速数据放在正常数据队列的前面,这就会导致这个标记没办法在这些协议中使用。

        MSG_PEEK

        这个标记指定从接收队列的头部接收数据,并且不会将数据从队列中移除。因此,下一次接收调用会返回相同的值。

        MSG_TRUNC

        对于原始协议(AF_PACKET),Internet datagram、netlink、UNIX datagram、sequenced-packet 套接字会返回实际的分组或报文长度,即使它比提供的 buffer 大。

        对于网络流套接字,参考 tcp(7)。

        MSG_WAITALL

        这个标记请求操作一直等到请求大小完全满足为止。然后,当信号、错误、连接断开、后面接收数据和之前的数据类型不同等发生时,调用仍然可能返回少于请求大小的数据。

recvfrom()

        recvfrom() 将收到的消息放到缓冲区 buf 中,调用者必须通过 len 参数指定 buf 的大小。

        如果 src_addr 不是 NULL,底层协议提供了消息的原地址,那么原地址会填到 src_addr 中,这种情况下 addrlen 是一个输入输出参数。调用前,它应该被初始化为 src_addr 缓冲区的大小,返回时会有原地址的实际大小更新。如果提供的缓冲器太小,那么返回地址就会被截断,这种情况下,addrlen 的值就会比提供的值大。

        如果调用者对原地址不感兴趣,那么 src_addr 和 addrlen 都应该被设置为 NULL。

recv()

        recv() 通常只能用于连接的套接字(参考 connect(2)),它相当于下面的调用:

           recvfrom(fd, buf, len, flags, NULL, 0);

recvmsg()

         recvmsg() 调用使用 msghdr 结构来减少需要传递参数的个数,结构体在 <sys/socket.h> 中定义,如下:

           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 */
           };

        msg_name 域指向用户分配的缓冲区,用来存放非连接套接字的源地址,调用者应该通过 msg_namelen 来设置缓冲器的大小,一旦成功返回,msg_namelen 会被设置为源地址的实际大小。如果应用不关心源地址,那么 msg_name 可以设置为 NULL。

        msg_iov 和 msg_iovlen 描述 scatter-gather 方式的区域(就是一些类分散的缓冲区列表),在 readv(2) 中有讨论。 

        msg_control 域具有 msg_controllen 长度,是一个为其他协议控制消息或者各种辅助数据准备的缓冲区。当 recvmsg() 调用时,msg_controllen 应指定 msg_control 缓冲区可用大小,一旦成功返回,它将包含控制消息序列的大小。消息格式如下:

           struct cmsghdr {
               size_t cmsg_len;    /* Data byte count, including header
                                      (type is socklen_t in POSIX) */
               int    cmsg_level;  /* Originating protocol */
               int    cmsg_type;   /* Protocol-specific type */
           /* followed by
               unsigned char cmsg_data[]; */
           };

        辅助数据应该只能被 cmsg(3) 中定义的宏来访问。

        作为例子,Linux 使用这个辅助数据机制在 UNIX 域套接字上传递扩展错误、IP 选项、文件描述符,参考 unix(7) 和 ip(7)。

        msghdr 中的 msg_fags 域会在 recvmsg() 返回时更新,它可能包含以下一些标记:

        MSG_EOR

        指示记录结束,记录中所有数据都已返回(通常用在 SOCK_SEQPACKET 中)。

        MSG_TRUNC

        指示数据报文的结尾部分因为大于提供的缓冲区大小而被丢弃。

        MSG_OOB

        指示有带外或者加速数据到达

5.返回值

        调用会返回接收到数据的字节数。

        发生错误时,返回 -1,并设置errno 来指示错误类型。

        当流套接字对端自己关闭了,那么将返回 0(传统意义的 EOF 返回)。

        各个域中的数据报文套接字允许 0 长度报文,当这样的报文收到时,返回的值就是 0。

        在流套接字请求接收 0 个字节时,返回值也可能是 0。

        错误值定义如下:

EAGAIN/EWOULDBLOCK 如果套接字被标记为非阻塞并且接收操作打算阻塞,或者设置了超时值,在数据到达前发生了超时。POSIX.1 允许使用两个错误值的任何一个,也不假设两个值相等,这就需要应用检查对两个错误都进行检查。
EBADF sockfd 参数是一个非法的文件描述符
ECONNREFUSED 远程主机拒绝网络连接(通常是没有运行请求的服务)
EFAULT 接收缓冲区指针指向进程外地址
EINTR 接收操作在数据来临前被传递来的信号打断
EINVAL 参数不合法
ENOMEM 无法申请 recvmsg() 的内存
ENOTCONN 套接字是一个面向连接的套接字,但是没有连接(参考 connect(2) 和 accept(2))
ENOTSOCK 文件描述符不是一个套接字

6.注意

       如果有 0 长度报文处于等待,那么 read(2) 和 标记为 0 的recv() 的处理行为是不同的。read(2) 没有任何影响(报文还在等待中),而 recv() 会消耗掉报文。

        参考 recvmmsg(2) 来看 Linux 系统特定的系统调用来在一次调用中处理多个报文。

7.代码

   Server program

       #include <netdb.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/socket.h>
       #include <sys/types.h>
       #include <unistd.h>

       #define BUF_SIZE 500

       int
       main(int argc, char *argv[])
       {
           int                      sfd, s;
           char                     buf[BUF_SIZE];
           ssize_t                  nread;
           socklen_t                peer_addrlen;
           struct addrinfo          hints;
           struct addrinfo          *result, *rp;
           struct sockaddr_storage  peer_addr;

           if (argc != 2) {
               fprintf(stderr, "Usage: %s port\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           memset(&hints, 0, sizeof(hints));
           hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
           hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
           hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
           hints.ai_protocol = 0;          /* Any protocol */
           hints.ai_canonname = NULL;
           hints.ai_addr = NULL;
           hints.ai_next = NULL;

           s = getaddrinfo(NULL, argv[1], &hints, &result);
           if (s != 0) {
               fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
               exit(EXIT_FAILURE);
           }

           /* getaddrinfo() returns a list of address structures.
              Try each address until we successfully bind(2).
              If socket(2) (or bind(2)) fails, we (close the socket
              and) try the next address. */

           for (rp = result; rp != NULL; rp = rp->ai_next) {
               sfd = socket(rp->ai_family, rp->ai_socktype,
                            rp->ai_protocol);
               if (sfd == -1)
                   continue;

               if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
                   break;                  /* Success */

               close(sfd);
           }

           freeaddrinfo(result);           /* No longer needed */

           if (rp == NULL) {               /* No address succeeded */
               fprintf(stderr, "Could not bind\n");
               exit(EXIT_FAILURE);
           }

           /* Read datagrams and echo them back to sender. */

           for (;;) {
               char host[NI_MAXHOST], service[NI_MAXSERV];

               peer_addrlen = sizeof(peer_addr);
               nread = recvfrom(sfd, buf, BUF_SIZE, 0,
                                (struct sockaddr *) &peer_addr, &peer_addrlen);
               if (nread == -1)
                   continue;               /* Ignore failed request */

               s = getnameinfo((struct sockaddr *) &peer_addr,
                               peer_addrlen, host, NI_MAXHOST,
                               service, NI_MAXSERV, NI_NUMERICSERV);
               if (s == 0)
                   printf("Received %zd bytes from %s:%s\n",
                          nread, host, service);
               else
                   fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

               if (sendto(sfd, buf, nread, 0, (struct sockaddr *) &peer_addr,
                          peer_addrlen) != nread)
               {
                   fprintf(stderr, "Error sending response\n");
               }
           }
       }
   Client program

       #include <netdb.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/socket.h>
       #include <sys/types.h>
       #include <unistd.h>

       #define BUF_SIZE 500

       int
       main(int argc, char *argv[])
       {
           int              sfd, s;
           char             buf[BUF_SIZE];
           size_t           len;
           ssize_t          nread;
           struct addrinfo  hints;
           struct addrinfo  *result, *rp;

           if (argc < 3) {
               fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
               exit(EXIT_FAILURE);
           }

           /* Obtain address(es) matching host/port. */

           memset(&hints, 0, sizeof(hints));
           hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
           hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
           hints.ai_flags = 0;
           hints.ai_protocol = 0;          /* Any protocol */

           s = getaddrinfo(argv[1], argv[2], &hints, &result);
           if (s != 0) {
               fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
               exit(EXIT_FAILURE);
           }

           /* getaddrinfo() returns a list of address structures.
              Try each address until we successfully connect(2).
              If socket(2) (or connect(2)) fails, we (close the socket
              and) try the next address. */

           for (rp = result; rp != NULL; rp = rp->ai_next) {
               sfd = socket(rp->ai_family, rp->ai_socktype,
                            rp->ai_protocol);
               if (sfd == -1)
                   continue;

               if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
                   break;                  /* Success */

               close(sfd);
           }

           freeaddrinfo(result);           /* No longer needed */

           if (rp == NULL) {               /* No address succeeded */
               fprintf(stderr, "Could not connect\n");
               exit(EXIT_FAILURE);
           }

           /* Send remaining command-line arguments as separate
              datagrams, and read responses from server. */

           for (size_t j = 3; j < argc; j++) {
               len = strlen(argv[j]) + 1;
                       /* +1 for terminating null byte */

               if (len > BUF_SIZE) {
                   fprintf(stderr,
                           "Ignoring long message in argument %zu\n", j);
                   continue;
               }

               if (write(sfd, argv[j], len) != len) {
                   fprintf(stderr, "partial/failed write\n");
                   exit(EXIT_FAILURE);
               }

               nread = read(sfd, buf, BUF_SIZE);
               if (nread == -1) {
                   perror("read");
                   exit(EXIT_FAILURE);
               }

               printf("Received %zd bytes: %s\n", nread, buf);
           }

           exit(EXIT_SUCCESS);
       }

下一篇 【计算机网络】网络编程接口 Socket API 解读(7)​​​​​​​文章来源地址https://www.toymoban.com/news/detail-728329.html

到了这里,关于【计算机网络】网络编程接口 Socket API 解读(6)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【计算机网络】网络编程接口 Socket API 解读(4)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 poll()           遵

    2024年02月09日
    浏览(36)
  • 【计算机网络】网络编程接口 Socket API 解读(2)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 遵循 POSIX.1 - 2008    

    2024年02月09日
    浏览(30)
  • 【计算机网络】网络编程接口 Socket API 解读(1)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 遵循 POSIX.1 - 2001、POS

    2024年02月09日
    浏览(28)
  • 【计算机网络】网络编程接口 Socket API 解读(11)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 遵循 POSIX.1-2008      

    2024年02月08日
    浏览(31)
  • 【计算机网络】网络编程接口 Socket API 解读(9)

             Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。         本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。 续  【计算机网络】网

    2024年02月08日
    浏览(34)
  • 计算机网络--网络编程(1)

    简单认识一下传输层中的UDP和TCP: TCP:有链接,可靠传输,面向字节流,全双工 UDP:无连接,不可靠传输,面向数据报,全双工 有链接类似于打电话,通了就是有链接。没通就一直在等待。 无连接类似于发短信,只管发,不管到。 可靠传输就是保证信息传输的可靠性。就

    2024年02月11日
    浏览(31)
  • 【计算机网络】4 Socket网络编程

    目录 写在前面的话 概览 环境 URL请求程序: 2. 系统时间查询 服务端 T_TCPServer.py代码 客户端 T_TCPClient.py代码 运行效果 3. 网络文件传输 服务端 TF_TCPServer.py代码 运行效果(后面加了远程功能,效果图暂时还在本地) 4. 网络聊天室 服务端 UDPServer.py代码 客户端 UDPClient.py代码 运

    2024年02月01日
    浏览(31)
  • 计算机网络---网络编程套接字(一)

    ✨ 计算机网络—网络编程套接字之UDP数据报套接字编程 作者介绍: 🎓作者:偷偷敲代码的青花瓷🐱‍🚀 👀作者的Gitee:代码仓库 📌系列文章推荐:计算机网络 ——网络原理之初识 ✨✨我和大家一样都是热爱编程✨,很高兴能在此和大家分享知识,希望在分享知识的同时,能和大

    2023年04月09日
    浏览(29)
  • 【计算机网络】网络编程套接字(二)

    简单TCP服务器实现 我们将会使用到的头文件放在 comm.h 文件中 创建套接字 创建过程和UDP服务器几乎完全一样,除了使用的是TCP服务器使用的是流式服务(SOCK_STREAM),UDP使用的是数据包服务(SOCK_DGRAM) 服务器绑定 绑定的过程和UDP服务器也是相同的,可以看着复习一下 定义好 st

    2024年02月13日
    浏览(35)
  • 【计算机网络】网络编程套接字(一)

    目录 1.预备知识 1.1.理解源IP地址和目的IP地址 1.2.认识端口号 1.2.1.理解\\\"端口号\\\"和\\\"进程ID\\\" 1.2.2.理解源端口号和目的端口号 1.3.认识TCP/UDP协议 1.3.1.TCP协议 1.3.2.UDP协议 1.4.网络字节序 网络字节序和主机字节序的转换 2.socket编程接口 2.1.sockaddr结构 struct sockaddr_in 的具体结构: 2.

    2024年02月08日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包