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

news/2024/5/17 15:50:53 标签: TCP, UDP, socket, bind, accept

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

        本文讲述的 socket 内容源自 Linux 发行版 centos 9 上的 man 工具,和其他平台(比如 os-x 及不同版本会有些出入)。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。

一.socket() 

遵循 POSIX.1 - 2001、POSIX.1-2008、4.4BSD

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

int socket(int domain, int type, int protocol);

4.接口描述

        创建一个通信端点(endpoint,通信的每一端都可以成为一个 endpoint),返回指向该端点的文件描述符。socket() 是所有其他操作的前提,比如创建了 socket,才能进一步的设置以及使用网络。

5.参数

  • domain
    socket 的网络协议簇,通常包括:
    AF_INET ipv4     网络协议
    AF_INET6 ipv6    网络协议(有时也会兼容 ipv4)
    AF_UNIX          本地socket,也就是我们常说的 domain socket
  • type 
    SOCK_STREAM      (TCP)有序、可靠、双向的基于连接的字节流,可选支持带外数据传输
    SOCK_DGRAM       (UDP)数据报文传输,非连接的、不可靠的、有固定最大长度
    SOCK_SEQPACKET   (不粘包的 TCP)有序、可靠、双向具有最大数据报长度的传输
    SOCK_RAW          提供原始网络协议访问
    SOCK_RDM          提供可靠的数据报传输层,但是不保证报文顺序
    

注意:有些类型可能协议簇未实现

带外数据传输指的是 TCP 在紧急情况下通过调整报文在发送/接收缓冲区的位置以及数据包中添加紧急标记的逻辑。 

  • protocol
    指定具体的传输协议,比如 IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP,在netinet/in.h 中定义

6.返回值

        发生错误时返回 -1,设置 errno 指示错误码,否则返回一个新创建的整型文件描述符。

        可能的错误码包括:

错误码含义
EACCES没有权限创建对应的 socket
EAFNOSUPPORT实现不支持指定的 AF_ 地址家族
EINVAL未知的协议或者地址家族不可用
EINVALtype 参数不合法
EMFILE进程文件描述符到达最大限制
ENFILE系统文件描述符到达上限
ENOBUFS or ENOMEM内存不足
EPROTONOSUPPORTdomain 不支持指定的协议类型

二、bind

遵循 POSIX.1-2008

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

 int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

4.接口描述

        通过 socket() 接口创建 socket 后,socket 只存在于名字空间中,并没有实际的地址分配给它。bind 接口将 addr 指定的 IP 地址分配给由文件描述符 sockfd 指定的 socket。addrlen 指定了 addr 指针指向的地址结构的字节长度。以前我们将这个操作给 socket 分配名字。

        通常在 TCP_STREAM socket 接收连接前需要将一个本地地址通过 bind 分配给 socket

        名字绑定规则随着地址家族的不同而不同。

         addr 的数据结构也是随着地址家族的变化而变化的。sockaddr 结构的定义类似:

 struct sockaddr {
      sa_family_t sa_family;
      char        sa_data[14];
 }

         这个结构定义主要是为了防止编译器报错,主要是将各种地址结构做一个强制转换。

5. 返回值

        发生错误时返回 -1,设置 errno 指示错误码,否则返回一个新创建的整型文件描述符。

        可能的错误码包括:

错误码含义
EACCES地址是保护地址,并且用户不是超级用户
EADDRINUSE指定的地址已经使用
EADDRINUSE

对于 domain socket,端口号在地址结构体中

指定为 0,但在尝试 bind 到临时端口时,临时端口没有空闲的了

EBADFsockfd 不是可用的文件描述符
EINVALsocket 已经绑定到了一个地址
EINVALaddrlen 错误,或者 addr 不是一个可用的 domain 地址
ENOTSOCK文件描述符没有指向任何 socket
UNIX domain(AF_UNIX) 特定的错误码
EACCESS在路径前缀下无搜索权限
EADDRNOTAVAIL请求的接口不存在或者不是本地的接口
EFAULTaddr 指向了用户无法访问的地址空间
ELOOP解析地址时遇到了太多的符号链接
ENAMETOOLONG地址太长
ENOENT指定路径不存在
ENOMEM内核内存不足
ENOTDIR路径前缀不是一个目录
EROFSsocket inode 位于只读文件系统中

