TCP/IP网络编程(3)——地址族与数据序列

news2025/1/20 1:08:30

文章目录

    • 第 3 章 地址族与数据序列
      • 3.1 分配给套接字的 IP 地址与端口号
        • 3.1.1 网络地址(Internet Address)
        • 3.1.2 网络地址分类与主机地址边界
        • 3.1.3 用于区分套接字的端口号
      • 3.2 地址信息的表示
        • 3.2.1 表示 IPV4 地址的结构体
        • 3.2.2 结构体 sockaddr_in 的成员分析
      • 3.3 网络字节序与地址变换
        • 3.3.1 字节序(Order)与网络字节序
        • 3.3.2 字节序转换
      • 3.4 网络地址的初始化与分配
        • 3.4.1 将字符串信息转换为网络字节序的整数型
          • (1)inet_addr函数
          • (2)inet aton函数
          • (3)inet_ntoa函数
        • 3.4.2 网络地址初始化

第 3 章 地址族与数据序列

把套接字比喻成电话,那么目前只安装了电话机,本章讲解给电话机分配号码的方法,即给套接字分配 IP 地址和端口号。

3.1 分配给套接字的 IP 地址与端口号

IP是 Internet Protocol(网络协议)的简写,是为收发网络数据而分配给计算机的值。
端口号并非赋予计算机的值,而是为了区分程序中创建的套接字而分配给套接字的序号。

3.1.1 网络地址(Internet Address)

IP 地址分为以下两类:

  • IPv4(Internet Protocol version 4)4 字节地址族
  • IPv6(Internet Protocol version 6)16 字节地址族

目前通用的是 IPv4 , 虽然 IPv4 地址不够用提出 IPv6 ,后者的普及还需要很长的时间。

IPv4 标准的 4 字节 IP 地址分为网络地址和主机(指计算机)地址,且分为以下 A、B、C、D、E 等类型

在这里插入图片描述

因此,首先应向 SEMICOM 网络传输数据,也就是说,并非一开始就浏览所有4字节IP地址。而是仅浏览 4 字节 IP 地址的网络地址,先把数据传到 SEMICOM 的网络。SEMICOM 网络(构成网络的路由器)接收到数据后,浏览传输数据的主机地址(主机ID)并将数据传给目标计算机。数据传输过程:

在这里插入图片描述

上图中,某主机向 203.211.172.103 和 203.211.217.202 传递数据,其中 203.211.172 和 203.211.217 为该网络的网络地址,所以“向相应网络传输数据”实际上是向构成网络的路由器或者交换机传输数据,然后由路由器或者交换机根据数据中的主机地址向目标主机传递数据。

提示:路由器或交换机是一种完成外网与本网主机之间的数据交换的物理设备,它们实际上也是一种计算机,只不过是为特殊目的而设计的。

3.1.2 网络地址分类与主机地址边界

只需通过IP地址的第一个字节即可判断网络地址占用的总字节数,因为我们根据IP地址的边界区分网络地址,如下所示:

  • A 类地址的首字节范围为:0~127
  • B 类地址的首字节范围为:128~191
  • C 类地址的首字节范围为:192~223

还有如下这种表示方式(二进制):

  • A 类地址的首位以 0 开始
  • B 类地址的前2位以 10 开始
  • C 类地址的前3位以 110 开始

因此套接字收发数据时,数据传到网络后即可轻松找到主机。

3.1.3 用于区分套接字的端口号

IP地址用于区分计算机,只要有IP地址就能向目标主机传输数据,但是只有这些还不够,我们需要把信息传输给具体的应用程序。

所以计算机一般有 NIC(网络接口卡)数据传输设备。通过 NIC 接收的数据内有端口号,操作系统参考端口号把信息传给相应的应用程序。

端口号由 16 位构成,可分配的端口号范围是 0~65535 。但是 0~1023 是知名端口,一般分配给特定的应用程序,所以应当分配给此范围之外的值。

