Hello,各位看官好,小弟最近在研究关于多并发出现的问题,所以开始研究了select和epoll的问题,现在终于有了一些小感悟,下面想跟各位看官一起分享一下。
一、普通socket的整个流程
1.1 socket套接字的写法
1.2 网络收发数据包的流程
1.3 Socket的作用
二、Select的意义
三、Select的缺陷和epoll的改进
四、Epoll中LT和ET的区别
五、实例分析
一、普通socket的整个流程
1.1 socket套接字的写法
我们首先看一下普通socket的函数(为了方便,后续里面的参数我就不填写了)
Int sockfd = socket();
首先我们创建了一个套接字,那么这个套接字的作用究竟是什么呢?
1.2 网络收发数据包的流程
我们知道数据包是怎么接收的吗?我们按照接收为例,首先我们必须有一个网卡,网卡用来接收远端网卡接收过来的数据包,网卡收到数据包以后,会将其送给操作系统,最后操作系统会把数据发送给应用层,这就是整个流程。
1.3 socket的作用
当我们了解完这个以后,我们在来说一下socket的作用,首先,我们可以肯定的是我们的网卡接收到的肯定不是一个网卡发送过来的数据,那么这里我们我们必须要有一个分流,通过网络协议可知,我们的分流依据的就是Mac,IP,port这些,那么我们是怎么进行匹配的吗?我们就是把这些东西封包弄进socket中,通过socket来进行分流即可。
好的,那么现在我们就知道socket的作用了,socket在创建时,会自动创建一个发送缓冲区,接收缓冲区,以及等待队列这些东西。好的,那么这里有一个问题,就是我们的进程是怎么知道socket里面有数据,并且将其拿出来了呢?我们看下一章
二、select的意义
我们首先看下代码,代码当中一定会有一个函数accept函数和recv函数,那么最简单的方法就是采用CPU轮询的手法,一直不间断的轮询,当有数据的时候来处理就好了,但是这个问题就是对CPU的消耗太大,因为我们肯定还要做别的事情呀,所以我们最简单的方式就是找一个快递接收员,然后查找socket套接字,这就是select的用途。
我们这里提一句阻塞的问题,首先假如我们有三个进程A,B,C,如果A阻塞了,我们的意思就是A睡眠了,这时CPU会去执行B和C,A进程不占用CPU。
那么select的功能就是它自己去检测套接字,然后当有活跃的数据后,唤醒A进程,让他继续执行即可。
下面说一下select的流程:首先select监听所有的文件描述符(这里可能有很多),那么在查到有数据以后,会把所有的文件描述符全部送到应用层,然后重新遍历一遍,找到相应的文件描述符和数据,我们再进行操作。
三、select的缺点和epoll详解
但是这里面有许多问题,首先由于select轮询的所有文件描述符,这样对CPU的消耗太大了,所以select的上限是1024个,其次,我们需要把所有的文件描述符全部送到应用层并且重新遍历,但是事实上我们一个进程用到了只有1个或者几个描述符,这样太费时间。所以epoll就是针对这些问题来设计的。
我在说epoll之前简单说下poll,poll的使用原理和select基本是一样的,唯一的区别就是把select上限去掉了。所以这里不说他了。
我们来说下epoll。Epoll针对select有了两点改动。首先就是把它的添加描述符和解除阻塞分开了,分别对应函数epoll_ctl和epoll_wait,也就是说以后我们监听的时候,只需要监听epoll_ctl中的东西就可以了。这样对效率是一个大大的提高。
其次当发现该描述符有数据以后,我们也不必再应用层再来一遍了,这个epoll是怎么做的呢?他在里面设置了一个链表,当发现有活跃的fd以后,会将数据放到这个链表中,这样可以省时省力,解决问题。
四、Epoll中LT和ET的区别
Epoll中还有一点提升就是关于LT和ET的问题。LT就是水平触发,ET就是边缘触发。意思就是说前者只要有数据他就会一直触发,直到你把数据拿走,后者就触发一次,即从无到有的那一次。那么两者有什么区别呢?有的,前者必须费CPU资源(因为你不拿走一直报嘛),但是实现起来比较容易,不容易出错。后者的话不浪费CPU,但是实现起来不容易,为什么呢?因为后者必须要把数据全部取走,否则是不会触发的,所以使用ET模式一定要注意这一点。
五、实例分析
我这里准备了epoll中ET模式的实例。
int new_fd = 0;
int m_epollfd = epoll_create(1024);
if (m_epollfd < 0) {
std::cout<<"epoll create failed"<<std::endl;
}
// 注册epoll_wai
epoll_event ev;
struct sockaddr_in their_addr;
socklen_t len;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = fd;
int ret = epoll_ctl(m_epollfd, EPOLL_CTL_ADD, fd, &ev);
if (ret < 0) {
std::cout<<"ctl add failed"<<std::endl;
}
// addEvent(m_epollfd, m_sockfd, EPOLLIN);
while (true) {
int nfds = epoll_wait(m_epollfd, m_waitEvent, 1024, -1);
printf("nfds is %d\n",nfds);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int i=0;i<nfds;i++) {
std::cout<<"leave loop0000000000"<<std::endl;
if (m_waitEvent[i].data.fd == fd) {
std::cout<<"leave loopaaaaaaaaaaaa"<<std::endl;
memset(&their_addr, 0, sizeof(struct sockaddr_in));
new_fd = accept(fd, (struct sockaddr *)&their_addr, &len);
while (new_fd > 0) {
std::cout<<"leave loop11111111"<<std::endl;
setnonblocking(new_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = new_fd;
int ret = epoll_ctl(m_epollfd, EPOLL_CTL_ADD, new_fd, &ev);
if (ret < 0) {
// perror("add failed");
// exit(-1);
}
break;
}
// else {
// std::cout<<"leave loop333333333"<<std::endl;
// continue;
// }
if (new_fd < 0) {
std::cout<<"leave loop222222222"<<std::endl;
continue;
}
continue;
}
else {
handle_message(m_waitEvent[i].data.fd);
}
}
}