一、前言
前面已经介绍过一个原始结构epoll服务器的使用,原文请点击查看:epoll原始结构服务器
现在,我将在原epoll服务器的基础上对epoll的用法进行改进,进一步简化epoll服务器的编码方式,这种框架在大型项目中十分高效,正所谓框架搭的好,往里面填“dog shit”都不会有问题。
这当中,我们需要用到一个简单的消息封装结构,这在通信中十分常见,类似于一种伪协议,只有内部通用。不过这种内部通用的协议在很多场合能大大简化程序的编码,提升编码效率。
首先来看一个消息头的定义:
typedef struct tagMSG_HEAD
{
UCHAR msgType;
UCHAR ucFlag;
USHORT usReserve;/* 字节对其 */
UINT uiMsgLen;
UINT uiPara1;/* 消息交互对方回复的值,用于多次交互中记录状态的情况 */
UINT uiPara2;
}MSG_HEAD_S;
#define MSG_BUFF_LEN_1024 1024
#define MSG_BUFF_LEN_2048 2048
这里我们定义了一个消息头,包含消息类型,消息携带flag,消息长度,交互参数para1,para2,其中usReserve字段是为了字节对齐留的,这在通信结构中十分常见,为使CPU能够对数据进行快速访问,数据的起始地址应具有“对齐”特性。比如4字节数据的起始地址应位于4字节边界上,即起始地址能够被4整除。更多的关于字节对齐的介绍,请自行bing 或百度。
下面我们再来看看epoll_event的结构:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // Epoll events
epoll_data_t data; // User data variable
};
请重点关注epoll_data_t这个字段。这个字段是一个联合体,联合体在同一时间只有一个成员变量,联合体占用的内存大小为所有成员中占用字节最大的成员决定,在此处,就是类型为uint_64_t 的u64。
原始epoll结构有一个很大的麻烦,fd字段放在了epoll_data_t结构 ,这就使得在使用的时候,我们不得不在fd与用户自己的数据之间做一个抉择,关键是fd不能少,少了也就失去 epoll的意义。选择保存用户数据到epoll_data_t中,那么我们自定义 的结构中一定要包含fd,但是请注意,用户自定义的数据此时只能保存一个地址,即保存在指针*ptr中。常规的编程中,我们发现,几乎所有的epoll结构都没有自定义用户数据,即使定义了,对epoll的使用也不得不变得麻烦,并且用户不得不为每一个epoll注册申请一个 内存空间(本人目前没有发现更好的办法,如果不申请内存,那么只能用全局或静态变量)。当有大量服务连接的时候,每次触发epoll机制都不得不重新根据ptr来获取自定义结构内存,这样难免会影响epoll服务器的性能,即使内核对epoll已经足够优化,用户自己查找的过程中,还是必不可少的增加了时间,间接或直接地降低了epoll的效率。
那么该怎么优化呢?
我们还是来看epoll_data_t这个结构:之前说过,联合体内存是邓毅字节最大的成员的字节数,此处就是u64。我们可以发现,这是一个8字节的长整型,有64位之多。我们可以在这个数上作作文章。想过将这个8字节的长整型分成两个整型部分,当然不是真的对分开,是通过左移右移实现。想过用4个字节保存函数指针,单指针的长度一般根据系统而定,32位系统占用4字节,64位系统指针占8字节,因此,不太合数。那么我们是否可以用来保存一个我们想保存的数字,比如消息类型,32位整型最大值位4294967295,最大42亿之多。另一部分32位,我们用来存放fd,SOCKET取值范围是0~4294967295,实际上我们常用的也就是0~65535。好了,想清楚方案,就很容易了。
根据以上规则,我们队epoll_ctl函数进行简单的封装,并把这个想法加上去:注意data.u64的使用。
static int Epoll_Control(const int sockfd, const int oper, const int events, unsigned int CallbackType)
{
struct epoll_event ep_event;
int iret = 0;
memset(&ep_event, 0, sizeof(ep_event));
ep_event.events = events;
/* 先存自定义消息类型,并左移(向高位移)32位,不同消息类型对应不同的回调函数,低位32位存储fd */
ep_event.data.u64 = (uint64_t)(((uint64_t)(CallbackType) << 32) | (uint64_t)(int)sockfd);
iret = epoll_ctl(g_Main_Epoll_Fd, oper, sockfd, &ep_event);
if (0 > iret)
{
perror("epoll_ctl error.");
printf("epoll_ctl error opt:%d\n", oper);
return -1;
}
return 0;
}
注册完成了,等待的就是epoll_wait触发回调,触发epoll_wait时,我们可以这样获取到fd和自定义类型,定义为宏,规则:先用int截断长整型,去除fd,然后右移32位,再以unsigned int 截断获取自定义type,先后顺序可调整,方法不变。
#define GET_EPOLL_CALLBACK_TYPE(ep_event) (unsigned int)((ep_event).data.u64 >>32)
#define GET_EPOLL_FD(ep_event) (int)(unsigned int)((ep_event).data.u64)
二、源码
由于时间有限,下面的源代码是在之前的epoll服务器上改写的,主要就是添加了epoll的回调,另外就是回调中含有回调,这在框架中十分常见。代码并没有编译调试,但是基本思路是正确的,一些epoll等资源回收还有缺陷,后面有时间会进行调试修改。
#ifdef __cplusplus //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <poll.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
//#include "basetype.h"
/*
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // Epoll events
epoll_data_t data; // User data variable
};
*/
typedef struct tagMSG_HEAD
{
UCHAR ucmsgType;
UCHAR ucFlag;
USHORT usReserve;/* 字节对其 */
UINT uiMsgLen;
UINT uiPara1;/* 消息交互对方回复的值,用于多次交互中记录状态的情况 */
UINT uiPara2;
}MSG_HEAD_S;
#define MSG_BUFF_LEN_1024 1024
#define MSG_BUFF_LEN_2048 2048
typedef enum tagEPOLL_MSG_TYPE
{
EPOLL_PROC_INFD = 0,
EPOLL_PROC_RECVMSG,
EPOLL_PROC_TYPE2,
EPOLL_PROC_MAX
}EPOLL_MSG_TYPE_E;
typedef void (*EPOLL_CALLBACK_PF)(int , unsigned int);
static EPOLL_CALLBACK_PF g_apfEpollCallback[EPOLL_PROC_MAX] = {NULL};
/***************************************************************************
* |
* Function Name : SORT_RegCallBackFunc
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void EPOLL_RegCallBackFunc(EPOLL_MSG_TYPE_E type, EPOLL_CALLBACK_PF callBack)
{
/* we thinks The default type > 0 */
if (EPOLL_PROC_MAX > type)
{
g_apfEpollCallback[type] = callBack;
}
}
/**************************************************************************
* |
* Function Name : EPOLL_DeRegCallBackFunc
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void EPOLL_DeRegCallBackFunc()
{
int i = 0;
for(i = 0; i < EPOLL_PROC_MAX; i ++)
{
g_apfEpollCallback[i] = NULL;
}
}
typedef enum tagMSG_TYPE
{
MSG_PROC_TYPE0 = 0,
MSG_PROC_TYPE1,
MSG_PROC_TYPE2,
MSG_PROC_MAX
}MSG_TYPE_E;
typedef void (*MSG_CALLBACK_PF)(int , MSG_HEAD_S *);
static MSG_CALLBACK_PF g_apfMsgCallback[MSG_PROC_MAX] = {NULL};
/**************************************************************************
* |
* Function Name : MSG_RegCallBackFunc
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void MSG_RegCallBackFunc(MSG_TYPE_E type, EPOLL_CALLBACK_PF callBack)
{
/* we thinks The default type > 0 */
if (MSG_PROC_MAX > type)
{
g_apfMsgCallback[type] = callBack;
}
}
/**************************************************************************
* |
* Function Name : MSG_DeRegCallBackFunc
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void MSG_DeRegCallBackFunc()
{
int i = 0;
for(i = 0; i < MSG_PROC_MAX; i ++)
{
g_apfMsgCallback[i] = NULL;
}
}
static int g_Main_Epoll_Fd = -1;
#define MAX_EPOLL_EVENT_COUNT 64
#define MAX_LISTEN_NUM 10
#define MAX_BUFF_LEN 512
#ifndef BIT_TEST
#define BIT_TEST(a, b) ( 0 != ((a) & (b)))
#endif
#ifndef BIT_SET
#define BIT_SET(a, b) ((a) |= (b))
#endif
#ifndef BIT_RESET
#define BIT_RESET(a, b) ((a) &= ~(b))
#endif
#ifndef BIT_MATCH
#define BIT_MATCH(a,b) (( (a) & (b) ) == (b))
#endif
#ifndef BIT_COMPARE
#define BIT_COMPARE(a,b,c) (( (a) & (b) ) == (c))
#endif
#define GET_EPOLL_CALLBACK_TYPE(ep_event) (unsigned int)((ep_event).data.u64 >>32)
#define GET_EPOLL_FD(ep_event) (int)(unsigned int)((ep_event).data.u64)
static void print_socket_info(struct addrinfo *ai)
{
char ipstr[INET6_ADDRSTRLEN];
uint16_t port;
void *addr = NULL;
char *ipver = NULL;
struct sockaddr_in *ipv4 = NULL;
struct sockaddr_in6 *ipv6 = NULL;
assert(NULL != ai);
if (ai->ai_family == AF_INET)
{ // IPv4
ipv4 = (struct sockaddr_in *)ai->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
port =ntohs(((struct sockaddr_in*)ai->ai_addr)->sin_port);
}
else
{ // IPv6
ipv6 = (struct sockaddr_in6 *)ai->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
port =ntohs(((struct sockaddr_in6*)ai->ai_addr)->sin6_port);
}
// convert the IP to a string and print it:
(void)inet_ntop(ai->ai_family, addr, ipstr, sizeof ipstr);
printf(" server initing... AF: %s IP: %s,PORT: %u\n", ipver, ipstr,port);
return ;
}
static int SetSocketNoblocking(const int sockfd)
{
int flags = 0;
int iret = 0;
flags = fcntl (sockfd, F_GETFL, 0);
if (flags == -1)
{
perror ("fcntl");
return -1;
}
flags |= O_NONBLOCK;
iret = fcntl (sockfd, F_SETFL, flags);
if (iret == -1)
{
perror ("fcntl");
return -1;
}
return 0;
}
/**************************************************************************
* |
* Function Name : Epoll_Control
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
static int Epoll_Control(const int sockfd, const int oper, const int events, unsigned int CallbackType)
{
struct epoll_event ep_event;
int iret = 0;
memset(&ep_event, 0, sizeof(ep_event));
ep_event.events = events;
/* 先存自定义消息类型,并左移(向高位移)32位,不同消息类型对应不同的回调函数,低位32位存储fd */
ep_event.data.u64 = (uint64_t)(((uint64_t)(CallbackType) << 32) | (uint64_t)(int)sockfd);
iret = epoll_ctl(g_Main_Epoll_Fd, oper, sockfd, &ep_event);
if (0 > iret)
{
perror("epoll_ctl error.");
printf("epoll_ctl error opt:%d\n", oper);
return -1;
}
return 0;
}
static int CreateServerScoket(const char *host, const char *port)
{
struct addrinfo hints;
struct addrinfo*ailist= NULL;
struct addrinfo *ai= NULL;
int iret;
int sockfd = 0;
int sockoptval = 1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // AF_INET 或 AF_INET6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; /* All interfaces */
iret = getaddrinfo(host, port, &hints, &ailist);
if (0 != iret)
{
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(iret));
return -1;
}
for(ai = ailist;ai != NULL; ai = ai->ai_next)
{
sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (0 > sockfd)
{
continue;
}
/* set reuse addr */
iret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockoptval, sizeof(sockoptval));
if (0 > iret)
{
perror("setsockopt error\n");
freeaddrinfo(ailist);
return -1;
}
iret = bind(sockfd, ai->ai_addr, ai->ai_addrlen);/*sizeof(struct sockaddr)*/
if (0 == iret)
{
print_socket_info(ai);
break;
}
else
{
close(sockfd);
}
}
if (NULL == ai)
{
fprintf(stderr, "get server socket error.\n");
return -1;
}
freeaddrinfo(ailist);
/* set no-block mode */
iret = SetSocketNoblocking(sockfd);
if (0 != iret)
{
return -1;
}
/* listen socket */
iret = listen(sockfd, MAX_LISTEN_NUM);
if (0 > iret)
{
perror("listen socket error.\n");
return -1;
}
printf("server socket init success. listenning...\n");
return sockfd;
}
/**************************************************************************
* |
* Function Name : HandleAccept
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
static void HandleAccept(const int listen_scok, int Event)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
int client_sock = -1;
if (BIT_TEST(EPOLLIN, Event))
{
client_sock = accept(listen_scok, (struct sockaddr *)&addr, &len);
if(client_sock < 0 )
{
perror("accept infd error");
return;
}
(void)Epoll_Control(client_sock, EPOLL_CTL_ADD, EPOLLIN, EPOLL_PROC_RECVMSG);/* 注册服务器回调函数类型 */
}
return ;
}
/**************************************************************************
* |
* Function Name : ProcRecvMsg
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
static void ProcRecvMsg(int sockfd, unsigned int Event)
{
ssize_t recvlen = 0;
char recvBuf[MSG_BUFF_LEN_1024];
MSG_HEAD_S *pstMsgHead = NULL;
unsigned int uiMsgType = MSG_PROC_MAX;
if (BIT_TEST(EPOLLIN, Event))
{
//read ev ready;
recvlen = recv(sockfd, recvBuf, sizeof(recvBuf), 0);
if(recvlen < 0)
{
perror("recv error.");
return ;
}
else if(recvlen == 0)
{
printf("client quit\n");
(void)Epoll_Control(sockfd, EPOLL_CTL_DEL, 0,0);
close(sockfd);
}
else
{
/* 可在此处定义回调函数 , 此种情况下,我们只需要在accept出注册关心EPOLLIN事件就可以
此处的回调函数可以是处理客户端的不同请求,是通用的,定义方式与epoll回调函数注册方式一致。*/
/* callback_proc[type],msgtype是解析消息传入的消息头携带的
*/
pstMsgHead = (MSG_HEAD_S)(recvBuf);
/* 这里需要转字节序 */
uiMsgType = ntohl(pstMsgHead->uiMsgLen);
/* 这里在内部处理,消息发送,配置等 */
g_apfMsgCallback[uiMsgType](sockfd, pstMsgHead);
}
}
return ;
}
/**************************************************************************
* |
* Function Name : SendMsg2Client
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
static void SendMsg2Client(const int sockfd, MSG_HEAD_S *pstMsgHead)
{
ssize_t sendlen = 0;
char sendBuf[MAX_BUFF_LEN];
int event = 0;
pstMsgHead = pstMsgHead;
sprintf(sendBuf, "HTTP/1.0 200 OK\r\n\r\n<html><h1>Hello Epoll! [client_fd:%d]</h1></html>", sockfd);
int sendsize = send(sockfd, sendBuf, strlen(sendBuf)+1, 0);
if(sendsize <= 0)
{
perror("send error.");
(void)Epoll_Control(sockfd, EPOLL_CTL_DEL, 0);
close(sockfd);
}
/*
else
{
printf("Server reply msg ok! data: %s\n", sendBuf);
event = EPOLLIN | EPOLLERR | EPOLLHUP;
(void)Epoll_Control(sockfd, EPOLL_CTL_MOD, event);
} */
return ;
}
/**************************************************************************
* |
* Function Name : GloballInit
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void GloballInit()
{
EPOLL_RegCallBackFunc(EPOLL_PROC_INFD, HandleAccept);
EPOLL_RegCallBackFunc(EPOLL_PROC_RECVMSG, ProcRecvMsg);
/* 注册epoll回调函数内部处理函数,这里不是向epoll注册 */
MSG_RegCallBackFunc(MSG_PROC_TYPE0, SendMsg2Client);
}
/**************************************************************************
* |
* Function Name : GloballFini
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
void GloballFini()
{
EPOLL_DeRegCallBackFunc();
EPOLL_DeRegCallBackFunc();
MSG_DeRegCallBackFunc();
}
/**************************************************************************
* |
* Function Name : main
* Input :
* Author : lily
* Date : 2018-8-19
* Description :
* Return :
* |
**************************************************************************/
int main(int argc, char *argv[])
{
int listenfd =0;
int epfd = 0;
int iret = 0;
int event = 0;
struct epoll_event ep_events[MAX_EPOLL_EVENT_COUNT];
int ev_num = 0;
int in_sockfd = 0;
int loop = 0;
unsigned int CallBackType = EPOLL_PROC_MAX;
if (3 > argc)
{
printf("Usage: %s [ip_addr] [port]\n",argv[0]);
return -1;
}
GloballInit();
listenfd = CreateServerScoket(argv[1], argv[2]);
if (0 > listenfd)
{
return -1;
}
epfd = epoll_create(1);
if (-1 == epfd)
{
perror("create epoll error.\n");
return -1;
}
else
{
g_Main_Epoll_Fd = epfd;
}
event = EPOLLIN;
iret = Epoll_Control(listenfd, EPOLL_CTL_ADD, event, EPOLL_PROC_INFD);
if (0 != iret)
{
return -1;
}
/* proc epoll event regined */
printf("server init success, epoll wait now.\n");
for(;;)
{
memset(ep_events, 0, sizeof(ep_events));
ev_num = epoll_wait(g_Main_Epoll_Fd, ep_events, MAX_EPOLL_EVENT_COUNT, -1);
switch (ev_num)
{
case 0:
{
printf("epoll_wait timeout.\n");
break;
}
case -1:
{
perror("epoll_wait error.\n");
break;
}
default:
{
for (loop = 0; loop < ev_num; loop++)
{
in_sockfd = GET_EPOLL_FD(ep_events[loop]);
CallBackType = GET_EPOLL_CALLBACK_TYPE(ep_events[loop]);
/* 这里回调函数仅仅是epoll的回调,至于其他的回调,可以在回调函数内部再回调 */
(EPOLL_CALLBACK_PF)g_apfEpollCallback[CallBackType](in_sockfd, CallBackType);
}
break;
}
}
}
GloballFini();
return 0;
}
#ifdef __cplusplus
}
#endif