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

2023-09-15 09:21:58

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

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


send

send()           遵循 POSIX.1 - 2008

MSG_CONFIRM 是 Linux 扩展

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

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

       ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

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

4.接口描述

        send()、sendto()、sendmsg() 调用用来向另一个套接字发送消息。

        send() 通常只能用在连接状态(即接收者已知)的套接字上,send() 和 write(2) 的唯一不同是 send() 存在 flags 标记。当标记为 0 时,send() 和 write(2) 等效。同样的,下面的两个调用也是等效的。

           send(sockfd, buf, len, flags);
           sendto(sockfd, buf, len, flags, NULL, 0);

         sockfd 参数是 发送套接字的文件描述符。

        如果sendto() 用在连接模式(SOCK_STREAM、SOCK_SEQPACKET)的套接字上,那么参数 dest_addr 和 addrlen 将被忽略(当它们不是 NULL 或者 0时,会返回 EISCON 错误),并且当套接字没有连接时,会返回 ENOTCONN 错误。否则需要给定 addrlen 指定长度的 dest_addr 目标地址,目标地址由 msg.msg_name 给定,长度是 msg.msg_namelen 指定。

        对于 send() 和 sendto(),发送的消息放在 len 长度的 buf 中。对于 sendmsg(),消息由 msg.msg_iov 数组元素指定,sendmsg() 调用同时允许发送一些辅助数据(也称为控制信息)。

        如果消息太大以至于不能自动的透传给底层协议,调用会返回  EMSGSIZE 错误,该消息不会被发送。

        send() 调用没有显示的关于传输失败的指示,在内部检测到这类错误时也只会返回 -1。

        当消息不能装进套接字的发送缓冲区时,send() 正常会阻塞,除非套接字放进了非阻塞 I/O 模式,这种情况在非阻塞模式下会报告 EAGAIN 或者 EWOULDBLOCK 错误。这时可以使用 select(2) 来检测什么时候可以发送更多数据。

flags 参数

        flags 参数可以是下面值的位或:

        MSG_CONFIRM(Linux 2.3.15 后)

        告诉链路层有进展发生:从对端收到了一个成功的回复。如果链路层没有收到这个,那么它通常会在骚扰邻居(通过 ARP 单播)。这个标记只在IPv4/IPv6 的 SOCK_DGRAM 和 SOCK_RAW 套接字中可用,详细信息可以参考 arp(7)。

        MSG_DONTROUTE

        不要使用向网关发送我们的数据包,只发给和我们直接连接的主机。这个通常只有诊断或者路由程序使用。这个只用于支持路由的协议家族,packet 套接字不支持。

        MSG_DONTWAIT(Linux 2.2 后)

        开启非阻塞操作。如果操作想要阻塞,那么就会返回 EAGAIN 或者 EWOULDBLOCK 错误。这个行为和设置 O_NONBLOCK 标记(通过 fcntl(2) F_SETFL 操作)行为类似,但是 MSG_DONTWAIT 只对当前调用生效,而 O_NONBLOCK 是设置到打开文件描述上(参考 open(2)),这会影响调用进程中的所有线程以及其他持有指向该打开文件描述的文件描述符的进程。

        MSG_EOR(Linux 2.2 后)

        结束一个记录(当支持该语义时,用于 SOCK_SEQPACKET 类型的套接字)

        MSG_MORE(Linux 2.4.4 后)

        调用者有更多数据要发送。这个标记用在 TCP 套接字中来获得和 TCP_CORK 套接字选项意向的效果,区别是这个标记只对当前调用生效。

        MSG_NOSIGNAL(Linux 2.2 后)

        如果对端关闭了流套接字,不要生成 SIGPIPE 信号,不过仍然会返回 EPIPE 错误。这和使用 sigaction(2) 来忽略 SIGPIPE 效果差不多,还是 MSG_NONSIGNAL 只对当前调用生效,并且忽略 SIGPIPE 设置的是进程属性,会影响进程中的所有线程。

        MSG_OOB

        在支持该带外数据的套接字(比如 SOCK_STREAM)上发送带外数据。 底层协议必须也支持带外数据传输。

         MSG_FASTOPEN(Linux 3.7 后)

        尝试 TCP 快速打开(RFC7413)并发送在 SYN 中发送数据,就像 connect(2) 和 write(2) 合并一样,进行了隐式的 connect(2) 操作。它会一直阻塞知道数据被缓存并且握手结束。对于一个非阻塞套接字,它会返回缓存数据的大小并且发送一个 SYN 包。如果本地 cookie 不可用,它会返回 EINPROGRESS,并自动发送一个 SYN 带着快速打开 cookie 请求。调用者需要在新连接的套接字上重新发送数据。发生错误时,它会在握手失败时设置和 connect(2) 相同的 errno。这个标记需要 sysctl net.ipv4.tcp_fastopen 来开启 TCP 快速打开客户端支持。

        参考 tcp(7) TCP_FASTOPEN_CONNECT 套接字选项来查看一些可选的方法。

