epoll相比于之前学的多进程/多线程的并发服务器,以及select和poll来说效率都高得多,并且连接数不受限制,采用类似树的结构。
特此分享一下本人在Linux下写的epoll简单的并发服务器端的代码。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<sys/epoll.h>
#define maxepoll 1000
//===============================================================
// 实现功能: 主函数,建立一个TCP并发服务器
//===============================================================
int main(int argc, char *argv[])
{
int sockfd = 0; // 套接字
int connfd = 0;
int err_log = 0;
struct sockaddr_in my_addr; // 服务器地址结构体
unsigned short port = 8888; // 监听端口
pthread_t thread_id;
printf("TCP Server Started at port %d!\n", port);
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
if (sockfd < 0)
{
perror("socket error");
exit(-1);
}
bzero(&my_addr, sizeof(my_addr)); // 初始化服务器地址
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("Binding server to port %d\n", port);
//设置端口复用
int flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag));
// 绑定
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if (err_log != 0)
{
perror("bind error");
close(sockfd);
exit(-1);
}
// 监听,套接字变被动
err_log = listen(sockfd, 4);
if (err_log != 0)
{
perror("listen error");
close(sockfd);
exit(-1);
}
//建立根结点
int epfd = epoll_create(maxepoll);
//初始化树
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd,&ev);
//创建下面获取改变的客户端存储
struct epoll_event all[maxepoll];
//客户端结构体
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
printf("Waiting client...\n");
while (1)
{
int num = epoll_wait(epfd, all, maxepoll, -1);
for (int i = 0; i < num; ++i)
{
int fd = all[i].data.fd;
if (fd == sockfd)//判断是否有新链接请求
{
int cfd = accept(fd, (struct sockaddr*)&client_addr, &client_len);
if (cfd == -1)
{
perror("accept error");
exit(1);
}
//把新的客户端结点挂到树上
ev.events = EPOLLIN;
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
//打印连接的客户端信息
char cli_ip[INET_ADDRSTRLEN] = "";
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("-----client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
}
else//如果客户端发来消息改变了epoll表
{
if (!all[i].events&EPOLLIN)//如果不是读操作直接略过当前的操作
{
continue;
}
char buf[1024] = { 0 };//清空缓冲区
int len = recv(fd, &buf, sizeof(buf), 0);
if (len == -1)
{
perror("recv error");
break;
}
else if(len==0)
{
printf("客户端fd:%d关闭,即将删除该结点\n",fd);
close(fd);//别忘了关闭文件描述符
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);//删除结点
}
else
{
printf("来自 fd:%d,内容:%s\n", fd, buf);
send(fd, buf, len, 0);//把收到的内容重新发给客户端
}
}
}
}
close(sockfd);
return 0;
}