6.示例代码

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

       #define MY_SOCK_PATH "/somepath"
       #define LISTEN_BACKLOG 50

       #define handle_error(msg) \
           do { perror(msg); exit(EXIT_FAILURE); } while (0)

       int
       main(void)
       {
           int                 sfd, cfd;
           socklen_t           peer_addr_size;
           struct sockaddr_un  my_addr, peer_addr;

           sfd = socket(AF_UNIX, SOCK_STREAM, 0);
           if (sfd == -1)
               handle_error("socket");

           memset(&my_addr, 0, sizeof(my_addr));
           my_addr.sun_family = AF_UNIX;
           strncpy(my_addr.sun_path, MY_SOCK_PATH,
                   sizeof(my_addr.sun_path) - 1);

           if (bind(sfd, (struct sockaddr *) &my_addr,
                    sizeof(my_addr)) == -1)
               handle_error("bind");

           if (listen(sfd, LISTEN_BACKLOG) == -1)
               handle_error("listen");

           /* Now we can accept incoming connections one
              at a time using accept(2). */

           peer_addr_size = sizeof(peer_addr);
           cfd = accept(sfd, (struct sockaddr *) &peer_addr,
                        &peer_addr_size);
           if (cfd == -1)
               handle_error("accept");

           /* Code to deal with incoming connection(s)... */

           if (close(sfd) == -1)
               handle_error("close");

           if (unlink(MY_SOCK_PATH) == -1)
               handle_error("unlink");
       }

 三、accept

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
                  socklen_t *_Nullable restrict addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *_Nullable restrict addr,
                  socklen_t *_Nullable restrict addrlen, int flags);

4.接口描述

        accept 系统调用用于面向连接的 socket (SOCK_STREAM、SOCK_SEQPACKET),它会从监听的 socket(sockfd)的等待连接队列里拿到第一个连接请求,创建一个新的连接的 socket,并返回一个新的文件描述指向这个新的 socket。新创建的 socket 并没有处于监听状态,原来的 socket(sockfd)并不会受到任何影响。

        sockfd 是由 sockect() 创建的,并通过 bind 绑定到了本地地址上,并通过 listen 监听连接。

        addr 是一个指向 sockaddr 结构的指针,这个地址由对端 socket 的地址填充。而返回的 addr 的结构类型根据 socket 地址家族的不同而不同。当 addr 是 NULL 时,底层并不会对其填充,这种情况下 addrlen 也没有用,也应该是 NULL。

        addrlen 参数是一个输入输出参数,调用者必须使用 addr 指向的结构体的大小来初始化它,而在返回时,则会使用对端地址的实际大小来填充。

        如果提供的 buffer 太小,则返回的地址将会被截断,这种情况下,addrlen 会返回一个比提供值大的值。

        如果当前等待连接队列中没有待连接请求,并且 socket 没有被设置成非阻塞,那么 accept() 将会一直阻塞。如果 socket 设置为非阻塞,那么 accept() 将报错为 EAGAIN 或者 EWOULDBLOCK。

        为了获取 socket 上有连接请求过来,我们需要使用 select、poll、epoll。当一个新连接来临时,会产生一个可读事件,我们可以使用 accept 来继续从连接上获取一个 socket

        我们也可以设置 socket 上有连接时发送 SIGIO 信号。

        如果 flag 是 0,那么 accept4() 就等同于 accept()。flag 可以是下面配置的或起来的值,来实现不同的行为:

  • SOCK_NONBLOCK

                 设置新文件描述符的 O_NONBLOCK 属性

  • SOCK_CLOEXEC

                 设置新文件描述符的 FD_CLEXEC 属性。

5.返回值

        成功时,返回一个新接收的 socket 的文件描述符(非负值)。

        出错时,返回 -1,设置 errno 为错误码,addrlen 不会被修改。

  • 错误处理

        Linux 的 accept() 会将既存的网络错误也会给返回值,这个行为和其他 BSD socket 实现的行为不太一样。为了可靠性,我们应该处理 accept 返回网络错误,这些错误是和协议相关的。比如 EAGAIN 表示重传,在TCP/IP 的场景下,还有 ENETDOWN、 EPROTO、 ENOPROTOOPT, EHOSTDOWN、 ENONET、 EHOSTUNREACH、 EOPNOTSUPP、 ENETUNREACH等需要处理。

       可能的错误码包括:

错误码含义
EAGAIN或EWOULDBLOCKsocket 设置为非阻塞,目前没有可用连接。POSIX.1-2001 和 POSIX.1-2008 允许范围任何一个错误,同时并没有他们有相同的值,所以为了实现移植性,需要分别判断。
ECONNABORTED连接已中断
EFAULT

addr 不是用户地址空间可写的地址