虽然端口号不能重复,但是 TCP 套接字和 UDP 套接字不会共用端接口号,所以允许重复。如果某 TCP 套接字使用了 9190 端口号,其他 TCP 套接字就无法使用该端口号,但是 UDP 套接字可以使用

总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最终的目的应用程序。

3.2 地址信息的表示

3.2.1 表示 IPV4 地址的结构体

结构体的定义如下

struct sockaddr_in
{
    sa_family_t sin_family;  //地址族(Address Family)
    uint16_t sin_port;       //16 位 TCP/UDP 端口号
    struct in_addr sin_addr; //32位 IP 地址
    char sin_zero[8];        //不使用
};

该结构体中提到的另一个结构体 in_addr 定义如下,它用来存放 32 位IP地址

struct in_addr
{
    in_addr_t s_addr; //32位IPV4地址
}

关于以上两个结构体的一些数据类型:

数据类型名称数据类型说明声明的头文件
int 8_tsigned 8-bit intsys/types.h
uint8_tunsigned 8-bit int (unsigned char)sys/types.h
int16_tsigned 16-bit intsys/types.h
uint16_tunsigned 16-bit int (unsigned short)sys/types.h
int32_tsigned 32-bit intsys/types.h
uint32_tunsigned 32-bit int (unsigned long)sys/types.h
sa_family_t地址族(address family)sys/socket.h
socklen_t长度(length of struct)sys/socket.h
in_addr_tIP地址,声明为 uint_32_tnetinet/in.h
in_port_t端口号,声明为 uint_16_tnetinet/in.h

3.2.2 结构体 sockaddr_in 的成员分析

  • 成员 sin_family

每种协议适用的地址族不同,比如,IPV4 使用 4 字节的地址族,IPV6 使用 16 字节的地址族。

地址族

地址族(Address Family)含义
AF_INETIPV4用的地址族
AF_INET6IPV6用的地址族
AF_LOCAL本地通信中采用的 Unix 协议的地址族

AF_LOACL 只是为了说明具有多种地址族而添加的。

  • 成员 sin_port

    该成员保存 16 位端口号,重点在于,它以网络字节序保存。

  • 成员 sin_addr

    该成员保存 32 位 IP 地址信息,且也以网络字节序保存

  • 成员 sin_zero

    无特殊含义。只是为结构体 sockaddr_in 结构体变量地址值将以如下方式传递给 bind 函数。

    在之前的代码中

    struct sockaddr_in serv_addr;
    
    if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");
    

    此处 bind 第二个参数期望得到的是 sockaddr 结构体变量的地址值,包括地址族、端口号、IP地址等。

    struct sockaddr
    {
        sa_family_t sin_family; //地址族
        char sa_data[14];       //地址信息
    }
    

    此结构体 sa_data 保存的地址信息中需要包含 IP 地址和端口号,剩余部分应该填充 0 ,但是这样对于包含地址的信息非常麻烦,所以出现了 sockaddr_in 结构体,然后强制转换成 sockaddr 类型,则生成符合 bind 条件的参数。

这边可能有些疑惑,为什么 sockaddr_in 类型可以强制转化为 sockaddr,不会出问题吗?
首先我们看占用内存,除去 sa_family_t 类型,sockaddr 结构体还占用 15 个字节;sockaddr_in 第一个成员也是 sa_family_t ,剩余占用 2+4+9=15 个字节,因此不用担心指针+1后地址错乱。再来看 sockaddr_in 结构体的第二个成员 —— sin_port(存放端口号),是 uint16_t 类型占用 2 个字节,相当于 sockaddr 结构体中的 sa_data[0] 和 sa_data[1] ,只不过要换一种读取方式,一个是十进制整数型,一个是字符型;同理, sockaddr_in 结构体的第三个成员 sin_addr 是无符号32位的整型,相当于sa_data[2]~[5],读取方式和端口号一样;第四个成员其实就是防止指针强制转换后内存不匹配问题。

3.3 网络字节序与地址变换

不同的 CPU 中,4 字节整数值1在内存空间保存方式是不同的。

有些 CPU 这样保存:

00000000 00000000 00000000 00000001

