Socket-IO复用技术
发表于:2024-11-13 作者:千家信息网编辑
千家信息网最后更新 2024年11月13日,(上一篇地址)前面使用socket完成一个服务器对应多个客户端的小实验的时候,针对TCP连接,我们必须得创建新的进程来与新的客户端通信。那么,就意味着,1000个客户端就有有1000个server进程
千家信息网最后更新 2024年11月13日Socket-IO复用技术
(上一篇地址)前面使用socket完成一个服务器对应多个客户端的小实验的时候,针对TCP连接,我们必须得创建新的进程来与新的客户端通信。那么,就意味着,1000个客户端就有有1000个server进程,这显然是不实际的。如果,我们可以提前把要监听的文件描述符放到一个集合里,一旦其中一个发生事件(不管是连上,还是通信),就去处理。这样,会方便很多。所以,今天学习一下IO复用。
1 五个I/O模型
- 阻塞I/O
- 非阻塞I/O
- I/O复用(select和poll)
- 信号驱动I/O
- 异步I/O
阻塞IO
最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的。
非阻塞IO
IO复用
信号驱动IO
异步IO
2 I/O复用
如果一个或多个I/O条件满足(例如:输入已准备好被读,或者描述字可以承接更多输出的时候)我们就能够被通知到,这样的能力被称为I/O复用,是由函数select和poll支持的。
I/O复用网络应用场合
- 当客户处理多个描述字
- 一个客户同时处理多个套接口
- 如果一个tcp服务器既要处理监听套接口,又要处理连接套接口
- 如果一个服务器既要处理TCP,又要处理UDP
select
/* According to POSIX.1-2001 */ #include /* According to earlier standards */ #include #include #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 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);//清空描述字集合
- 作用:函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程
提供了即时响应多个套接的读写事件 - 参数:
- nfds:集合中最大的文件描述符 + 1 (指定被测试的描述字个数,它的值是要被测试的最大描述字加1,描述字0、1、2…….一直到nfds均被测试)
- readfds:要检查读事件的容器
- writefds:要检查写事件的容器
- timeout:超时时间
- 返回值:返回触发套接字的个数
中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件所需的描述字
如果我们对某个条件不感兴趣,这三个参数中相应的参数就可以设为空指针
timeout参数
时间的结构体如下:
struct timeval( long tv_sec; //秒 long tv_usec;//微秒 );
timeout参数有三种可能
- 永远等待下去:仅在有一个描述字准备好I/O时才返回,为此,我们将timeout设置为空指针
- 等待固定时间:在有一个描述字准备好I/O是返回,但不超过由timeout参数所指timeval结构中指定的秒数和微秒数
根本不等待:检查描述字后立即返回,这称为轮询。定时器的值必须为0
fd_set参数
select使用描述字集,它一般是一个整数数组,每个数中的每一位对应一个描述字。
使用流程
使用select完成之前socket的测试,流程如下:
客户端代码不变。#include < sys/types.h> #include < sys/socket.h>#include < netinet/in.h> //sockaddr_in#include < stdio.h>#include < string.h>//TCPint main(){ int fd; int ret; int addrLen; char acbuf[20] = ""; struct sockaddr_in serAddr = {0}; struct sockaddr_in myAddr = {0}; //1.socket(); fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //2.连接connect() 服务器的地址 serAddr.sin_family = AF_INET; serAddr.sin_port = htons(1234); serAddr.sin_addr.s_addr = inet_addr("192.168.159.5"); ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in)); if(ret == -1) { perror("connect"); return -1; } //获取自己的地址 addrLen = sizeof(struct sockaddr_in); ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen); if(ret == -1) { perror("getsockname"); return -1; } printf("client---ip: %s , port: %d\n",\ inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port)); //3.通信 while(1) { printf("send: "); fflush(stdout); scanf("%s",acbuf); if(strcmp(acbuf,"exit") == 0) { break; } write(fd,acbuf,strlen(acbuf)); } //4.close() close(fd); return 0;}
服务器端:
select.c#include < sys/types.h> #include < sys/socket.h> #include < netinet/in.h> //sockaddr_in #include < stdio.h> #include < string.h> #include < signal.h> #include < sys/select.h> #include < unistd.h> #include < sys/time.h> //TCP int main() { int fd; int clientfd; int ret; pid_t pid; int i; int maxfd; //当前最大套接字 int nEvent; fd_set set = {0}; //监听集合 fd_set oldset = {0}; //存放所有要监听的文件描述符 struct timeval time = {0}; int reuse = 0; char acbuf[20] = ""; char client_addr[100] = ""; struct sockaddr_in addr = {0}; //自己的地址 struct sockaddr_in clientAddr = {0}; //连上的客户端的地址 int addrLen = sizeof(struct sockaddr_in); signal(SIGCHLD,SIG_IGN); //1.socket() fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use . //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { perror("setsockopet error\n"); return -1; } //2.bind() addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("192.168.159.5"); ret = bind(fd,(struct sockaddr *)&addr,addrLen); if(ret == -1) { perror("bind"); return -1; } //3.listen() ret = listen(fd,10); if(ret == -1) { perror("listen"); return -1; } //创建监听集合 FD_ZERO(&oldset); FD_SET(fd,&oldset); //maxfdp1:当前等待的最大套接字。比如:当前fd的值=3,则最大的套接字就是3 //所以每当有客户端连接进来,就比较一下文件描述符 maxfd = fd; //select //select之前,set放的是所有要监听的文件描述符;{3,4,5} //select之后,set只剩下有发生事件的文件描述符。{3} while(1) { set = oldset; printf("before accept.\n"); time.tv_sec = 5; nEvent = select(maxfd + 1,&set,NULL,NULL,&time); //返回文件描述符的个数(即事件的个数) printf("after accept.%d\n",nEvent); if(nEvent == -1) { perror("select"); return -1; } else if(nEvent == 0) //超时 { printf("time out"); return 1; } else { //有事件发生 //判断是否是客户端产生的事件 for(i = 0 ; i <= maxfd ; i++) { if(FD_ISSET(i,&set)) { if(i == fd) { clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen); FD_SET(clientfd,&oldset); printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port)); if(clientfd > maxfd) { maxfd = clientfd; } } else { memset(acbuf,0,20); if(read(i,acbuf,20) == 0) //客户端退出 { close(i); //还要从集合里删除 FD_CLR(i,&oldset); } else printf("receive: %s\n",acbuf); } } } } } return 0; }
epoll
epoll用到的函数有以下几个:
#include int epoll_create(int size);//创建epoll int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作函数 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
事件集合的结构体:
(这里 ,还要注意,epoll的超时参数是int,单位是us)
使用流程
#include #include #include //sockaddr_in #include #include #include #include //epoll //epoll_wait() epoll_creat() epoll_ctl() //TCP int main() { int fd; int clientfd; int ret; pid_t pid; int i; int epfd; int nEvent; struct epoll_event event = {0}; struct epoll_event rtl_events[20] = {0}; //事件结果集 int reuse = 0; char acbuf[20] = ""; char client_addr[100] = ""; struct sockaddr_in addr = {0}; //自己的地址 struct sockaddr_in clientAddr = {0}; //连上的客户端的地址 int addrLen = sizeof(struct sockaddr_in); signal(SIGCHLD,SIG_IGN); //1.socket() fd = socket(PF_INET,SOCK_STREAM,0); if(fd == -1) { perror("socket"); return -1; } //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use . //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { perror("setsockopet error\n"); return -1; } //2.bind() addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("192.168.159.5"); ret = bind(fd,(struct sockaddr *)&addr,addrLen); if(ret == -1) { perror("bind"); return -1; } //3.listen() ret = listen(fd,10); if(ret == -1) { perror("listen"); return -1; } epfd = epoll_create(1000); //同时监听的文件描述符 event.data.fd = fd; event.events = EPOLLIN; //读 epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &event); while(1) { // nEvent = epoll_wait(epfd,rtl_events,20,-1); //-1:阻塞 0:非阻塞 nEvent = epoll_wait(epfd,rtl_events,20,5000); if(nEvent == -1) { perror("epoll_wait"); return -1; } else if(nEvent == 0) { printf("time out."); } else { //有事件发生,立即处理 for(i = 0; i < nEvent;i++) { //如果是 服务器fd if( rtl_events[i].data.fd == fd ) { clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen); //添加 event.data.fd = clientfd; event.events = EPOLLIN; //读 epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event); printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port)); } else { //否则 客户端fd memset(acbuf,0,20); ret = read(rtl_events[i].data.fd,acbuf,20); printf("%d\n",ret); if( ret == 0) //客户端退出 { close(rtl_events[i].data.fd); //从集合里删除 epoll_ctl(epfd,EPOLL_CTL_DEL,rtl_events[i].data.fd,NULL); } else printf("receive: %s\n",acbuf); } } } } return 0; }
运行结果如前,正常收发。
事件
客户
参数
套接字
客户端
文件
处理
阻塞
地址
多个
监听
复用
服务器
服务
最大
测试
个数
函数
时间
进程
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
永康软件开发部
软件开发中属于设计阶段的是
网络安全教育研究性课题
网络技术定位
校园网络安全责任书范文
网络安全审查原创
淘宝的网络安全员
数据库实体必须包含属性吗
和家亲小黄人摄像头无法连接服务器
服务器如何备份数据
网络安全路由器配置
兰州市青少年网络安全知识大赛
路由器怎么做服务器配置
查询表数据库更新的时间
软件开发与软件设计相同吗
swith哪个服务器最便宜
我的世界服务器怎么召唤him
苏州的程刚aap软件开发
网络技术人员能力是什么
错过网络安全知识竞赛时间
芜湖新悦网络技术
致信服务器配置获取数据失败
远程遥控车软件开发
5g需要的网络技术
企业密信怎么进入服务器
软件开发李凯旋
京东抢券软件开发
数据库表fid是什么意思
信息系统与数据库技术的发展
软件开发有限公司赚钱吗