概述
多线程:
1. 每个请求对应一个线程,但是多线程需要进行CPU上下文切换。
2. 而进行上下文切换需要处理一些句柄,这些操作时非常繁琐的,所以多线程不是最好的解决方案。
单线程:
1.问题:如果服务器正在处理A的请求,此时B发送一个请求,B的请求会被丢弃吗?不会
处理IO的设备并不是CPU而是专门的DMA控制器。因而单线程处理请求并不会造成数据的丢失。
每一个网络连接都对应一个网络描述符:fd
while(true) {
for(fdx in fdA~fdE) {
if(fdx 有数据) {
读取fdx,处理;
}
}
}
select
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
listen (sockfd, 5);
这部分主要做了两件事:
- 创建了socket客户端
- 创建了5个文件描述符,并把这5个文件描述符放入到了数组中。
for (i=0;i<5;i++)
{
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
if(fds[i] > max)
max = fds[i];
}
while(1){
FD_ZERO(&rset);
for (i = 0; i< 5; i++ ) {
FD_SET(fds[i],&rset);
}
puts("round again");
select(max+1, &rset, NULL, NULL, NULL);
for(i=0;i<5;i++) {
if (FD_ISSET(fds[i], &rset)){
memset(buffer,0,MAXBUF);
read(fds[i], buffer, MAXBUF);
puts(buffer);
}
}
}
select方法的参数:
- 读文件描述符集合、写文件描述符集合、异常描述符集合、超时时间,其中第二个参数&rset是一个bitmap用于表征哪些文件描述符是被启用或者说被监听的,bitmap大小为1024位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5hv70ycR-1602557432960)(media/16025047279295/16025151457751.jpg)]
- 将rset从用户态拷贝到内核态由内核态直接判断文件描述符是否有数据的操作,这样效率更高
- 以前判断时需要由用户态切换到内核态所以效率低
分析select函数的执行流程:
1,select是一个阻塞函数,当没有数据时,会一直阻塞在select那一行。
- 当有数据时会将rset中对应的那一个位置为1
- select函数返回,不在阻塞
- 遍历文件描述符数组,判断哪个fd被置位了
- 读取数据,然后处理
select函数的缺点
1. bitmap默认大小为1024, 虽然可以调整但还是有限度。
2. rset每次循环都必须重新置位为0, 不可重复使用
3. 尽管rset从用户态拷贝到内核态,由内核态判断是否有数据,但是还是有拷贝的开销。
4. 当有数据时select返回,但是select函数并不知道哪个文件描述符有数据了,后面还需要再次对文件描述符进行遍历。效率比较低。
poll
struct pollfd {
int fd;//文件描述符
short events; //在意的事件是什么,如果在于读就是POLLIN,如果在意于写就是POLLOUT
short revents;//对events事件的反馈,开始时为0,当有数据可读时就置为POLLIN,类似于上面rset。
};
for (i=0;i<5;i++)
{
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
pollfds[i].fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
pollfds[i].events = POLLIN;
}
sleep(1);
while(1){
puts("round again");
poll(pollfds, 5, 50000);
poll的参数:
1. 自定义的结构体数组
2. 数据的长度
3. 超时时间
for(i=0;i<5;i++) {
if (pollfds[i].revents & POLLIN){
pollfds[i].revents = 0;
memset(buffer,0,MAXBUF);
read(pollfds[i].fd, buffer, MAXBUF);
puts(buffer);
}
}
}
解决问题:
1. 解决了bitmap大小的限制
2. 解决了rset不可重用的情况
epoll
struct epoll_event events[5];
int epfd = epoll_create(10);
...
...
for (i=0;i<5;i++)
{
static struct epoll_event ev;
memset(&client, 0, sizeof (client));
addrlen = sizeof(client);
ev.data.fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}
- epoll_create(10)创建一个白板
- epoll_ctl()在白板上写字,写入五个fd_event
- epoll_event()为包含fd和event的结构体,与poll类似
- 最后得到epfd传到了下面的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBwPjgGW-1602557432965)(media/16025047279295/16025571282334.jpg)]
while(1){
puts("round again");
nfds = epoll_wait(epfd, events, 5, 10000);
for(i=0;i<nfds;i++) {
memset(buffer,0,MAXBUF);
read(events[i].data.fd, buffer, MAXBUF);
puts(buffer);
}
}
epoll时非阻塞的!!!
epoll的执行流程:
- 当有数据的时候,会把相应的文件描述符“置位”,但是epoll没有revent标志位,所以并不是真正的置位,这时候会把有数据的文件描述符放到队首。
- epoll会返回有数据的文件描述符的个数
- 根据返回的个数,读取前N个文件描述符即可
- 读取,处理