【Linux网络编程】4.TCP协议、select多路IO转换

2024-05-08 09:52

本文主要是介绍【Linux网络编程】4.TCP协议、select多路IO转换,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

TCP协议

TCP通讯时序

三次握手

四次挥手

滑动窗口

测试代码1

测试结果

Address already in use解决方法

批量杀进程

测试代码2

测试结果

测试代码4

测试结果

TCP状态转换

主动发起连接请求端

主动关闭连接请求端

被动接收连接请求端

被动关闭连接请求端

2MSL时长

端口复用

测试代码5

测试结果

半关闭

参数sockfd

参数how

select多路IO转换

FD_ZERO

参数set

FD_SET

参数fd

参数set

FD_CLR

参数fd

参数set

FD_ISSET

参数fd

参数set

返回值

select

参数nfds

参数readfds

参数writefds

参数exceptfds

参数timeout

返回值

优缺点

测试代码6

测试结果

TCP协议

TCP通讯时序

三次握手

  1. 主动发起连接请求端,发送SYN标志位,请求建立连接。 携带序号、数据字节数(0)、滑动窗口大小。

  2. 被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

  3. 主动发起连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号。

巧记

  • 女:我喜欢你。

  • 男:好,我知道了,我也喜欢你。

  • 女:好,我也知道了,那我们在一起吧。

