基于VC开发epoll/linux 程序指南
8.3 调用socket-closesocket()函数调整... 7
本文是讲解日如何采用VC进行epoll编程,这是基于VC和windows API开发跨平台程序的重要组成部分,阅读本文之前需要先阅读《基于VC开发linux服务器程序指南》,文档网址:
https://blog.****.net/freeland008/article/details/107214767
目前在linux下进行socket编程,一般使用select多路复用和epoll模式,
Windows和linux基于select模式开发,调用的函数类似。 windows不支持epoll,而只支持完成端口。要实现海量TCP并发访问必须采用epoll模式,为了实现在VC上进行epoll编程,进而移植到linux/g++,作者提供了相应的解决方案,具体参考下面章节。
1、作者基于VC把select相关函数封装成epoll函数,具体参考作者提供的源码
文件。
2、开发人员就可以使用VC调用作者提供的epoll函数进行开发,相关epoll函
数的接口参数和返回值、功能兼容linux下的epoll函数。
3、因为不能完全兼容linux epoll,需要在VC的代码中加入部分兼容代码,采用
预定义指令,这些兼容代码只对VC有效,当移植到linux上是,这些兼容代
码不需要编译和执行。
4、VC工程在window上编译、调试、测试过后,就可以移植到linux/g++,epoll
部分代码可做到100%兼容。
说明:
1、作者会提供2个移植源码文件:EpollMng.cpp、EpollMng.h。
2、这2个文件只用于VC工程,移植到linux的源码是不需要这2个文件的。
作者提供的epoll封装函数有以下特点
- 支持epoll的函数有epoll_create()、epoll_ctl()、epoll_wait()
- 支持epoll定义的LT和ET模式
- 支持listen()、accept()、connect()等socket函数。
4、 作者封装的epoll内部的数据结构采用了数组和avl树数据结构,性能优异。
开发人员完全可以在windows下用epoll取代select进行开发。
5、 支持多次调用epoll_create(),生成多个epoll句柄,每个poller支持绑定多
个socket。
1、建议采用/VC 2008及以上版本,同时安装Visual Assist X开发助手。
2、操作系统
1)桌面系统采用64位Win7及以上版本。
2)服务器系统采用Windows 2008x64及以上版本
3、其他
如果只是开发32位程序,建议安装VC6,同时安装Visual Assist X开发助
手。
作者开发选用RedHat和CentOS 的linux,如果开发人员希望在其他厂商的linux上开发,需要进行进一步的测试
- 开发人员要求
- 精通Socket编程,尤其是异步编程。
- 精通epoll的工作机制,具体可参考epoll的相关文档。
3、 熟悉VC++编程。
- Linux针对epoll移植
1、只需去掉VC源码下的宏定义即可。
例如:#define PORT_OS_WIN32
- VC工程源码兼容性调整步骤和方案
作者提供了epoll封装源码文件为: EpollMng.cpp、EpollMng.h,开发人员把这2个文件加入到自己VC工程。
需要在源码中新增一个宏定义:
#define PORT_OS_WIN32
这个宏定义是限制某些epoll的兼容函数只运行在Windows的VC工程,在linux/g++下无效。
在调用任何epoll函数前,必须调用且只调用一次epoll的初始化函数,一般代码放置在程序初始化阶段,调用代码如下。
#ifdef PORT_OS_WIN32
epoll_init();
#endif
这个函数只对VC工程有效,linux忽略这个函数。
-
- 调用socket-recv()函数调整
因为采用epoll编程,socket必须是异步,在调用socket的recv()函数接收数据时,需要做以下调整。示例代码如下:
while(true)
{
char pBuf[1024*8];
int nRet;
nRet = recv(hSocket, pBuf, 8*1024, 0);
if(nRet == 0)
{
return -1;
}
else if(nRet == SOCKET_ERROR)
{
nRet = WSAGetLastError();
if(nRet == WSAEWOULDBLOCK)
{
#ifdef PORT_OS_WIN32
epoll_setrecvnull(nPollerID, hSocket);
#endif
return 0;
}
return -1;
}
}
其中红色代码部分是必须要加的,也就是说当recv()接收完socket缓冲区中所有的数据时,recv()函数时会返回阻塞(无数据)的错误码,这时必须执行epoll_setrecvnull函数,用来通知epoll封装类对象,已经无数据可读。红色代码只在VC工程有效,在linux/g++中不被编译。
以上加入的红色代码部分纯粹是为了让VC支持epoll接口的编程机制。
-
- 调用socket-send()函数调整
因为采用epoll编程,socket必须是异步,在调用socket的send()函数发送 数据时,需要做以下调整。示例代码如下:
while(true)
{
char pBuf[1024*8];
int nRet;
nRet = send(hSocket, pBuf, 8*1024, MSG_NOSIGNAL);
if(nRet == 0)
{
return -1;
}
else if(nRet == SOCKET_ERROR)
{
nRet = WSAGetLastError();
if(nRet == WSAEWOULDBLOCK)
{
#ifdef PORT_OS_WIN32
epoll_setsendnull (nPollerID, hSocket);
#endif
return 0;
}
return -1;
}
}
其中红色代码部分是必须要加的,也就是说当socket缓冲区满,再调用send()时,send()函数时会返回阻塞(缓冲满)的错误码,这时必须执行epoll_ setsendnull函数,用来通知epoll封装类对象,socket不可写。红色代码只在VC工程有效,在linux/g++中不被编译。
以上加入的红色代码部分纯粹是为了让VC支持epoll接口的编程机制。
当在VC工程中调用closesocket函数关闭socket(linux调用close())时,如果该socket之前绑定到某个epoll句柄,需要对closesocket()函数附近的相关代码做调整。示例代码如下:
#ifdef PORT_OS_WIN32
epoll_event pEvent;
epoll_ctl(nPollerID, EPOLL_CTL_DEL, hSocket, &pEvent);
#endif
closesocket(hSocket);
其中红色代码部分是必须要加的,当在VC工程中调用closesocket()关闭socket时,需要把socket从epoll句柄中解除绑定。linux不需要执行此函数,因为linux调用close()关闭socket时会自动解除绑定。
以上加入的红色代码部分纯粹是为了让VC支持epoll接口的编程机制。
由于作者是采用select函数封装成的epoll接口,在执行性能上稍低于原始的
select模式。如果进行并发测试,TCP连接超过上千个,性能会下降,主要是受限于select本身的限制。如果要进行大量TCP并发连接测试,还是要在linux上进行测试。
作者开发的所有服务器程序的网络编程都是采用epoll模式,先是在Windows的VC上进行开发、调试、测试,然后再把工程源码复制到linux上进行编译、运行、测试。
1、内存数据库系统
2、WebServer产品
- 短信网关
4、MQTT服务器系统
5、其他软件。
作者提供基于此方案的工程源码:TCP端口映射程序和WebServer程序。
- epoll移植头文件
以下文件是在VC源码工程中实现的epoll封装类的头文件(EpollMng.h),部分代码是从linux的头文件复制过来。EpollMng.cpp文件请从相关网址下载,或向作者索取。
#ifndef EPOLLMNG_H
#define EPOLLMNG_H
#include <winsock2.h>
#include <windows.h>
/* Flags to be passed to epoll_create2. */
enum
{
EPOLL_CLOEXEC = 02000000,
#define EPOLL_CLOEXEC EPOLL_CLOEXEC
EPOLL_NONBLOCK = 04000
#define EPOLL_NONBLOCK EPOLL_NONBLOCK
};
enum EPOLL_EVENTS
{
EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
EPOLLONESHOT = (1 << 30),
#define EPOLLONESHOT EPOLLONESHOT
EPOLLET = (1 << 31)
#define EPOLLET EPOLLET
};
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */
#define EPOLL_CTL_ADD 1 /* Add a file decriptor to the interface. */
#define EPOLL_CTL_DEL 2 /* Remove a file decriptor from the interface. */
#define EPOLL_CTL_MOD 3 /* Change file decriptor epoll_event structure. */
typedef union epoll_data
{
void *ptr;
int fd;
unsigned int/*uint32_t*/ u32;
unsigned __int64/*uint64_t*/ u64;
} epoll_data_t;
struct epoll_event
{
unsigned int/*uint32_t*/ events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
//////////////////////////////////////////////////////////////////////////
struct epoll_event_ex
{
epoll_event event;
bool bCanSend;
bool bCanRecv;
};
//////////////////////////////////////////////////////////////////////////
class CEpoller
{
public:
int m_nPeerHashSize;
epoll_event_ex** m_pPeerArray;
HANDLE m_hPollerTree;
HANDLE m_hEventTree;
fd_set m_pReadSet;
fd_set m_pWriteSet;
fd_set m_pErrorSet;
public:
epoll_event* m_pEventArray;
int m_nEventIndex;
public:
CEpoller();
~CEpoller();
public:
int Init(int nMaxSize);
int Destroy();
int AddEvent(SOCKET hSocket, epoll_event* ev);
int RemoveEvent(SOCKET hSocket);
int ModifyEvent(SOCKET hSocket, epoll_event* ev);
epoll_event_ex* GetEvent(SOCKET hSocket);
int WaitEvent(epoll_event* pEventArray, int nMaxEvents);
int FillSocket(int nTimeOut);
int SetSendNull(SOCKET hSocket);
int SetRecvNull(SOCKET hSocket);
};
class CEpollMng
{
public:
CRITICAL_SECTION m_cri;
int m_nUNID;
int m_nMaxPoller;
CEpoller** m_pPollerArray;
public:
CEpollMng();
~CEpollMng();
public:
int Init();
int Destroy();
public:
int CreatePoller(int nMaxSize);
int ClosePoller(int nPollerID);
CEpoller* GetPoller(int nPollerID);
int AddEvent(int nPollerID, SOCKET hSocket, epoll_event* ev);
int RemoveEvent(int nPollerID, SOCKET hSocket);
int ModifyEvent(int nPollerID, SOCKET hSocket, epoll_event* ev);
int WaitEvent(int nPollerID, epoll_event* pEventArray, int nMaxEvents, int nTimeOut);
int SetSendNull(int nPollerID, SOCKET hSocket);
int SetRecvNull(int nPollerID, SOCKET hSocket);
};
//////////////////////////////////////////////////////////////////////////
int epoll_wininit();
int epoll_setsendnull(int nPollerID, SOCKET hSocket);
int epoll_setrecvnull(int nPollerID, SOCKET hSocket);
//////////////////////////////////////////////////////////////////////////
int epoll_create(int nMaxSize);
int epoll_ctl(int nPollerID, int nActionID, SOCKET hSocket, epoll_event* pEvent);
int epoll_wait(int nPollerID, epoll_event* pEventArray, int nMaxEvents, int nTimeOut);
#endif
1、下图为端口映射的源码工程,可100%迁移到linux/g++编译运行。
2、作者可提供windowsx64和CentOSx64源码工程。