1 网络基础
- 网络应用程序设计模式:
- C/S -Client/Server架构
- 优点:协议选用灵活,可以缓存数据
- 缺点:对用户安全构成威胁,开发工作量大,调试困难。
- B/S-browser/server
- 优点:跨平台
- 缺点:只能使用http
- 协议的概念
- 规则:数据传输和数据解释的规则
- 原始协议---->(改进、完善)------>标准协议
- 典型协议:TCP/UDP HTTP FTP IP ARP
- 分层模型
OSI七层模型
- 物理层–双绞线、光纤
- 数据链路层–数据的传输和错误检测
- 网络层—为数据包选择路由
- 传输层—提供端对端的接口
- 会话层—解除或建立与别的节点的联系
- 表示层–数据格式化,代码转换,数据加密
- 应用层—文件传输,电子邮件,文件服务,虚拟终端。
TCP/IP四层模型
- 数据传输层—以太网帧协议
- 网络层—IP协议
- 传输层—TCP/UDP
- 应用层—ftp,http,ssh,telent
4. 协议格式
根据IP地址获取mac地址,再根据mac地址和ip地址进行发送数据包。
1.1 网络通信过程
以太网帧协议:
ARP请求:通过IP获取目的地址的MAC地址。
以太网帧协议:根据MAC地址,完成数据包传输。
IP协议:
- 版本:IPv4/IPv6。
- TTL:time to live。设置数据包在路由节点中的跳转上限。每经过一个路由节点,该值-1。当减为0的路由,有义务将该数据包丢弃。
- 源IP: 32位----4字节 192.168.1.108 —点分十进制 IP地址(string)———> 二进制在网络中传输
- 目的IP:32位----4字节
UDP协议:
- 16位:源端口号。 2^16 = 65536
- 16位:目的端口号。
TCP协议:
- 16位源端口号。
- 16位目的端口号
- 32位序号
- 32位确认序号
- 6个标志位。
- 16位的窗口大小。
对比 | C/S | B/S |
---|---|---|
优点 | 缓存大量数据、协议选择灵活、速度快 | 安全性、跨平台、开发工作量较小 |
缺点 | 安全性、开发工作量大 | 不能缓存大量数据、严格遵守http |
1.2 TCP协议
tcp时序
三次握手
- 主动发送连接请求端:发送SYN标志位,请求建立连接。携带序列号、数据字节数(0)、滑动窗口大小
- 被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。
- 主动发送连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号,
四次挥手
- 主动关闭连接请求端,发送FIN标志位。
- 被动关闭连接请求端,应答ACK标志位。------半关闭完成
- 被动关闭连接请求端,发送FIN标志位。
- 主动关闭连接请求端,应答ACK标志位。-------连接全部关闭
滑动窗口实现流量控制
发送给连接对象,本端的缓冲区大小(实时),保证数据不会发送丢失。
错误处理函数的封装思想
需要对所有的函数返回值进行判断,以确保程序出错时的有错误信息。
1.3 TCP状态转换
CLOSED:表示初始状态。
LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。
SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。
ESTABLISHED:表示连接已经建立。
FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:
FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。
FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。
FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以 close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。
LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。
TCP状态时序图:
结合三次握手、四次挥手 理解记忆。
1. 主动发起连接请求端: CLOSE -- 发送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 发送 ACK -- ESTABLISHED(数据通信态)
2. 主动关闭连接请求端: ESTABLISHED(数据通信态) -- 发送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半关闭)
-- 接收对端发送 FIN -- FIN_WAIT_2(半关闭)-- 回发ACK -- TIME_WAIT(只有主动关闭连接方,会经历该状态)
-- 等 2MSL时长 -- CLOSE
3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)
4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK
-- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE
重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)
netstat -apn | grep 端口号
2MSL时长:
一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。
保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)
端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
端口复用:
int opt = 1; // 设置端口复用。
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
半关闭:
通信双方中,只有一端关闭通信。 --- FIN_WAIT_2
close(cfd);
shutdown(int fd, int how);
how: SHUT_RD 关读端
SHUT_WR 关写端
SHUT_RDWR 关读写
shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。
2 Socket编程
2.1网络套接字socket
在通信过程中,套接字一定是成对出现的。
套接字的通信原理如下图:
sfd,cfd为文件描述符。发送缓冲区、和接收缓冲区。
一端的发送缓冲区对应另一端的接收缓冲区。我们使用同一个文件描述符发送缓冲区和接收缓冲区。
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
2.2 网络字节序
小端法:高位存高地址。低位存低地址。计算机存储一般用小端法。(inter)
大端法: 高位存低地址,低位存高地址。网络数据流采用大端字节序(IBM)
函数:
uint32_t htonl(uint32_t hostlong); //本地字节序转网络字节序,转32位的。(IP)转IP
uint16_t htons(uint16_t hostshort); //本地字节序转网络字节序,转16位。(port)转端口号
uint32_t ntohl(uint32_t netlong); //网络转本地 (IP)
uint16_t ntohs(uint16_t netshort);//网络转本地(port)
IP地址转换函数:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst); //本地字节序转网络字节序
- af:版本,AF_INET(IPv4),AF_INET6(IPv6)
- src:传入参数,IP地址(点分十进制)
- dst:传出参数,转换后的网络字节序的 IP地址。
- 返回值:成功返回1,异常返回0,说明src指向的不是一个有效的IP地址。失败返回-1,设置errono
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); //网络字节序转本地字节序
- af:版本,AF_INET(IPv4),AF_INET6(IPv6)
- src:传入参数,网络字节序IP(二进制)
- dst:传出参数,本地字节序的IP
- size:dst的大小
- 返回值:成功dst,失败返回NULL。
2.3sockaddr地址结构
struct sockaddr_in addr; //定义有效的数据类型
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9527);
addr.sin_addr.s_addr = htonl(INADDR_ANY); //取出系统中有效的任意IP地址。二进制类型。
bind(fd,(struct sockaddr )&addr,size); //但是传参要强制类型转换为sockaddr类型
struct sockaddr_in
{
sa_family_t sin_family; / address family: AF_INET /
in_port_t sin_port; / port in network byte order /
struct in_addr sin_addr; / internet address /
};
/ Internet address. /
struct in_addr { uint32_t s_addr; / address in network byte order */
};
2.4网络套接字函数
socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol); //创建一个 套接字
- domain:AF_INET、AF_INET6、AF_UNIX//ipv4 ipv6
- type:数据传输协议。SOCK_STREAM(流式协议)、SOCK_DGRAM(报式协议)
- protocol:所选协议中的代表协议。一般填0。流式协议-TCP。报式协议–UDP
- 返回值:成功返回新套接字对应的文件描述符。失败返回-1,设置errno。
bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //给socket绑定一个地址结构(IP+port)
- sockfd:socket函数返回值
-
- struct sockaddr_in addr;
-
- addr.sin_family = AF_INET;
-
- addr.sin_port = htons (8888);
-
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- addr : (struct sockaddr *)&addr
- addrlen:sizeof(addr) //地址结构的大小
listen函数-----设置同时与服务器建立连接的上限数。(可以同时进行三次握手的客户端数量)
int listen(int sockfd, int backlog);
- sockfd:socket 函数返回值
- backlog:上限数值。最大值128。
- 成功0,失败-1,设置errno
accept函数—阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:socket函数返回值
- addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)
-
- socklen_t clit_addr_len = sizeof(addr);
- addrlen:传入传出参数。入:addr的大小。出:客户端addr的实际大小。
-
- &clit_addr_len
- 返回值:成功返回能与服务器进行数据通信的socket对应的文件描述符。失败返回-1,设置errno
connct函数 --------使用现有的socket与服务器建立连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd:socket函数返回值
- addr:传入参数。服务器的地址结构
- addrlen:sizeof(addr)服务器地址结构的大小
- 返回值:成功返回0,失败-1,设置errno。
如果不使用bind绑定客户端地址结构,采用“隐式绑定”。
2.5 代码实例
一个客户端和一个服务器进行通信,一共会有三个套接字
TCP通信流程分析:
server:
- socket()创建socket
- bind() 绑定服务器地址结构
- listen()设置监听上限
- accept() 阻塞监听客户端连接
- read(fd)读socket获取客户端数据
- 小写—>大写
- write(fd)
- close()
client:
- socket() 创建socket
- connect() 与服务器建立连接
- write() 写数据到socket
- read() 读转换后的数据
- 显示读取结果
- close()
服务器端:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <ctype.h>
9
10 int main(int argc, const char* argv[])
11 {
12 // 创建监听的套接字
13 int lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
14 if(lfd == -1)
15 {
16 perror("socket error");
17 exit(1);
18 }
19
20 // lfd 和本地的IP port绑定
21 // 创建本地端口
22 struct sockaddr_in server;
23 memset(&server, 0, sizeof(server));
24 server.sin_family = AF_INET; // 地址族协议 - ipv4
25 server.sin_port = htons(8888); //端口号8888,htos函数将主机字节顺序转换为网络字节顺序,s---短整型
26 server.sin_addr.s_addr = htonl(INADDR_ANY);
27 int ret = bind(lfd, (struct sockaddr*)&server, sizeof(server)); //bind,将本地IP和端口与套接字绑定
28 if(ret == -1)
29 {
30 perror("bind error");
31 exit(1);
32 }
33
34 // 设置监听
35 ret = listen(lfd, 20);
36 if(ret == -1)
37 {
38 perror("listen error");
39 exit(1);
40 }
41
42 // 等待并接收连接请求
43 // 创建客户端
44 struct sockaddr_in client;
45 socklen_t len = sizeof(client);
46 int cfd = accept(lfd, (struct sockaddr*)&client, &len); //阻塞等待客户端连接请求,并接受连接
47 if(cfd == -1)
48 {
49 perror("accept error");
50 exit(1);
51 }
52
53 printf(" accept successful !!!\n");
54 char ipbuf[64] = {0};
55 printf("client IP: %s, port: %d\n",
56 inet_ntop(AF_INET, &client.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
57 ntohs(client.sin_port));
58 // 一直通信
59 while(1)
60 {
61 // 先接收数据
62 char buf[1024] = {0};
63 int len = read(cfd, buf, sizeof(buf));
64 if(len == -1)
65 {
66 perror("read error");
67 exit(1);
68 }
69 else if(len == 0)
70 {
71 printf(" 客户端已经断开了连接 \n");
72 close(cfd);
73 break;
74 }
75 else
76 {
77 printf("recv buf: %s\n", buf);
78 // 转换 - 小写 - 大写
79 for(int i=0; i<len; ++i)
80 {
81 buf[i] = toupper(buf[i]);
82 }
83 printf("send buf: %s\n", buf);
84 write(cfd, buf, len);
85 }
86 }
87
88 close(lfd);
89
90 return 0;
91 }
客户端:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8
9 int main (int argc, const char * argv[])
10 {
11 if (argc <2)
12 {
13 printf("eg:./a.out port\n");
14 exit(1);
15 }
16 int port = atoi(argv[1]);
17 // 创建套接字
18 int fd = socket(AF_INET,SOCK_STREAM,0);
19
20 //链接服务器
21 struct sockaddr_in serv;
22 memset(&serv,0,sizeof(serv));
23 serv.sin_family = AF_INET;
24 serv.sin_port = htons(port);
25 //serv.sin_addr.s_addr = htonl();
26 inet_pton(AF_INET,"127.0.0.1",&serv.sin_addr.s_addr);
27
28 connect(fd,(struct sockaddr*)&serv,sizeof(serv));
29 //通信
30 while(1)
31 {
32 //发送数据
33 char buf[1024];
34 printf("请输入要发送的字符串:\n");
35 fgets(buf,sizeof(buf),stdin);
36 write(fd,buf,strlen(buf));
37
38 //等待接受数据
39 int len = read(fd,buf,sizeof(buf));
40 if(len == -1)
41 {
42 perror("read error");
43 exit(1);
44 }
45 else if (len == 0)
46 {
47 printf("服务器端关闭来连接\n");
48 break;
49 }
50 else
51 {
52 printf("recv buf:%s\n",buf);
53 }
54
55 }
56 close (fd);
57
58 return 0;
59
60 }
3多进程并发服务器
3.1 设计思路
多进程并发服务器:
server.c
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1) {
cfd = Accpet(); 接收客户端连接请求。
pid = fork();
if (pid == 0){ 子进程 read(cfd) --- 小-》大 --- write(cfd)
close(lfd) 关闭用于建立连接的套接字 lfd
read()
小--大
write()
} else if (pid > 0) {
close(cfd); 关闭用于与客户端通信的套接字 cfd
contiue;
}
}
5. 子进程:
close(lfd)
read()
小--大
write()
父进程:
close(cfd);
注册信号捕捉函数: SIGCHLD
在回调函数中, 完成子进程回收
while (waitpid());
多线程并发服务器:
server.c
1. Socket(); 创建 监听套接字 lfd
2. Bind() 绑定地址结构 Strcut scokaddr_in addr;
3. Listen();
4. while (1) {
cfd = Accept(lfd, );
pthread_create(&tid, NULL, tfn, (void *)cfd);
//pthread_join()阻塞回收子线程
pthread_detach(tid); // pthead_join(tid, void **); 新线程---专用于回收子线程。
}
5. 子线程:
void *tfn(void *arg)
{
// close(lfd) 不能关闭。 主线程要使用lfd
read(cfd)
小--大
write(cfd)
pthread_exit((void *)10);
}
3.2 实现多进程并发服务器
3.3 实现多线程并发服务器
其中首字母大写的函数为经过封装判断返回值的自定义函数。
服务器端:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <wrap.h>
#include <pthread.h>
#include <stdint.h>
#define srv_port 9977
#define MAXLINE 8192
void * do_work(void *arg)
{
int n,i;
int cfd = (int)(intptr_t)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
while(1) {
n = Read(cfd,buf,MAXLINE);
if (n == 0 )
{
printf("the client cfd = %d closed...\n",cfd);
break ;
}
//printf("received from %s at Port %d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(client_addr.sin_port));
for (i = 0; i<n ;i++)
{
buf[i] = toupper(buf[i]);
}
Write(STDOUT_FILENO,buf,n);
Write(cfd,buf,n);
}
}
int main()
{
int lfd,cfd;
//int socket(int domain, int type, int protocol);
lfd = Socket(AF_INET,SOCK_STREAM,0);
//int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(srv_port);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));
//int Listen(int fd, int backlog);
int ret;
pthread_t tid;
ret = Listen(lfd,128);
//int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
while(1){
cfd = Accept(lfd,(struct sockaddr *)&client_addr,&client_addr_len); //阻塞等待client连接
char ipbuf[64] = {0};
printf("client IP:%s, port:%d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(client_addr.sin_port));
pthread_create(&tid,NULL,do_work,(void *)(intptr_t)cfd);
pthread_detach(tid);
}
return 0;
}
客户端可用
nc 192.168.1.77 9977 ,来与服务端建立连接。
3.4 select多路IO转接服务器
响应式–多路IO转接服务器
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
-
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
-
readfds: 监控有读数据到达文件描述符集合,传入传出参数
-
writefds: 监控写数据到达文件描述符集合,传入传出参数
-
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
-
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select多路IO转接:原理: 借助内核, select 来监听, 客户端连接、数据通信事件。
void FD_ZERO(fd_set *set); — 清空一个文件描述符集合。
fd_set rset; FD_ZERO(&rset);
void FD_SET(int fd, fd_set *set); — 将待监听的文件描述符,添加到监听集合中
FD_SET(3, &rset); FD_SET(5, &rset); FD_SET(6, &rset);
void FD_CLR(int fd, fd_set *set); — 将一个文件描述符从监听集合中 移除。
FD_CLR(4, &rset);
int FD_ISSET(int fd, fd_set *set); — 判断一个文件描述符是否在监听集合中。
返回值: 在:1;不在:0; FD_ISSET(4, &rset);
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
nfds:监听的所有文件描述符中,最大文件描述符+1 readfds: 读 文件描述符监听集合。 传入、传出参数 writefds:写 文件描述符监听集合。 传入、传出参数 NULL exceptfds:异常 文件描述符监听集合 传入、传出参数 NULL timeout: > 0: 设置监听超时时长。 NULL: 阻塞监听 0: 非阻塞监听,轮询 返回值: > 0: 所有监听集合(3个)中, 满足对应事件的总数。 0: 没有满足监听条件的文件描述符 -1: errno
3.4.1 思路
思路分析:
int maxfd = 0;
lfd = socket() ; 创建套接字
maxfd = lfd;
bind(); 绑定地址结构
listen(); 设置监听上限
fd_set rset, allset; 创建r监听集合
FD_ZERO(&allset); 将r监听集合清空
FD_SET(lfd, &allset); 将 lfd 添加至读集合中。
while(1) {
rset = allset; 保存监听集合
ret = select(lfd+1, &rset, NULL, NULL, NULL); 监听文件描述符集合对应事件。
if(ret > 0) { 有监听的描述符满足对应事件
if (FD_ISSET(lfd, &rset)) { // 1 在。 0不在。
cfd = accept(); 建立连接,返回用于通信的文件描述符
maxfd = cfd;
FD_SET(cfd, &allset); 添加到监听通信描述符集合中。
}
for (i = lfd+1; i <= 最大文件描述符; i++){
FD_ISSET(i, &rset) 有read、write事件
read()
小 -- 大
write();
}
}
}
select优缺点:
缺点: 监听上限受文件描述符限制。 最大 1024.
检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。
优点: 跨平台。win、linux、macOS、Unix、类Unix、mips