有些 CPU 这样保存:

00000001 00000000 00000000 00000000

两种一种是顺序保存,一种是倒序保存 。

3.3.1 字节序(Order)与网络字节序

CPU 保存数据的方式有两种,这意味着 CPU 解析数据的方式也有 2 种:

  • 大端序(Big Endian):高位字节存放到低位地址,低位字节存放到高位地址
  • 小端序(Little Endian):高位字节存放到高位地址,低位字节存放到低位地址
    记:小弟弟(小低低)

big.png
small.png

两台字节序不同的计算机在数据传递的过程中可能出现的问题:

zijiexu.png

因为这种原因,所以在通过网络传输数据时必须约定统一的方式,这种约定被称为网络字节序(Network Byte Order),非常简单,统一为大端序。即,先把数据数组转化成大端序格式再进行网络传输。

3.3.2 字节序转换

帮助转换字节序的函数:

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

通过函数名称掌握其功能,只需要了解:

  • htons 的 h 代表主机(host)字节序。
  • htons 的 n 代表网络(network)字节序。
  • s 代表两个字节的 short 类型,因此以 s 为后缀的函数用于端口转换
  • l 代表四个字节的 long 类型,所以以 l 为后缀的函数用于 IP 地址转换

用一个程序试验一下小端序转大端序会有什么样的结果

#include <stdio.h>
#include<arpa/inet.h>

int main(int argc, char* argv[])
{
    unsigned short host_port = 0x1234;
    unsigned short net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;

    net_port = htons(host_port);
    net_addr = htonl(host_addr);

    printf("Host ordered port:%#x \n", host_port);
    printf("Network ordered port:%#x \n", net_port);
    printf("Host ordered address:%#x \n", host_addr);
    printf("Network ordered address:%#x \n", net_addr);
}

用 visual studio 就可以直接在 Windows 下进行操作,结果如下图所示:

在这里插入图片描述

因为我们所使用的CPU大部分是小端序,如果你的结果和上面不一样,那就要注意你的CPU了。

为什么一个要用小端序,一个用大端序?
因为 Intel 和 AMD 的 CPU 都是用的小端序,大端序其优势更适合网络路由传输。在实际编程时候我们知道有这么个流程,调用一下函数即可。

3.4 网络地址的初始化与分配

3.4.1 将字符串信息转换为网络字节序的整数型

(1)inet_addr函数

sockaddr_in 中需要的是 32 位整数型,但是我们只熟悉点分十进制表示法,那么改如何把类似于 201.211.214.36 转换为 4 字节的整数类型数据呢 ?幸运的是,有一个函数可以帮助我们完成它,该函数将字符串形式的 IP 地址转换为网络字节序形式的 32 位整数型数据。

#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
//成功时返回 32 位大端序整数型值,失败时返回 INADDR_NONE

代码实现:

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char* argv[])
{
  char* addr1 = "1.2.3.4";
  char* addr2 = "1.2.3.256";

  unsigned long conv_addr = inet_addr(addr1);
  if (conv_addr == INADDR_NONE)
    printf("Error occured! \n");
  else
    printf("Network ordered integer addr: %#lx \n", conv_addr);

  conv_addr = inet_addr(addr2);
  if (conv_addr == INADDR_NONE)
    printf("Error occured! \n");
  else
    printf("Network ordered integer addr: %#lx \n", conv_addr);
}

运行结果:
在这里插入图片描述

1个字节能表示的最大整数为255,也就是说代码中 addr2 是错误的IP地址。
从运行结果可以看出,inet addr函数不仅可以把IP地址转成32位整数型,而且可以检测无效的IP地址。

(2)inet aton函数

inet aton 函数与inet addr函数在功能上完全相同,也将字符串形式IP地址转换为32位网络字节序整数并返回。

#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
/*
成功时返回 1 ,失败时返回 0
string: 含有需要转换的IP地址信息的字符串地址值
addr: 保存转换结果的 in_addr 结构体变量的地址值
*/