四次挥手

 

  1. 主动关闭连接请求端, 发送FIN标志位。

  2. 被动关闭连接请求端, 应答ACK标志位。(半关闭完成

  3. 被动关闭连接请求端, 发送FIN标志位。

  4. 主动关闭连接请求端, 应答ACK标志位。(连接全部关闭

巧记

  • 女:我不喜欢你了,我们分手吧,还有什么想说的赶紧说吧。

  • 男:好,我知道了,我也不喜欢你了。

  • 男:我没什么想说的了,我们分手吧。

  • 女:好,我也知道了,我们以后不要再联系了。

滑动窗口

 

  • mss:一条数据的最大数据量。

  • win:滑动窗口

  • 1:第1次握手。

  • 2:第2次握手,应答,滑动窗口的空间为6k。

  • 3:第3次握手。

  • 4~9:连续发送6次数据,每次发送1k的数据。

  • 10:应答接收到6k的数据。win 2048:处理完2k的数据,空出2k的数据空间。

  • 11:应答接收到6k的数据。win 4096:已经处理了4k的数据,空出了4k的空间。

  • 12:发送1k的数据。

  • 13:发送数据完成,第1次挥手。

  • 14~16:处理接收到的数据,第2次挥手。

  • 17:第3次挥手。

  • 18:第4次挥手。

测试代码1

多进程服务器接收多个客户端数据,并返回。

/*多进程并发服务器
*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>#define DuanKouHao 8080void ChuLi_HanShu() //处理函数
{while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收;return;
}int main(int argc, char *argv[])
{int fd_FWQ; //服务器文件描述符int fd_KHD; //客户端文件描述符int flag;struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构socklen_t KHD_DaXiao;                //客户端大小pid_t JinCheng_ID;                   //进程IDint ZiJie_DaXiao;                    //字节大小char data[1024];char Show_Data[1024]; //显示的数据clock_t start, stop;  //clock_t为clock()函数返回的变量类型double duration;bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构if (flag == -1){perror("绑定服务器地址结构错误");exit(1);}flag = listen(fd_FWQ, 6); //设置连接上限if (flag == -1){perror("设置连接上限错误");exit(1);}KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);while (1){fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接if (fd_KHD == -1){perror("建立客户端连接错误");exit(1);}JinCheng_ID = fork(); //创建子进程if (JinCheng_ID < 0){perror("创建子进程错误");exit(1);}else if (JinCheng_ID == 0) //子进程{close(fd_FWQ);break;}else if (JinCheng_ID > 0) //父进程,注册信号捕捉函数{struct sigaction ChuLi_FangShi;ChuLi_FangShi.sa_handler = ChuLi_HanShu; //捕捉信号后的处理函数sigemptyset(&ChuLi_FangShi.sa_mask);     //清空信号集ChuLi_FangShi.sa_flags = 0;              //默认处理方式flag = sigaction(SIGINT, &ChuLi_FangShi, NULL);if (flag == -1){perror("注册处理函数错误");exit(1);}close(fd_KHD);}}if (JinCheng_ID == 0) //子进程{while (1){ZiJie_DaXiao = read(fd_KHD, &data, sizeof(data)); //读取客户端数据if (ZiJie_DaXiao == -1){if (errno == EINTR){continue;}else{perror("读取数据错误");exit(1);}}sprintf(Show_Data, "这是%d号服务器子进程,接收到的数据是%s\n", getpid(), data);write(fd_KHD, Show_Data, strlen(Show_Data));        //发回给客户端write(STDOUT_FILENO, Show_Data, strlen(Show_Data)); //显示到终端}}return 0;
}

测试结果

 

Address already in use解决方法

netstat -apn  |  grep 端口号

批量杀进程

ps aux | grep 运行的文件 | awk '{print $2}' | xargs kill -9

测试代码2

单进程服务器与千级数量级的客户端建立连接。

/*CeShi2_FWQ.c单进程服务器与千级数量级的客户端建立连接
*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>#define DuanKouHao 8080void ChuLi_HanShu() //处理函数
{while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收;return;
}int main(int argc, char *argv[])
{int fd_FWQ; //服务器文件描述符int fd_KHD; //客户端文件描述符int flag;struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构socklen_t KHD_DaXiao;                //客户端大小pid_t JinCheng_ID;                   //进程IDint ZiJie_DaXiao;                    //字节大小char data[1024];char Show_Data[1024]; //显示的数据clock_t start, stop;  //clock_t为clock()函数返回的变量类型double duration;bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构if (flag == -1){perror("绑定服务器地址结构错误");exit(1);}//flag = listen(fd_FWQ, 10); //设置连接上限flag = listen(fd_FWQ, 100); //设置连接上限if (flag == -1){perror("设置连接上限错误");exit(1);}KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);flag = 0;start = clock();while (1){fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接if (fd_KHD == -1){perror("建立客户端连接错误");exit(1);}flag++;printf("第%d次连接,客户端IP地址是:%s\n", flag,inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息close(fd_KHD);if (flag >= 2000){break;}}stop = clock();duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点printf("时间:%f\n", duration);return 0;
}
/*CeShi2_KHD.c多进程客户端测试服务器连接代码
*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>#define FWQ_IP "10.3.22.7" //服务器IPint main(int argc, char *argv[])
{int fd_FWQ; //服务器int flag;struct sockaddr_in FWQ_DiZhi; //服务器地址int ZiFuShu;                  //字符数char data[1024];              //数据pid_t JinCheng_ID;            //进程IDFWQ_DiZhi.sin_family = AF_INET;                         //IPv4FWQ_DiZhi.sin_port = htons(8080);                       //端口号8080flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IPif (flag == -1){perror("十进制IP转换网络IP错误");exit(1);}flag = 0;while (1){JinCheng_ID = fork(); //创建子进程if (JinCheng_ID < 0){perror("创建子进程错误");exit(1);}else if (JinCheng_ID == 0) //子进程{break;}flag++;if (flag >= 2000){break;}}if (JinCheng_ID == 0) //子进程{printf("这是%d子进程\n", getpid());fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}sleep(5);flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));if (flag == -1){perror("连接服务器错误");exit(1);}close(fd_FWQ);}while(1);return 0;
}
测试结果

同时连接服务器的客户端上限为10时。

同时连接服务器的客户端上限为100时。  

测试代码4

多线程服务器与千级数量级的客户端建立连接。

/*CeShi4_FWQ.c多线程并发服务器处理客户端数据,并发回去
*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>#define DuanKouHao 8080               //端口号
#define XianCheng_XinXi_ShuLiang 2000 //线程信息数量
#define data_DaXiao 1024              //数据缓冲区大小struct XianCheng_XinXi //线程信息
{struct sockaddr_in KHD_XinXi;int fd_KHD;
};void *ChuLi_HanShu(void *arg) //处理函数
{int leng;char data[data_DaXiao];struct XianCheng_XinXi *xian_cheng_xin_xi = (struct XianCheng_XinXi *)arg;while (1){leng = read(xian_cheng_xin_xi->fd_KHD, data, sizeof(data));if (leng == -1){if (errno == EAGAIN || errno == EWOULDBLOCK){continue;}else{break;}}}close(xian_cheng_xin_xi->fd_KHD);return (void *)0;
}int main(int argc, char *argv[])
{int fd_FWQ; //服务器文件描述符int fd_KHD; //客户端文件描述符int flag;int flag1;struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构socklen_t KHD_DaXiao;                //客户端大小pthread_t XianCheng_ID;              //线程IDint ZiJie_DaXiao;                    //字节大小char data[1024];clock_t start, stop; //clock_t为clock()函数返回的变量类型double duration;struct XianCheng_XinXi xian_cheng_xin_xi[XianCheng_XinXi_ShuLiang];bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构if (flag == -1){perror("绑定服务器地址结构错误");exit(1);}flag = listen(fd_FWQ, 128); //设置连接上限if (flag == -1){perror("设置连接上限错误");exit(1);}KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);flag1 = 0;start = clock();while (1){fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接if (fd_KHD == -1){perror("建立客户端连接错误");exit(1);}flag1++;printf("第%d次连接,客户端IP地址是:%s\n", flag1,inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息xian_cheng_xin_xi[fd_KHD].KHD_XinXi = DiZhi_JieGou_KHD;xian_cheng_xin_xi[fd_KHD].fd_KHD = fd_KHD;pthread_create(&XianCheng_ID, NULL, ChuLi_HanShu, (void *)&xian_cheng_xin_xi[fd_KHD]);pthread_detach(XianCheng_ID);close(fd_KHD);if (flag1 >= 2000){break;}}stop = clock();duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点printf("时间:%f\n", duration);return 0;
}
/*CeShi4_KHD.c多线程并发服务器处理客户端数据,并发回去
*/
/*测试1,多进程客户端测试服务器连接代码
*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>#define FWQ_IP "10.3.22.7" //服务器IPint main(int argc, char *argv[])
{int fd_FWQ; //服务器int flag;struct sockaddr_in FWQ_DiZhi; //服务器地址int ZiFuShu;                  //字符数char data[1024];              //数据pid_t JinCheng_ID;            //进程IDFWQ_DiZhi.sin_family = AF_INET;                         //IPv4FWQ_DiZhi.sin_port = htons(8080);                       //端口号8080flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IPif (flag == -1){perror("十进制IP转换网络IP错误");exit(1);}flag = 0;while (1){JinCheng_ID = fork(); //创建子进程if (JinCheng_ID < 0){perror("创建子进程错误");exit(1);}else if (JinCheng_ID == 0) //子进程{break;}flag++;if (flag >= 2000){break;}}if (JinCheng_ID == 0) //子进程{printf("这是%d子进程\n", getpid());fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}sleep(10);flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));if (flag == -1){perror("连接服务器错误");exit(1);}//close(fd_FWQ);}//while (1);sleep(180);close(fd_FWQ);if(JinCheng_ID>0){sleep(30);}return 0;
}
测试结果

TCP状态转换

  • CLOSED:初始状态。

  • LISTEN:服务器端的某个SOCKET处于监听状态,可以接受连接。

  • SYN_SENT:客户端已发送SYN报文。

  • SYN_RCVD:接收到SYN报文,服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。

  • ESTABLISHED:连接已经建立。

  • FIN_WAIT_1:等待对方的FIN报文,当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。

  • FIN_WAIT_2:等待对方的FIN报文,socket只能接收数据,不能发。主动关闭链接的一方,发出FIN收到ACK以后进入该状态,称之为半连接或半关闭状态。

  • TIME_WAIT:收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

  • CLOSING:双方都正在关闭SOCKET连接。

  • CLOSE_WAIT:在等待关闭。

  • LAST_ACK:被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。

主动发起连接请求端

  1. CLOSE

  2. 发送SYN

  3. 进入SYN_SENT状态

  4. 接收ACK、SYN

  5. 发送ACK

  6. 进入ESTABLISHED状态(数据通信态)

主动关闭连接请求端

  1. ESTABLISHED状态(数据通信态)

  2. 发送FIN

  3. 进入FIN_WAIT_1状态

  4. 接收ACK

  5. 进入FIN_WAIT_2状态(半关闭)

  6. 接收对端发送FIN

  7. 回发ACK

  8. 进入TIME_WAIT状态(只有主动关闭连接方,会经历该状态)

  9. 等2MSL时长

  10. CLOSE

被动接收连接请求端

  1. CLOSE

  2. 进入LISTEN状态

  3. 接收SYN

  4. 发送ACK、SYN

  5. 进入SYN_RCVD状态

  6. 接收ACK

  7. 进入ESTABLISHED状态(数据通信态)

被动关闭连接请求端

  1. ESTABLISHED状态(数据通信态)

  2. 接收FIN

  3. 发送ACK

  4. 进入CLOSE_WAIT状态(说明对端【主动关闭连接端】处于半关闭状态)

  5. 发送FIN

  6. 进入LAST_ACK状态

  7. 接收ACK

  8. CLOSE

2MSL时长

一般为40s,保证最后一个 ACK 能成功被对端接收。一定出现在主动关闭连接请求端

端口复用

测试代码5

使用端口复用,解除服务器主动断开连接后的2MSL等待时长。

/*服务器端代码*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>int main(int argc, char *argv[])
{int flag;int fd_FWQ;                          //服务器文件描述符int fd_KFD;                          //客户端文件描述符char data[1024];                     //读取的数据int ZiJieShu;                        //字节数struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构socklen_t KeHuDuan_DaXiao;           //客户端大小char KHD_IP[1024];                   //客户端IPchar FWQ_IP[1024];                   //服务器IPint opt = 1;bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用DiZhi_JieGou_FWQ.sin_family = AF_INET;                                               //IPv4DiZhi_JieGou_FWQ.sin_port = htons(8080);                                             //端口号8080DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);                                //获取系统中任意有效的IP地址flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器的地址结构if (flag == -1){perror("绑定服务器地址结构错误");exit(1);}printf("服务器IP:%s,端口号:%d\n",inet_ntop(AF_INET, &DiZhi_JieGou_FWQ.sin_addr.s_addr, FWQ_IP, sizeof(FWQ_IP)),ntohs(DiZhi_JieGou_FWQ.sin_port));flag = listen(fd_FWQ, 128); //设置连接服务器上限数if (flag == -1){perror("设置连接上限数错误");exit(1);}KeHuDuan_DaXiao = sizeof(DiZhi_JieGou_KHD);fd_KFD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KeHuDuan_DaXiao); //阻塞监听客户端连接if (fd_KFD == -1){perror("阻塞监听客户端连接错误");exit(1);}printf("客户端IP:%s,端口号:%d\n",inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, KHD_IP, sizeof(KHD_IP)), //网络转换成十进制本地IPntohs(DiZhi_JieGou_KHD.sin_port));                                             //网络转换成本地端口while (1){ZiJieShu = read(fd_KFD, data, sizeof(data));write(STDOUT_FILENO, data, ZiJieShu); //终端显示write(fd_KFD, data, ZiJieShu);}close(fd_KFD);close(fd_FWQ);return 0;
}
测试结果

半关闭

通信双方中,只有一端关闭通信。在关闭多个文件描述符应用的文件时,采用全关闭方法。

man 2 shutdown

参数sockfd

文件描述符。

参数how

SHUT_RD:关闭读端

SHUT_WR:关闭写端

SHUT_RDWR:关闭读写端

select多路IO转换

借助内核, select 来监听, 客户端连接、数据通信事件。

FD_ZERO

清空一个文件描述符集合。

man FD_ZERO

参数set

文件描述符集合。

FD_SET

将待监听的文件描述符,添加到监听集合中。

man FD_SET

参数fd

文件描述符。

参数set

文件描述符集合。

用法:

fd_set rset;
FD_SET(3, &rset);
FD_SET(5, &rset);
FD_SET(6, &rset);

FD_CLR

将一个文件描述符从监听集合中移除。

man FD_CLR

参数fd

文件描述符。

参数set

文件描述符集合。

FD_ISSET

判断一个文件描述符是否在监听集合中。

man FD_ISSET 

参数fd

文件描述符。

参数set

文件描述符集合。

返回值

1:在

0:不在

用法:

fd_set rset;
int flag;
flag=FD_ISSET(4,&rset);

select

man select

参数nfds

监听的所有文件描述符中,最大文件描述符+1。

参数readfds

读,文件描述符监听集合。传入、传出参数。

参数writefds

写,文件描述符监听集合。传入、传出参数。

参数exceptfds

异常,文件描述符监听集合。传入、传出参数。

参数timeout

大于0:设置监听超时时长。

NULL:阻塞监听。

0:非阻塞监听,轮询。

返回值

大于0:所有监听集合(3个)中, 满足对应事件的总数。

0:没有满足监听条件的文件描述符。

-1:errno

优缺点

        缺点:监听上限受文件描述符限制,最大1024。检测满足条件的fd,自己添加业务逻辑,提高了编码难度。

        优点:跨平台。win、linux、macOS、Unix、类Unix、mips。

测试代码6

服务器利用select进行监听客户端。

/*服务器端代码*/#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>int main(int argc, char *argv[])
{int fd_FWQ;                          //服务器文件描述符int fd_KHD;                          //客户端文件描述符int fd_Max;                          //最大的文件描述符struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构int opt = 1;int flag, i, leng;fd_set fd_all_JIHe;         //所有的文件描述符集合fd_set fd_Du_JiHe;          //读的文件描述符集合socklen_t JieGouTi_ChangDu; //结构体长度char data[1024];bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字if (fd_FWQ == -1){perror("创建服务器套接字错误");exit(1);}setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用DiZhi_JieGou_FWQ.sin_family = AF_INET;                                               //IPv4DiZhi_JieGou_FWQ.sin_port = htons(8080);                                             //端口号DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);                                //获取可用IP地址flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构if (flag == -1){perror("绑定服务器地址结构错误");exit(1);}flag = listen(fd_FWQ, 128); //设置连接服务器上限数if (flag == -1){perror("设置连接服务器上限数错误");exit(1);}FD_ZERO(&fd_all_JIHe);        //清空文件描述符集合FD_SET(fd_FWQ, &fd_all_JIHe); //添加服务器文件描述符,监听服务器fd_Max = fd_FWQ;while (1){fd_Du_JiHe = fd_all_JIHe;flag = select(fd_Max + 1, &fd_Du_JiHe, NULL, NULL, NULL); //监听if (flag == -1){perror("监听错误");exit(1);}if (FD_ISSET(fd_FWQ, &fd_Du_JiHe)){JieGouTi_ChangDu = sizeof(DiZhi_JieGou_KHD);fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &JieGouTi_ChangDu); //与客户端连接,不会阻塞if (fd_KHD == -1){perror("客户端建立错误");exit(1);}FD_SET(fd_KHD, &fd_all_JIHe); //添加连接好客户端的文件描述符,监听读事件if (fd_Max < fd_KHD) //最大的文件描述符小于连接上服务器的文件描述符{fd_Max = fd_KHD;}if (flag == 1) //说明只有一个文件描述符,是服务器的文件描述符{continue;}}for (i = fd_FWQ + 1; i <= fd_Max; i++){if (FD_ISSET(i, &fd_Du_JiHe)){leng = read(i, &data, sizeof(data));if (leng == -1){if (errno == EINTR){continue;}else{perror("读取文件错误");exit(1);}}else if (leng == 0) //客户端关闭{close(i);FD_CLR(i, &fd_all_JIHe); //移除客户端文件描述符}else //接收到数据{write(i, "你好客户端,我是服务器,接收到的数据是:", sizeof("你好客户端,我是服务器,接收到的数据是:"));write(i, data, leng);write(STDOUT_FILENO, "接收到的数据是:", sizeof("接收到的数据是:"));write(STDOUT_FILENO, data, leng);}}}}close(fd_FWQ);return 0;
}
测试结果

这篇关于【Linux网络编程】4.TCP协议、select多路IO转换的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/970000

相关文章

LangChain转换链:让数据处理更精准

1. 转换链的概念 在开发AI Agent(智能体)时,我们经常需要对输入数据进行预处理,这样可以更好地利用LLM。LangChain提供了一个强大的工具——转换链(TransformChain),它可以帮我们轻松实现这一任务。 转换链(TransformChain)主要是将 给定的数据 按照某个函数进行转换,再将 转换后的结果 输出给LLM。 所以转换链的核心是:根据业务逻辑编写合适的转换函

Linux 安装、配置Tomcat 的HTTPS

Linux 安装 、配置Tomcat的HTTPS 安装Tomcat 这里选择的是 tomcat 10.X ,需要Java 11及更高版本 Binary Distributions ->Core->选择 tar.gz包 下载、上传到内网服务器 /opt 目录tar -xzf 解压将解压的根目录改名为 tomat-10 并移动到 /opt 下, 形成个人习惯的路径 /opt/tomcat-10

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

【Altium】查找PCB上未连接的网络

【更多软件使用问题请点击亿道电子官方网站】 1、文档目标: PCB设计后期检查中找出没有连接的网络 应用场景:PCB设计后期,需要检查是否所有网络都已连接布线。虽然未连接的网络会有飞线显示,但是由于布线后期整板布线密度较高,虚连,断连的网络用肉眼难以轻易发现。用DRC检查也可以找出未连接的网络,如果PCB中DRC问题较多,查找起来就不是很方便。使用PCB Filter面板来达成目的相比DRC

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

16.Spring前世今生与Spring编程思想

1.1.课程目标 1、通过对本章内容的学习,可以掌握Spring的基本架构及各子模块之间的依赖关系。 2、 了解Spring的发展历史,启发思维。 3、 对 Spring形成一个整体的认识,为之后的深入学习做铺垫。 4、 通过对本章内容的学习,可以了解Spring版本升级的规律,从而应用到自己的系统升级版本命名。 5、Spring编程思想总结。 1.2.内容定位 Spring使用经验

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

Windows/macOS/Linux 安装 Redis 和 Redis Desktop Manager 可视化工具

本文所有安装都在macOS High Sierra 10.13.4进行,Windows安装相对容易些,Linux安装与macOS类似,文中会做区分讲解 1. Redis安装 1.下载Redis https://redis.io/download 把下载的源码更名为redis-4.0.9-source,我喜欢跟maven、Tomcat放在一起,就放到/Users/zhan/Documents

Toolbar+DrawerLayout使用详情结合网络各大神

最近也想搞下toolbar+drawerlayout的使用。结合网络上各大神的杰作,我把大部分的内容效果都完成了遍。现在记录下各个功能效果的实现以及一些细节注意点。 这图弹出两个菜单内容都是仿QQ界面的选项。左边一个是drawerlayout的弹窗。右边是toolbar的popup弹窗。 开始实现步骤详情: 1.创建toolbar布局跟drawerlayout布局 <?xml vers