I/O复用
I/O复方法:select poll epoll
作用:
- 可以同时监听多个文件描述符,
使服务器在不引入多进程的情况下同时处理多个文件描述符。
网络程序需要使用 I/O 复用技术:
◼ TCP 服务器同时要处理监听套接字和连接套接字。
◼ 服务器要同时处理 TCP 请求和 UDP 请求。
◼ 程序要同时处理多个套接字。
◼ 客户端程序要同时处理用户输入和网络连接。
◼ 服务器要同时监听多个端口。
I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当
多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以配合使用多线程或多进程等编程方法。
socket
socket 有读事件产生:
socket 通信的对方关闭连接。
监听socket 上有新的链接请求。
socket 上有未处理的错误。
select 系统调用
#include<sys/select.h>
select 返回值为就绪的文件描述符的数目。
如果在超时时间内没有任何文件描述符就绪,select 将返回 0。
select 失败是返回-1。
n>0,有n个文件描述符就绪。
处理三种事件:读事件,写事件,和异常事件。
集合 fd_set 将描述符添加到集合中,总大小默认为 1024 个位。
int FD_ISSET(int fd, fd_set *fdset); // 测试 fdset 的位 fd 是否被设置。
适用于描述符数目不多的场景。
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<sys/select.h>
5 #include<sys/time.h>
6
7 int main()
8 {
9 int fd=0;// stdin 0; stdout 1; stderr 2 将键盘描述符的值赋给fd
10
11 fd_set fdset;//定义集合,用来收集描述符,被selet检测
12
13 while(1)//循环检测
14 {
15 struct timeval tv={5,0};//设置超时时间 5秒
16 FD_ZERO(&fdset); //清空整个集合中的每个位 为0
17 FD_SET(fd,&fdset);//将描述符添加到集合 只有一个描述符 如果有多个描述符 循环
18
19 int n=select(fd+1,&fdset,NULL,NULL,&tv);//fd+1 描述符最大值+1 只用检测有数据的地方
20 if(n==-1)
21 {
22 printf("select err\n");
23 break;
24 }
25 else if(n==0)
26 {
27 printf("time out\n");
28 }
29 else
30 {
31 if(FD_ISSET(fd,&fdset))//检测描述符上是否有数据
32 {
33 char buff[128]={0};
34 read(fd,buff,127);
35 printf("read:%s\n",buff);
36 }
37 }
38 }
39
40 }
TCP
TCP服务器
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<assert.h>
9 #include<sys/select.h>
10 #include<sys/time.h>
11
12 #define MAX 10
13 int socket_init();
14
15 void fds_init(int fds[])//清空描述符 全置为-1
16 {
17 if(fds==NULL)
18 {
19 return;
20 }
21 for(int i=0;i<MAX;i++)
22 {
23 fds[i]=-1;
24 }
25 }
26 void fds_add(int fd,int fds[])//向空闲的集合中添加描述符
27 {
28 if(fds==NULL)
29 {
30 return;
31 }
32 for(int i=0;i<MAX;i++)
33 {
34 if(fds[i]==-1)
35 {
36 fds[i]=fd;
37 break;
38 }
39 }
40 }
41 void fds_del(int fd,int fds[])//将描述符从集合中移除
42 {
43 if(fds==NULL)
44 {
45 return;
46 }
47 for(int i=0;i<MAX;i++)
48 {
49 if(fds[i]==fd)
50 {
51 fds[i]=-1;
52 break;
53 }
54 }
55 }
56 int main()
57 {
58 int sockfd=socket_init();
59 assert(sockfd!=-1);
60 int fds[MAX];//将描述符存放到数组中
61
62 fds_init(fds);//初始化数组值都为-1,代表数组是空的
63
64 fds_add(sockfd,fds);//将监听套介子添加到数组
65
66 fd_set fdset;//集合 ->select
67
68 while(1)
69 {
70 FD_ZERO(&fdset);//清空集合
71 int maxfd=-1;//记录描述符最大值
72
73 for(int i=0;i<MAX;i++)
74 {
75 if(fds[i]==-1)
76 {
77 continue;
78 }
79 FD_SET(fds[i],&fdset);//把有效描述符添加到集合
80 if(maxfd<fds[i])
81 {
82 maxfd=fds[i];
83 }
84 }
85 struct timeval tv={5,0};//超时时间 5秒
86 int n=select(maxfd+1,&fdset,NULL,NULL,&tv);
87 if(n==-1)
88 {
89 continue;
90 }
91 else if(n==0)
92 {
93 printf("time out\n");
94 continue;
95 }
96 else
97 {
98 for(int i=0;i<MAX;i++)//遍历所有描述符找到就绪的
99 {
100 if(fds[i]==-1)
101 {
102 continue;
103 }
104 if(FD_ISSET(fds[i],&fdset))
105 {
106 if(fds[i]==sockfd) //监听套接子 调用accept
107 {
108 struct sockaddr_in caddr;
109 int len=sizeof(caddr);
110
111 int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
112 if(c<0)
113 {
114 continue;
115 }
116 printf("accept c=%d\n",c);
117 fds_add(c,fds);
118 }
119 else //连接套接子 调用recv
120 {
121 char buff[128]={0};
122 int num=recv(fds[i],buff,127,0);
123 if(num<=0)
124 {
125 close(fds[i]);
126 fds_del(fds[i],fds);//从数组中删除不用的描述符
127 printf("cilent close\n");
128 continue;
129 }
130 printf("read:%s\n",buff);
131 send(fds[i],"ok",2,0);
132 }
133 }
134 }
135 }
136 }
137 }
138
139 int socket_init()
140 {
141 int sockfd=socket(AF_INET,SOCK_STREAM,0);
142 if(sockfd==-1)
143 {
144 return -1;
145 }
146
147 struct sockaddr_in saddr;
148 memset(&saddr,0,sizeof(saddr));
149 saddr.sin_family=AF_INET;
150 saddr.sin_port=htons(6000);
151 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
152
153 int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
154 if(res==-1)
155 {
156 return -1;
157 }
158
159 res=listen(sockfd,5);
160 if(res==-1)
161 {
162 return -1;
163 }
164 return sockfd;
165 }
TCP客户端
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5 #include<assert.h>
6 #include<sys/socket.h>
7 #include<netinet/in.h>
8 #include<arpa/inet.h>
9
10 int main()
11 {
12 int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
13 assert(sockfd!=-1);
14
15 //bind()//可以绑定,但是一般不绑定
16
17 struct sockaddr_in saddr;
18 memset(&saddr,0,sizeof(saddr));
19 saddr.sin_family=AF_INET;
20 saddr.sin_port=htons(6000);
21 saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
22
23 int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//三次握手
24 assert(res!=-1);
25 while(1)
26 {
27 char buff[128]={0};
28 printf("input:\n");
29 fgets(buff,128,stdin);
30
31 if(strncmp(buff,"end",3)==0)
32 {
33 break;
34 }
35 send(sockfd,buff,strlen(buff),0);
36
37 memset(buff,0,128);
38 recv(sockfd,buff,127,0);
39 printf("buff=%s\n",buff);
40 }
41 close(sockfd);
42 }
~
- 对方关闭描述符
- 对方发数据
- 对方链接
select 都会返回。