inet aton 函数利用了in_addr 结构体,且其使用频率更高。实际编程中若要调用 inet_addr 函数,需将转换后的IP地址信息代人sockaddr_in 结构体中声明的 in_addr 结构体变量。而 inet_aton 函数则不需此过程,因为函数会自动把结果填入该结构体变量

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

void error_handling(char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

int main(int argc, char *argv[])
{
  char *addr = "127.232.124.79";
  struct sockaddr_in addr_inet;

  if (!inet_aton(addr, &addr_inet.sin_addr))
    error_handling("Conversion error");
  else
    printf("Network ordered integer addr: %#x \n",
      addr_inet.sin_addr.s_addr);
  return 0;
}

结果如下:
在这里插入图片描述

(3)inet_ntoa函数

还有一个函数,与 inet_aton() 正好相反,它可以把网络字节序整数型IP地址转换成我们熟悉的字符串形式,函数原型如下:

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
//成功时返回保存转换结果的字符串地址值,失败时返回 NULL 空指针

该函数将通过参数传入的整数型IP地址转换为字符串格式并返回。但要小心,返回值为 char 指针,返回字符串地址意味着字符串已经保存在内存空间,但是该函数未向程序员要求分配内存,而是再内部申请了内存保存了字符串。也就是说调用了该函数后要立即把信息复制到其他内存空间。因为,若再次调用 inet_ntoa 函数,则有可能覆盖之前保存的字符串信息。总之,再次调用 inet_ntoa 函数前返回的字符串地址是有效的。若需要长期保存,则应该将字符串复制到其他内存空间

代码实现:

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
  struct sockaddr_in addr1,addr2;
  char* str_ptr;
  char str_arr[20];
  
  addr1.sin_addr.s_addr = htonl(0x1020304);
  addr2.sin_addr.s_addr = htonl(0x1010101);

  str_ptr = inet_ntoa(addr1.sin_addr);
  strcpy(str_arr, str_ptr);
  printf("Dotted-Decimal notation1: %s \n", str_ptr);

  inet_ntoa(addr2.sin_addr);
  printf("Dotted-Decimal notation2: %s \n", str_ptr);
  printf("Dotted-Decimal notation3: %s \n", str_arr);
  return 0;
}

运行结果:

在这里插入图片描述

从运行结果不难看出,如果我们调用了一次 inet_ntoa 函数,如果不及时根据返回的 char 指针将字符串复制,第二次调用 inet_ntoa 函数会覆盖原来的字符串的内存。

3.4.2 网络地址初始化

结合前面的内容,介绍套接字创建过程中,常见的网络信息初始化方法:

struct sockaddr_in addr;
char *serv_ip = "211.217,168.13";          //声明IP地址族
char *serv_port = "9190";                  //声明端口号字符串
memset(&addr, 0, sizeof(addr));            //结构体变量 addr 的所有成员初始化为0
addr.sin_family = AF_INET;                 //制定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port));    //基于字符串的IP地址端口号初始化

但是,每次创建服务器端套接字都要输入IP地址会有些繁琐,此时可如下初始化地址信息

struct sockaddr_in addr;
char *serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port)); 

与之前方式最大的区别在于,利用常数 INADDR ANY 则可自动获取运行服务器端的计算机IP地址,不必亲自输入。而且,若同一计算机中已分配多个IP地址(多宿主(Multi-homed)计算机,一般路由器属于这一类),则只要端口号一致就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式。而客户端中除非带有一部分服务器端功能,否则不会采用

完整版笔记请参考
TCP/IP网络编程笔记完整版

最后感谢大家的支持,谢谢!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

王道操作系统笔记(二)———— 进程与线程

文章目录一、进程的概念和特征1.1 进程的概念1.2 进程的组成1.3 进程的特征1.4 进程的状态与转换1.5 进程控制1.6 进程的通信1.6.1 共享存储1.6.2 消息传递1.6.3 管道通信1.7 父进程与子进程二、线程概念和多线程模型2.1 线程的概念2.2 线程的属性2.3 线程的实现方式2.4 多线程…

