Linux—TCP

xiaoxiao2021-02-28  82

1、前言

之前我有写过 利用多路转接的select的TCP_server,但当时我们提到了很多关于select的缺点:

1、select可监听的文件描述符有上限制; 2、因为select参数是输入输出型的,所以每次重新设置select时,都需遍历式设置,对性能有一定的影响 3、用户增多时,多次重复遍历和频繁内核与进程数据拷贝(多次的返回) 4、需要自己维护一个数组/链表,对文件描述符的管理,实现也比较复杂 5、每次多需要重新设置select—将fd设置从用户拷贝到内核

基于这么多的缺和实现的复杂,所以我们基本不会使用select来实现,而今天的主题epoll对这些问题都进行解决

2、epoll

epoll man手册上说linux2.6后性能最好的!!!

为什么性能好呢? 我们来看张图: 再来学习epoll的函数: epoll有三个函数: 1、int epoll_create(int size); //创建epoll(创建红黑树)

参数size :对内核的提醒(建议)空间大小,man手册解释size可以被忽略返回值: 返回一个epoll句柄,用于对红黑树的操作,在使用完epoll之后,因使用close()关闭;

2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //设置红黑树

参数:epfd , 红黑树的句柄参数:op,操作方式,有以下三种: EPOLL_CTL_ADD :添加事件(在红黑树上添加节点) EPOLL_CTL_MOD:更改事件(改变红黑树指定节点事件发生条件) EPOLL_CTL_DEL:删除事件(删除节点)返回值: 成功返回 0、失败返回-1;

3、int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);//等待事件就绪函数

typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* 事件,用专门的宏来设置*/ epoll_data_t data; /* User data variable */ }; 参数 epfd epoll句柄!!!参数 events,是用户创建的结构体数据组,是一个输出型参数,当内核的就绪时间队列中有事件时,将事件带回。maxevents events最多可以带回多少事件,0 < maxevents < size(前面创建epoll是的大小)参数:timeout 超时时间,-1: 阻塞,0: 不阻塞返回值,就绪事件的个数函数功能:epoll_wait如果监测到事件就将所有就绪的事件传送到第二个参数struct epoll_event 结构体数组中,每次调用epoll_wait返回链表是从内核返回给用户空间的,每次从内核传送到用户空间的描述符并不是很多,epoll的时间复杂度是O(1).

讲完所有的接口是不是对EPOLL为什么效率高的原因,有了一定的了解;

3、总结epoll的高效性:

1、内核创建红黑树 2、不需要每次都对每个事件重新设置(比较于select) 3、 操作系统在检测文件描述符采用回调函数 4、用户查找就绪文件符的复杂度O(1),—利用队列遍历 5、用户与内核采用内存映射,看到同意内存,不需要拷贝

4、利用epoll编写TCP_server

下面的代码可以实现 server—client的交互式通信 while(1) { int n = epoll_wait(epfd, rev, 64, -1);//等待就绪事件 switch(n){ // case 0:{ printf("time out\n"); continue; } case -1:{ perror("epoll_wait"); return 5; } default:{//有事件就绪 int i = 0; for(i=0; i<n; ++i)//遍历数组,时间复杂度O(n)=O(1) { if(rev[i].data.fd == listen_sock) { struct sockaddr_in client; size_t len = sizeof(client); int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len); if(new_sock < 0) { perror("accept"); continue; } printf("get a new sock:%s, %d\n",\ inet_ntoa(client.sin_addr), ntohs(client.sin_port)); ev.events = EPOLLIN;//添加新的套接字读事件 ev.data.fd = new_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev); } else if(rev[i].events & EPOLLIN){//读事件就绪 char buf[1024]; ssize_t s = read(rev[i].data.fd, buf, sizeof(buf)-1); if(s < 0){//当读取错误或者对端关闭连接时,删除该套接字 perror("read"); close(rev[i].data.fd); ev.events = EPOLLIN; ev.data.fd = rev[i].data.fd; epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev); continue; } else if(s == 0){ close(rev[i].data.fd); ev.events = EPOLLIN; ev.data.fd = rev[i].data.fd; epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev); printf("client quit\n"); continue; } buf[s] = '\0'; printf("clinet say#%s\n",buf); //读取数据完成后,更改套接字读事件为写事件 ev.events = EPOLLOUT; ev.data.fd = rev[i].data.fd; epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev); } else{//写事件就绪 char *str = "wlcome to sock "; write(rev[i].data.fd, str, strlen(str)); ev.events = EPOLLIN;//数据回馈完成后,更改套接字事件为读 ev.data.fd = rev[i].data.fd; epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev); } } } }
转载请注明原文地址: https://www.6miu.com/read-74049.html

最新回复(0)