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 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>