C#【必备技能篇】DatagridView添加行时,设置行标题单元格的值为行数

文章目录1、DatagridView添加行的代码2、方法一&#xff1a;【每次添加行都重新刷新了全部的行数&#xff0c;不推荐】3、方法二&#xff1a;【只有一个DatagridView时&#xff0c;推荐此方法】4、方法三&#xff1a;【通用方法&#xff0c;多个DatagridView都有这个需求时&…

利用 Burp Suite 进行密码爆破

利用 Burp Suite 进行密码爆破1.Intruder 功能介绍2.攻击类型3.实战4.验证码爆破1.Intruder 功能介绍 使用 BP 工具的 Intruder 模块高度可配置&#xff0c;可以对目标网站进行密码爆破&#xff0c;一般被用于网站的安全渗透测试场景 BP 工具的 Intruder 模块包含几个功能标签…

解决2022.3.1版本中 IDEA中 XML文件屎黄色背景 的方法

问题&#xff1a;在idea打开mybatis的xml映射文件&#xff0c;出现大面积黄色背景提示 1&#xff1a;打开文件&#xff0c;点击设置 2&#xff1a;打开编辑器--> 检查--> SQL 3&#xff1a;受不了&#xff0c;我的是中文&#xff0c;我换成英文继续了 找到 No data sou…

CDH6.3.2 ORC文件格式 Spark引擎查询数组越界异常

组件版本: flink1.13.2 cdh6.3.2 hive2.1.1 问题描述: CDH6.3.2 ORC文件格式 Spark引擎查询数组越界异常 java.io.IOException: java.lang.ArrayIndexOutOfBoundsException: 7 ‘org.apache.orc.impl.ReaderImpl::ReaderImpl.java:385’, org.apache.hadoop.hive.ql.io.orc.Re…

解决OBS录屏模糊问题

相信大家在使用OBS过程中也会遇到录屏模糊的问题&#xff0c;网上有很多配置教程&#xff0c;尝试了视频比特率、提高OBS当中其他的硬件参数。 模糊是要分情况的&#xff0c;如果是静态情况下模糊&#xff0c;就是屏幕不动的时候录制的视频也很模糊&#xff0c;那就是视频的基…

linux中使用KubeSphere和k8s 部署springboot项目

1、创建项目----》按照做的项目名称建 创建一个项目 创建后&#xff0c;如图所示&#xff1a; 2、工作负载---》就是创建服务容器Prod第一步&#xff1a;创建一个工作负载服务-->基本信息第二步&#xff1a;容器组设置 设置容器端口&#xff0c;健康检查&#xff0c;环境…

基于Gentoo发行版本的Calculate Linux 23发布

导读Calculate Linux 是一个为在组织环境中快速部署而优化的 Linux 发行版。它以 Gentoo Linux 项目为基础&#xff0c;包括许多预配置的功能。 Calculate Linux 以五种方式发布。Calculate Linux Desktop&#xff08;CLD&#xff09;、Calculate Directory Server&#xff08;…

【涵子来信python大全】——第二季——opencv第三篇-numpy和颜色通道解释

各位亲爱的读者&#xff0c;博主&#xff1a; 大家好&#xff0c;我是涵子。今天我们继续讲讲opencv&#xff0c;讲讲其中numpy的秘密。如果不清楚上一章的内容&#xff0c;请从链接或者主页回去先读一遍之前的文章&#xff0c;否则今天的内容很难理解。 【涵子来信&pyth…

人工智能ai写作系统,ai智能写作机器人

人工智能AI大数据深度&#xff1a;基于伪原创算法&#xff0c;采用神经网络算法&#xff0c;在超过1535000篇文章中进行自动学习、聚合算法进行人工智能的创建&#xff0c;内容语义不变&#xff0c;媒体阿里、腾讯、百度均于日前在百家号内容创作者盛典上推出人工智能创作支撑平…

Vue2 Vuex在大型项目中的应用

