服务器升级–epoll模型
一、epoll的设计思路
措施一:功能分离
select低效的原因之一是将“维护等待队列”和“阻塞进程”两个步骤合二为一。如下图所示,每次调用select都需要这两步操作,然而大多数应用场景中,需要监视的socket相对固定,并不需要每次都修改。epoll将这两个操作分开,先用epoll_ctl维护等待队列,再调用epoll_wait阻塞进程(解耦)。显而易见的,效率就能得到提升。
相比select,epoll拆分了功能,为方便理解后续的内容,我们先复习下epoll的用法。如下的代码中,先用epoll_create创建一个epoll对象epfd,再通过epoll_ctl将需要监视的socket添加到epfd中,最后调用epoll_wait等待数据。
措施二: 就绪列表
select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。如下图所示,计算机共有三个socket,收到数据的sock2和sock3被rdlist(就绪列表)所引用。当进程被唤醒后,只要获取rdlist的内容,就能够知道哪些socket收到数据。
什么时候select优于epoll? 一般认为如果在并发量低,socket都比较活跃的情况下,select效率更高,也就是说活跃socket数目与监控的总的socket数目之比越大,select效率越高,因为select反正都会遍历所有的socket,如果比例大,就没有白白遍历。加之于select本身实现比较简单,导致总体现象比epoll好)
一、epoll服务器的设计
1、epoll对象创建与监听
/* 开始epoll流程 */
int epollfd;
struct epoll_event events[EPOLLEVENTS];// 并发100个事件
//创建一个描述符
epollfd = epoll_create(FDSIZE);
//添加监听描述符事件
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sock_fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sock_fd, &ev);
2、epoll_wait等待数据
//获取已经准备好的描述符事件
ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
int i;
int fd;
//进行选好遍历
for (i = 0; i < ret; i++)
{
fd = events[i].data.fd;
//根据描述符的类型和事件类型进行处理
if ((fd == sock_fd) && (events[i].events & EPOLLIN))
{
handle_accpet(epollfd, sock_fd);
}
else if (events[i].events & EPOLLIN)
{
memset(recv_buf, 0, sizeof(recv_buf));
do_read(epollfd, fd, recv_buf);
}
}
服务器处理多客户端连接处理函数
void handle_accpet(int epollfd, int listenfd)
{
int client_num = 0;
socklen_t addr_len = sizeof(struct sockaddr);
struct sockaddr_in client_addr;
int new_fd = accept(listenfd, (struct sockaddr*)&client_addr, &addr_len);
if (-1 == new_fd)
{
fprintf(stderr, "accept error:%s\n\a", strerror(errno));
close(listenfd);
exit(1);
}
else
{
client_num++;
fprintf(stderr, "Server get connetion form client%d: %s\n", client_num, inet_ntoa(client_addr.sin_addr));
//添加一个客户描述符和事件
add_event(epollfd, new_fd, EPOLLIN);
}
}
测试结果如下图
更新通信数据包,设计总包
//定义数据包头
typedef struct head
{
int funcCode; // 功能码
int dataLength; //描述数据长度
int operCode; // 操作码
}HEAD_T;
typedef struct user //定义用户数据包体
{
char UserName[32];
char PassWord[32];
int sockfd; //客户端连接套接字
}USER_T;
typedef struct ret //定义服务器应答包体
{
int flag; //定义用户登录返回状态
char name[32]; //返回用户名
}RET_T;
//定长包头+定长数据包
typedef struct data
{
HEAD_T head; //包头字段
char data[1024-sizeof(HEAD_T)];
}DATA_T;
服务器读事件解析数据包处理函数
void do_read(int epollfd, int fd, char* buf)
{
int nread;
nread = read(fd, buf, sizeof(DATA_T)); //每次读取固定长度数据
if (nread == -1)
{
perror("read error:");
close(fd);
delete_event(epollfd, fd, EPOLLIN);
}
else if (nread == 0)
{
fprintf(stderr, "client close.\n");
close(fd);
delete_event(epollfd, fd, EPOLLIN);
//移出在线列表
}
else
{
//解析命令
DATA_T dataPack;
memcpy(&dataPack, buf, sizeof(DATA_T));
int funcode, optcode;
funcode = dataPack.head.funcCode;
optcode = dataPack.head.operCode;
switch (funcode)
{
case LOGIN_IN: //客户端登录
{ //获取用户名、密码
USER_T login = { 0 };
memcpy(&login, dataPack.data, sizeof(USER_T));
printf("read message is : name=%s, password=%s, sockfd=%d \n", login.UserName, login.PassWord, login.sockfd);
//数据包定义
//char BackData[1024] = { 0 };
//HEAD_T ret_head ={0}; //服务应答数据包头
RET_T ret_info = { 0 };
//登录应答包
printf("login module\n");
if (strcmp(login.UserName, "admin") == 0 && strcmp(login.PassWord, "123456") == 0)
{
dataPack.head.funcCode = LOGIN_IN;
dataPack.head.dataLength = sizeof(HEAD_T) + sizeof(RET_T);
ret_info.flag = LOGIN_SUCCESS;
strcpy(ret_info.name, login.UserName);
memcpy(dataPack.data, &ret_info, sizeof(RET_T));
printf("%s login success\n", ret_info.name);
write(fd, &dataPack, sizeof(DATA_T));
printf("server %d write login answer data success\n ", fd);
}
//存入在线列表
//广播上线信息
//to do....
}
break;
case LOGIN_OUT: // 下线
{
printf("receive [end]\n");
}
break;
}
}
}
完整服务器设计
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/epoll.h>
#include <sqlite3.h>
#define PORT_NUMBER 8888
#define BACKLOG 10
#define MAXSIZE 1024 //数据缓冲区
#define FDSIZE 1000 //客户端连接数量
#define EPOLLEVENTS 100
#define MAX_CLIENT 10 //客户端最大数
#define LOGIN_IN 1
#define LOGIN_OUT 2
#define LOGIN_SUCCESS 1
#define LOGIN_FAIL 0
//定义数据包头
typedef struct head
{
int funcCode; // 功能码
int dataLength; //描述数据长度
int operCode; // 操作码
}HEAD_T;
typedef struct user //定义用户数据包体
{
char UserName[32];
char PassWord[32];
int sockfd; //客户端连接套接字
}USER_T;
typedef struct ret //定义服务器应答包体
{
int flag; //定义用户登录返回状态
char name[32]; //返回用户名
}RET_T;
//定长包头+定长数据包
typedef struct data
{
HEAD_T head; //包头字段
char data[1024-sizeof(HEAD_T)];
}DATA_T;
//处理接收到的连接
static void handle_accpet(int epollfd, int listenfd);
//读处理
void do_read(int epollfd, int fd, char* buf);
//写处理
void do_write(int epollfd, int fd, char* buf);
//添加事件
void add_event(int epollfd, int fd, int state);
//修改事件
void modify_event(int epollfd, int fd, int state);
//删除事件
void delete_event(int epollfd, int fd, int state);
/* socket->bind->listen->accept->send/recv->close*/
int main(int argc, char** argv)
{
int sock_fd, new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int ret;
socklen_t addr_len;
int recv_len;
char recv_buf[1024];
int client_num = -1;
signal(SIGCHLD, SIG_IGN);
/* socket */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
if (-1 == sock_fd)
{
fprintf(stderr, "socket error:%s\n\a", strerror(errno));
exit(1);
}
//设置Socket属性:SO_REUSEADDR:允许在bind过程中本地地址重复使用
int iSockopt = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
(const char*)&iSockopt, sizeof(int)) < 0)
{
close(sock_fd);
exit(1);
}
/* set server sockaddr_in */
memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
server_addr.sin_port = htons(PORT_NUMBER);
/* bind */
ret = bind(sock_fd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr));
if (-1 == ret)
{
fprintf(stderr, "bind error:%s\n\a", strerror(errno));
close(sock_fd);
exit(1);
}
/* listen */
ret = listen(sock_fd, BACKLOG);
if (-1 == ret)
{
fprintf(stderr, "listen error:%s\n\a", strerror(errno));
close(sock_fd);
exit(1);
}
printf("server start!!!\n");
/* 开始epoll流程 */
int epollfd;
struct epoll_event events[EPOLLEVENTS];// 并发100个事件
//创建一个描述符
epollfd = epoll_create(FDSIZE);
//添加监听描述符事件
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sock_fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, sock_fd, &ev);
/* accept */
while (1)
{
//获取已经准备好的描述符事件
ret = epoll_wait(epollfd, events, EPOLLEVENTS, -1);
int i;
int fd;
//进行选好遍历
for (i = 0; i < ret; i++)
{
fd = events[i].data.fd;
//根据描述符的类型和事件类型进行处理
if ((fd == sock_fd) && (events[i].events & EPOLLIN))
{
handle_accpet(epollfd, sock_fd);
}
else if (events[i].events & EPOLLIN)
{
//memset(recv_buf, 0, sizeof(recv_buf));
do_read(epollfd, fd, recv_buf);
}
}
}
/* close */
close(sock_fd);
exit(0);
}
void handle_accpet(int epollfd, int listenfd)
{
int client_num = 0;
socklen_t addr_len = sizeof(struct sockaddr);
struct sockaddr_in client_addr;
int new_fd = accept(listenfd, (struct sockaddr*)&client_addr, &addr_len);
if (-1 == new_fd)
{
fprintf(stderr, "accept error:%s\n\a", strerror(errno));
close(listenfd);
exit(1);
}
else
{
client_num++;
fprintf(stderr, "Server get connetion form client%d: %s\n", client_num, inet_ntoa(client_addr.sin_addr));
//添加一个客户描述符和事件
add_event(epollfd, new_fd, EPOLLIN);
}
}
void do_read(int epollfd, int fd, char* buf)
{
int nread;
nread = read(fd, buf, sizeof(DATA_T)); //每次读取固定长度数据
if (nread == -1)
{
perror("read error:");
close(fd);
delete_event(epollfd, fd, EPOLLIN);
}
else if (nread == 0)
{
fprintf(stderr, "client close.\n");
close(fd);
delete_event(epollfd, fd, EPOLLIN);
//移出在线列表
}
else
{
//解析命令
DATA_T dataPack;
memcpy(&dataPack, buf, sizeof(DATA_T));
int funcode, optcode;
funcode = dataPack.head.funcCode;
optcode = dataPack.head.operCode;
switch (funcode)
{
case LOGIN_IN: //客户端登录
{ //获取用户名、密码
USER_T login = { 0 };
memcpy(&login, dataPack.data, sizeof(USER_T));
printf("read message is : name=%s, password=%s, sockfd=%d \n", login.UserName, login.PassWord, login.sockfd);
//数据包定义
//char BackData[1024] = { 0 };
//HEAD_T ret_head ={0}; //服务应答数据包头
RET_T ret_info = { 0 };
//登录应答包
printf("login module\n");
if (strcmp(login.UserName, "admin") == 0 && strcmp(login.PassWord, "123456") == 0)
{
dataPack.head.funcCode = LOGIN_IN;
dataPack.head.dataLength = sizeof(HEAD_T) + sizeof(RET_T);
ret_info.flag = LOGIN_SUCCESS;
strcpy(ret_info.name, login.UserName);
memcpy(dataPack.data, &ret_info, sizeof(RET_T));
printf("%s login success\n", ret_info.name);
write(fd, &dataPack, sizeof(DATA_T));
printf("server %d write login answer data success\n ", fd);
}
//存入在线列表
//广播上线信息
//to do....
}
break;
case LOGIN_OUT: // 下线
{
printf("receive [end]\n");
}
break;
}
}
}
void do_write(int epollfd, int fd, char* buf)
{
}
void add_event(int epollfd, int fd, int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
}
void delete_event(int epollfd, int fd, int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev);
}
void modify_event(int epollfd, int fd, int state)
{
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev);
}
客户端登录槽,数据包发送
客户端更新接收线程解析应答数据包
实验结果如下
下一篇:
C++高并发服务器设计–共享内存封装(六)