sendmsg()

        sendmsg() 使用的 msghdr 结构定义如下:

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

        msg_name 字段用来指定非连接数据包类型套接字的目标地址,它指向一个包含地址的缓冲区,msg_namelen 字段应该设置为地址的大小。对于连接套接字,这些字段应该对应的设置为 NULL 和 0。   

         msg_iov 和 msg_ivolen 字段指定了 scatter-gether 区域,和 writev(2) 类似。

        我们可以使用 msg_control 和 msg_controllen 成员发送控制信息(辅助数据),内核能够处理套接字最大的控制缓冲区大小由 /proc/sys/net/core/optmem_max 指定,参考 socket(7)。对于其他域套接字关于辅助数据的信息,可以参考 unix(7) 和 ip(7)。

        msg_flags 字段忽略。        

5.返回值

        调用成功时,返回已发送数据的字节数。

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

       下面这些标准错误值是由套接字层生成的,其他错误可能会由底层协议模块产生并返回,具体可以参考对应的手册页。

        错误值定义如下:

EACCESS(特定于由路径指定的 UNIX 域套接字)目的套接字写权限被拒绝,或者在目录前缀下的搜索权限被拒绝        
 EAGAIN/EWOULDBLOCK套接字被设置为非阻塞,但是请求操作打算阻塞。POSIX.1-2021 允许随意哪个错误都可以,并且不假定两个值相等,所以移植程序应该对每个值都进行判断。
EAGAIN(网络域数据报套接字)sockfd 指定的套接字还没有绑定到地址,并且在尝试绑定到临时端口时,临时端口用尽了。可以看 /proc/sys/net/ipv4/ip_local_port_rang 的讨论
EALREADY另一个快速打开(Fast Open)正在进行中
EBADFsockfd 不是一个打开的文件描述
ECONNRESET对端重置了连接
EDESTADDRREQ套接字不是连接模式,并且没有设置对端地址
EFAULT参数中指定了用户空间不合法的地址
EINTR数据发送完成前有信号发生,参考 signal(7)
EINVAL参数不合法
EISCONN连接模式的套接字早已经连接过了,但是又指定了接收者。(目前要么返回这个错误,要么直接忽略掉接收者参数)
EMSGSIZE套接字类型要求消息自动发送,但是消息的大小却使得这个无法完成
ENOBUFS网络接口输出队列满了。这个通常指示接口已经停止发送,但可能导致传输堵塞。(正常情况下,这个不会在 Linux 上发生,当设备队列溢出时,数据包会偷偷的被丢掉。)
ENOMEM没有内存了
ENOTCONN套接字没连接,也没有指定目标地址
ENOTSOCKsockfd 文件描述符没有指向一个套接字
EOPNOTSUPPflags 里面有些位设置不正确
EPIPE面向连接的套接字在本地被关闭,这种情况下,进程也会收到 SIGPIP 信号,除非我们设置了 MSG_NOSIGNAL。

6.注意

       参考 sendmmsg(2) 来查阅更多 Linux 系统用于一次调用传输多个数据包的信息。

        Linux 系统可能返回 EPIPE 而不是 ENOTCONN。

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);
       }
更多推荐

flutter聊天界面-TextField输入框实现@功能等匹配正则表达式展示高亮功能

flutter聊天界面-TextField输入框实现@功能等匹配正则表达式展示高亮功能一、简要描述描述:最近有位朋友讨论的时候,提到了输入框的高亮展示。在flutterTextField中需要插入特殊样式的标签,比如:“请@张三回答一下”,这一串字符在TextField中输入,当输入@时弹出好友列表选择,然后将“@张三