EBADFsockfd 不是打开的文件描述符
EINVALsocket 没有在监听连接或者addrlen不合法
EINVALaccept4,flags 值不合法
ENOTSOCK文件描述符没有指向任何 socket
EINTR在连接到达前,系统调用被信号打断
EMFILE进程描述符数达到上限
EFAULTaddr 指向了用户无法访问的地址空间
ENFILE系统文件描述符达到上限
ENAMETOOLONG地址太长
ENOENT指定路径不存在
ENOMEM或ENOBUFS内核内存不足
EOPNOTSUPPsocket 不 SOCK_STREAM 类型
EPERM防火墙禁止连接
EPROTO协议错误

Linux 上,新创建的 socket 并不会从监听 socket 上继承 O_NONBLOCK 和 O_AYSNC 属性,这点和 canonical BSD socket 实现的行为不同。所以,实现可移植的程序不应该依赖这些行为。

值得注意的是,有时在我们收到 SIGIO 或者通过select、poll、epoll 获得到一个刻度的事件时,并不一定就会有一个连接等待连接,这是因为连接很可能会被异步网络错误或者其他线程通过 accept() 拿走了。在这种情况下,就会导致 accept 阻塞,直到下一个连接到达。为了保证 accept 永远不会阻塞,传来的 socketfd 需要有 O_NONBLOCK 属性。

在最初的 BSD socket 实现中,accept 的第三个参数是 int *,在 POSIX.1g 草稿版标准想把它改成 size_t *C,后来 POSIX 标准和glibc 2.x 定为 socket_t *。

遵循:

accept()   POSIX.1-2008

accept4    Linux 


http://www.niftyadmin.cn/n/5007891.html

相关文章

[machine Learning]推荐系统

其实严格来说推荐系统也是一种监督学习,我们需要根据已有数据进行预测,但是这种训练数据不是单纯的输入和输出问题,所以被归类为"超越监督学习"的一种? 今天去旁听了隔壁专业的机器学习课程,感觉自己的知识确实不是很系统,所以后面会找个机会把前面的代码给补充上.…

win10 磁盘命令 修复磁盘硬盘

windows 磁盘修复命令 Windows 系统中有多个磁盘修复命令&#xff0c;以下是其中一些常用的命令&#xff1a; 1. chkdsk命令&#xff1a;用于检测和修复文件系统中的错误。命令格式为“chkdsk [驱动器名] /f”。 2. sfc命令&#xff1a;用于扫描和修复系统文件。命令格式为“…

使用aidlux进行模型迁移、部署、推理

AidLux是一个构建在ARM硬件上&#xff0c;基于创新性跨Android/鸿蒙 Linux融合系统环境的智能物联网 (AIoT) 应用开发和部署平台。 说的直白点&#xff0c;aidlux就是一个在arm架构芯片的设备上运行的linux系统&#xff0c;我们可以将身边的安卓设备当作边缘设备&#xff0c;在…

Chinese-LLaMA-Alpaca-2模型的测评

训练生成效果评测 Fastchat Chatbot Arena推出了模型在线对战平台&#xff0c;可浏览和评测模型回复质量。对战平台提供了胜率、Elo评分等评测指标&#xff0c;并且可以查看两两模型的对战胜率等结果。生成回复具有随机性&#xff0c;受解码超参、随机种子等因素影响&#xff…

Docker 搭建Redis Cluster 集群

环境&#xff1a; centos7 redis:7.0.5 三主三从&#xff0c;六个节点 一、下载redis镜像 docker pull redis:7.0.5 二、创建虚拟网卡 docker network create redis-cluster# 查看创建的Docker网卡 docker network ls 网卡类型为bridge桥接类型 三、准备redis配置文件 redi…

形态的分类稳定性

假设存在一种形态类对象&#xff0c;这种对象只有形态属性&#xff0c;没有元素属性。对着这种对象的分类仅取决于其外部形态&#xff0c;而与这种对象的元素组成毫无关系。 很显然相较于化学元素&#xff0c;形态是没有半衰期的。时间的流逝并不会把一个方形变成一个圆形。如果…

Promise异步请求/async-await

问题&#xff1a;调接口时&#xff0c;非以往的函数异步请求去调接口。而是用到了Promise中.then方法 Promise Promise是一种用于处理异步操作的对象。它代表了一个尚未完成但预计会未来完成的操作&#xff0c;并提供了一种结构化的方式来处理操作的结果。它起到代理作用&…

芒果app逆向分析 (二)

接着上文,我们发现请求后的结果是加密的状态,我们需要解密成明文看数据。 前面提到无法使用frida,直接上xposed. 直接就hook出来了?? key = "xkSHHy5DQzYwbZS32zJBDyrHCHWMDGDk" iv = "4yXhd2Ta4m6dif54"堆栈记录下,方便后续使用: 调用堆栈:at ja…