文章目录前言一、总体结构二、代码结构1. 文件层2. 一级功能模块3. 二级功能模块4. 总状态总结前言 参考去年参与的大型ERP项目, 我主要负责财务模块的前端部分. 这个项目有几百个前端页面(具体多少没算过), 状态管理结构应该是具有参考价值的. 一、总体结构 项目标准中约定仅…

CobaltStrike与Metasploit联动方法

文章目录CobaltStrike联动Metasploit方法一方法二Metasploit联动CobaltStrikeCobaltStrike联动Metasploit CobaltStrike &#xff08;简称CS&#xff09;及 MetaSploit &#xff08;简称MSF&#xff09;各有所长&#xff0c;CS更适合作为稳控平台&#xff0c;MSF更适用于与各类…

包体积优化·工具论·初识包体积优化

“ 【小木箱成长营】包体积优化系列文章&#xff1a; 包体积优化 实战论 怎么做包体积优化? 做好能晋升吗? 能涨多少钱? 包体积优化 方法论 揭开包体积优化神秘面纱 ”一、引言 Hello&#xff0c;我是小木箱&#xff0c;欢迎来到小木箱成长营系列教程&#xff0c;今天将…

预制菜开启春节之战,破局立新正在进行时

撰稿 | 火华 来源 | 贝多财经 对预制菜行业来说&#xff0c;2022年绝对是浓墨重彩的一年。 这条汇集了餐饮企业、专业预制菜企业、冷冻食品企业、农牧水产企业、生鲜电商企业的赛道&#xff0c;在乏善可陈的商业市场中野蛮生长一路高歌。 有调研数据显示&#xff0c;“自己做…

嵌入式linux-僵尸进程?

1.僵尸进程 1.1 僵尸进程的由来和概念 通常&#xff0c;子进程结束之后&#xff0c;需要父进程为子进程进行回收&#xff0c;俗称“收尸”&#xff0c;则回收子进程占用的一些内存资源&#xff0c;父进程通过调用wait()&#xff08;或其变体 waitpid()、waitid()等&#xff0…

公共数据 | CnOpenData中国省际铁路通行时间数据

中国省际铁路通行时间数据 一、数据简介 本数据来自南京大学长江产业经济研究院《全国统一大市场下的省际铁路交通研究报告》的附录部分。中国的铁路&#xff08;高铁&#xff09;建设取得了辉煌成果。但受铁路时刻众多、历史数据不容易搜集整理的限制&#xff0c;学术与政策研…

Linux进程状态和优先级

我的另一篇有关进程概念的博客&#xff1a;Linux 进程概念 目录 一、操作系统进程状态 1.1 运行状态(R) 1.2 阻塞状态(S) 1.3 挂起状态(S&#xff09; 二、Linux操作系统内核中的进程状态 2.1 进程状态种类 2.2 查看R和S进程状态 2.3 T和t状态 2.3.1 T状态 2.3.2 t状态…

python正则表达式与回溯绕过waf

1.正则表达式的背景 正则表达式的历史&#xff1a;美国的两个人类神经元研究者&#xff0c;使用特殊的符号描述。之后有一位科学家将这门技术引入了数学&#xff0c;将这门技术命名为正则表示式。 肯汤普森在编写UNIX系统时&#xff0c;将正则引入到了一个编辑器 绝大多数编…

GNN实战——KarateClub数据集

GNN&#xff1a;graph neural network 图神经网络&#xff0c;是⼀种连接模型&#xff0c;通过⽹络中节点之间的信息传递(message passing)的⽅式来获取图中的依存关系(dependence of graph)&#xff0c;GNN通过从节点任意深度的邻居来更新该节点状态&#xff0c;这个状态能够表…

Linux网络编程 第四天

目录 学习目标 多路IO-poll 多路IO-epoll 进阶epoll 用实验验证LT和ET模式 epoll反应堆 学习目标 1 了解poll函数 2 熟练使用epoll多路IO模型 3 了解epoll ET/LT触发模式并实现 4 理解epoll边缘非阻塞模式并实现 5 了解epoll反应堆模型设计思想 6 能看懂epoll反应堆模型的…