棒球游戏代码编写·棒球1号位

棒球代码编写1.棒球游戏的代码结构介绍棒球游戏的整体代码结构棒球游戏的整体代码结构可以按照以下几个模块进行划分:游戏引擎:游戏引擎是整个游戏的核心,负责管理游戏的各个系统,包括图形渲染、物理模拟、声音播放等。棒球游戏的引擎需要支持场景切换、角色动画、特效渲染等功能。角色系统:角色系统负责管理游戏中的角色,包括球员、裁判

Unity 课时 4 : No.4 模拟面试题

课时4:No.4模拟面试题C#1.请说明字符串中stringstr=nullstringstr=“”stringstr=string.Empty三者的区别第一个未作初始化没有值,第二个为空字符串,答案:str=null在堆中没有分配内存地址str=""和string.Empty一样都是在堆内存中分配了空间,里面存储的是

数据优化与可视化:3D开发工具HOOPS在BIM模型轻量化中的作用分析

在建筑和工程领域,BIM(建筑信息建模)是一种重要的数字化工具,但大型BIM模型往往需要大量的计算资源和存储空间。为了解决这一问题,HOOPS技术成为了一种关键工具,可以帮助实现BIM模型轻量化,提高性能、减小资源占用,从而加速项目进展。本文将探讨HOOPS如何帮助BIM模型实现轻量化的方式以及其在建筑和工程领域的重要

14:00面试,14:06就出来了,问的问题有点变态。。。

从小厂出来,没想到在另一家公司又寄了。到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到5月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%,这下搞的饭都吃不起了。还在有个朋友内推我去了一家互联网公司,兴冲冲见面试官,没想到一道题把我给问死了:如果模块请求http改为了h

Linux系统下建立Socket聊天服务器

目录1.服务器结构2.各模块函数2.1socket函数2.2bind函数2.3Listen函数2.4accept函数2.5接收发送函数2.6close函数2.7connect函数3代码段3.1服务器代码1.服务器结构使用socket的API函数编写服务端和客户端程序的步骤图示:2.各模块函数服务器:2.1socket函

滚雪球学Java(37):深入了解Java方法作用域和生命周期,让你写出更高效的代码

🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!前言在Java开发中,方法是程序的基本构建块之一。在编写Java代码时,必须了解Java方法的作用域和生命周期。这将有助于您更好地编写高效的Java代码。摘要本文将深入了解J

Linux虚拟化指南:构建虚拟化环境

虚拟化技术在计算领域具有广泛的应用,能够提高硬件资源的利用率、降低维护成本,并实现灵活的资源分配。Linux作为一种开源操作系统,在虚拟化方面也有多种选择和工具可供使用。下面将介绍如何构建Linux虚拟化环境,并提供一些建议和最佳实践。一、选择虚拟化平台1、KVM(Kernel-basedVirtualMachine)

QT 信号与槽

QT核心便是信号与槽,通过信号将数据在界面和类中,在本类和其他类中发送和接收。信号负责发送数据(也可以单纯的发送信号),槽负责接收。系统自定义的槽在相应组件上单机右键转到槽即可看见这列表,例如:一般按钮事件的点击(clicked),松开(released),按下(pressed)等。选择相应的槽,那么它会自动帮你做上个

基于SpringBoot的民宿管理平台系统的设计与实现

博主主页:一季春秋博主简介:专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发,远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容:毕业设计(Java项目、小程序等)、简历模板、学习资料、面试题库、技术咨询。🍅文末获取联系🍅精彩专栏推荐订阅👇🏻👇🏻不然下次找不到哟Sp

VR虚拟仿真在旅游课堂教学演示

首先,VR虚拟仿真能够为学生提供逼真的旅游体验。传统的旅游课堂教学主要以图片、文字和视频为主要教学工具,这无法给学生带来身临其境的感觉。而VR技术能够通过360度全景视角、立体声音和触觉反馈等功能,将学生置身于虚拟的旅游场景中。无论是登上古老的埃及金字塔,在巴黎卢浮宫欣赏名画,还是漫步在纽约时代广场的繁华街头,学生可以

热文推荐