epoll实现简单socket通信
epoll是常用的socket通信方式,相比于select和poll来说,效率提升了不止一点半点
其一:select中socket描述符(文件描述符)集的数据结构为数组,poll的文件描述符集数据结构为链表,无论数组还是链表,它们都是线性结构,当遍历时,也只能线性遍历;而epoll文件描述符集采用的红黑树(平衡二叉树的一个变体)的数据结构,红黑树的遍历则相比于线性遍历,效率要高很多。
其二:select和poll对文件描述符集先要拷贝到内核区,内核修改后再拷贝至用户区,要经过2次拷贝;而epoll底层用的是共享内存的方式,即内核区与用户区共享一片内存,无需拷贝,也就大大提高了效率。
下面是epoll相关函数的介绍:
#include <sys/epoll.h>
// 创建一棵红黑树
int epoll_create(int size);
参数:
size: 没意义, 随便写个数就行(早期epoll数据结构为hash表,需要指定表长,但后面改为红黑树后其实树节点个数就没有什么意义了)
返回值;
>0: 文件描述符, 操作epoll树的根节点
Epoll检测的事件:
- EPOLLIN
- EPOLOUT
- EPOLLERR
// 对epoll树进行管理: 添加节点, 删除节点, 修改已有的节点属性
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
- epfd: epoll_create的返回值, 通过这个值就可以找到红黑树
- op: 要进行什么样的操作
EPOLL_CTL_ADD: 注册新节点, 添加到红黑树上
EPOLL_CTL_MOD: 修改检测的文件描述符的属性
EPOLL_CTL_DEL: 从红黑树上删除节点
- fd: 要检测的文件描述符的值
- event: 检测文件描述符的什么事件
// 检测函数
struct epoll_event events[1000];
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数:
- epfd: epoll_create的返回值(这棵红黑树的根节点), 通过这个值就可以找到红黑树
- events: 传出参数, 保存了发生变化的文件描述符的信息(数组形式)
- maxevents: 第二个参数结构体数组的大小
- timeout: 阻塞时间
- 0: 不阻塞
- -1: 一直阻塞, 知道检测的fd有状态变化, 解除阻塞
- >0: 阻塞的时长(毫秒)
返回值:
- 成功: 有多少个文件描述符状态发生了变化 > 0
- 失败: -1
下面直接上代码吧
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/epoll.h>
int main()
{
// 1.创建套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
exit(0);
}
// 2. 绑定 ip, port
struct sockaddr_in addr;
addr.sin_port = htons(1234);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
// 3. 监听
ret = listen(lfd, 100);
if(ret == -1)
{
perror("listen");
exit(0);
}
// 4. 创建epoll树
int epfd = epoll_create(1000);//1000并没有什么意义
if(epfd == -1)
{
perror("epoll_create");
exit(-1);
}
//5、将用于监听的lfd挂的epoll树上(红黑树)
struct epoll_event ev;//这个结构体记录了检测什么文件描述符的什么事件
ev.events = EPOLLIN;
ev.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);//ev里面记录了检测lfd的什么事件
// 循环检测 委托内核去处理
struct epoll_event events[1024];//当内核检测到事件到来时会将事件写到这个结构体数组里
while(1)
{
int num = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);//最后一个参数表示阻塞
//遍历事件,并进行处理
for(int i=0; i<num; i++)
{
if(events[i].data.fd == lfd)//有连接请求到来
{
struct sockaddr_in client_sock;
int len = sizeof(client_sock);
int connfd = accept(lfd, (struct sockaddr *)&client_sock, &len);
if(connfd == -1)
{
perror("accept");
exit(-1);
}
char ip[32]={0};
inet_ntop(AF_INET, &(client_sock.sin_addr.s_addr), ip, sizeof(ip));
printf("a new client connected! ip:%s port:%d\n", ip, ntohs(client_sock.sin_port));
//将用于通信的文件描述符挂到epoll树上
ev.data.fd = connfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else//通信
{
//通信也有可能是写事件
if(events[i].events & EPOLLOUT)
{
//这里先忽略写事件
continue;
}
char buf[1024]={0};
struct sockaddr_in peerAddr;//用于保存当前通信的客户端的信息
int peerLen;
char ip[32]={0};
inet_ntop(AF_INET, &(peerAddr.sin_addr), ip, sizeof(ip));
getpeername(events[i].data.fd, (struct sockaddr *)&peerAddr, &peerLen); //获取对应客户端相应信息
int count = read(events[i].data.fd, buf, sizeof(buf));
if(count == 0)//客户端关闭了连接
{
printf("%s(%d) 客户端关闭了连接。。。。\n",ip, ntohs(peerAddr.sin_port));
//将对应的文件描述符从epoll树上取下
close(events->data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, events->data.fd, NULL);
}
else
{
if(count == -1)
{
perror("read");
exit(-1);
}
else
{
//正常通信
printf("%s:%d say: %s\n",ip , ntohs(peerAddr.sin_port), buf);
//printf("client say:%s\n",buf);
write(events[i].data.fd, buf, strlen(buf)+1);
}
}
}
}
}
close(lfd);
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
printf("create socket success!\n");
//向服务器(特定的IP和端口)发起请求
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))<0){
printf("connect failed!!!\n");
return 0;
}
printf("connect success!!!\n");
//读取服务器传回的数据
char buffer[100];
//recv(sock, buffer, sizeof(buffer),0);
//printf("Message form server: %s\n", buffer);
while(1){
memset(buffer,0,sizeof(buffer));
fgets(buffer,sizeof(buffer),stdin);//fgets会读取换行符\n
buffer[strlen(buffer)-1]='\0';//去掉换行符
send(sock, buffer, strlen(buffer)+1,0);
//sleep(1);
recv(sock, buffer, sizeof(buffer),0);
printf("Message form server: %s\n", buffer);
}
//关闭套接字
close(sock);
return 0;
}
版权声明:本文为lj779323436原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。