RT,常用的服务器架构原理如上面所示三种,虽然出现的时间不同,但即使到今天也仍然是各有侧重的,没有绝对的优劣之分
1)多线程/多进程
适用于多个连接间共享数据较少的情况,对于每一个客户端连接都会fork一个子进程,APACH是典型的代表。源码如下:
struct stClientInfo
{
int connfd;
std::string s_ip;
};
DWORD CServer::_ReadThreadFunc(LPVOID lpParam)
{
stClientInfo *pClientInfo = (stClientInfo *)lpParam;
if (NULL == pClientInfo)
return 1;
static const int BUF_SIZE = 512;
char buf[BUF_SIZE + 1];
while (true)
{
if (recv(pClientInfo->connfd, buf, BUF_SIZE, 0) <= 0) // socket断开
break;
buf[BUF_SIZE] = 0;
printf("%s 套接字%d:%s\n", pClientInfo->s_ip.c_str(), pClientInfo->connfd, buf);
}
printf("客户端断开! ip: %s 套接字: %d\n", pClientInfo->s_ip.c_str(), pClientInfo->connfd);
closesocket(pClientInfo->connfd);
delete pClientInfo;
return 0;
}
bool CServer::Run()
{
SOCKADDR_IN saServer;
int nRet = 0;
// 创建套接字
m_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (INVALID_SOCKET == m_sockfd)
{
_ErrorMsg(__FUNCTION__, "create socket failed");
return false;
}
// 初始化
memset(&saServer, 0, sizeof(saServer));
saServer.sin_family = AF_INET;
saServer.sin_port = htons(m_nPort);
saServer.sin_addr.s_addr = htonl(INADDR_ANY); // 远端为任意IP
// 绑定到端口
nRet = bind(m_sockfd, (sockaddr *)&saServer, sizeof(saServer));
if (-1 == nRet) // bind失败
{
_ErrorMsg(__FUNCTION__, "bind faild");
return false;
}
// 开始监听
nRet = listen(m_sockfd, SOMAXCONN);
if (-1 == nRet) //
{
_ErrorMsg(__FUNCTION__, "listen failed");
return false;
}
printf("Server start to listen port %d!\n", m_nPort);
while(true)
{
int nAddrSize = sizeof(saServer);
int connfd = accept(m_sockfd, (SOCKADDR *)&saServer, &nAddrSize);
SOCKADDR_IN saClient;
getpeername(connfd, (SOCKADDR *)&saClient, &nAddrSize);
printf("客户端连入! ip: %s 套接字: %d\n", inet_ntoa(saClient.sin_addr), connfd);
stClientInfo *pClientInfo = new stClientInfo();
pClientInfo->connfd = connfd;
pClientInfo->s_ip = inet_ntoa(*(struct in_addr *)&saClient.sin_addr);
::CreateThread(NULL, 0, _ReadThreadFunc, pClientInfo, 0, NULL);
}
return true;
}
2)select模型
非常之经典的模型,适用于连接数不是很多的情况,默认只支持64个客户端,修改后可支持到1024,但理想情况是要到数百个。优点是可以跨平台,假如代码需要同时用在windows与linux上,且连接数不大,那么select有时会是不错的选择。
注,标准的select模型其实有一定的改进空间,主要在于FD_SET、FD_ISSET等几个函数,稍后会提到。
源码:
bool CServer::RunSelectMode()
{
SOCKADDR_IN saServer;
int nRet = 0;
// 创建套接字
m_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (INVALID_SOCKET == m_sockfd)
{
_ErrorMsg(__FUNCTION__, "create socket failed");
return false;
}
// 初始化
memset(&saServer, 0, sizeof(saServer));
saServer.sin_family = AF_INET;
saServer.sin_port = htons(m_nPort);
saServer.sin_addr.s_addr = htonl(INADDR_ANY); // 远端为任意IP
// 绑定到端口
nRet = bind(m_sockfd, (sockaddr *)&saServer, sizeof(saServer));
if (-1 == nRet) // bind失败
{
_ErrorMsg(__FUNCTION__, "bind faild");
return false;
}
// 开始监听
nRet = listen(m_sockfd, SOMAXCONN);
if (-1 == nRet) //
{
_ErrorMsg(__FUNCTION__, "listen failed");
return false;
}
printf("Server start to listen port %d!\n", m_nPort);
// 初始化套接字集合
fd_set fdSocket;
FD_ZERO(&fdSocket);
FD_SET(m_sockfd, &fdSocket);
while(true)
{
fd_set fdRead = fdSocket;
int nRet = ::select(m_sockfd, &fdRead, NULL, NULL, NULL); // 找出当前有读信号的socket集合
if (nRet <= 0)
{
_ErrorMsg(__FUNCTION__, "select failed");
break;
}
for (int nIndex = 0; nIndex < fdSocket.fd_count; ++nIndex) // 遍历所有socket
{
if (FD_ISSET(fdSocket.fd_array[nIndex], &fdRead)) // 如果该socket属于fdRead,即该socket有读信号
{
if (fdSocket.fd_array[nIndex] == m_sockfd) // 监听socket收到读信号,即有新连接连入
{
if (fdSocket.fd_count < FD_SETSIZE) // 连接数未超过最大连接数
{
SOCKADDR_IN addrRemote;
int nLen = sizeof(addrRemote);
SOCKET sNew = accept(m_sockfd, (SOCKADDR *)&addrRemote, &nLen); // 接受新连接
FD_SET(sNew, &fdSocket); // 添加到socket集合中
printf("客户端连入! ip: %s 套接字: %d\n", inet_ntoa(addrRemote.sin_addr), sNew);
}
else
{
printf("too much connections!\n");
continue;
}
}
else // 其他socket收到读信号,即有客户端消息传入
{
static const int BUF_SIZE = 512;
static char pszBuf[BUF_SIZE];
int nRecv = recv(fdSocket.fd_array[nIndex], pszBuf, BUF_SIZE - 1, 0);
if (nRecv > 0) // 可读,有新数据到来
{
pszBuf[nRecv] = 0;
printf("%d: %s\n", fdSocket.fd_array[nIndex], pszBuf);
}
else // 连接断开
{
printf("客户端断开! 套接字: %d\n", fdSocket.fd_array[nIndex]);
closesocket(fdSocket.fd_array[nIndex]); // 关闭对应socket
FD_CLR(fdSocket.fd_array[nIndex], &fdSocket); // 从socket集合中删除该socket
}
}
}
}
}
return true;
}
3)完成端口 很简陋,未考虑数据包排序及异步投递接收请求
CIOCPServer::CIOCPServer(int nPort, int nMaxAccepts)
{
m_nPort = nPort;
m_nMaxAccepts = nMaxAccepts;
m_hListenThread = NULL;
m_hWorkThread = NULL;
m_sock = INVALID_SOCKET;
m_hCompletion = NULL;
}
CIOCPServer::~CIOCPServer(void)
{
}
bool CIOCPServer::Run()
{
// 创建重叠套接字
m_sock = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == m_sock)
{
printf("failed to create socket! errno = %d\n", WSAGetLastError());
return false;
}
// 绑定本地端口
SOCKADDR_IN sa;
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons(m_nPort);
if (::bind(m_sock, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR)
{
printf("failed to bind! errno = %d\n", WSAGetLastError());
return false;
}
// 监听端口
if (::listen(m_sock, m_nMaxAccepts) == SOCKET_ERROR)
{
printf("failed to listen! errno = %d\n", WSAGetLastError());
return false;
}
// 创建完成端口对象
m_hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
// 将监听套接字关联到完成端口对象,completion key 为0
::CreateIoCompletionPort((HANDLE)m_sock, m_hCompletion, 0, 0);
m_hListenThread = ::CreateThread(NULL, 0, __ListenThreadFunc, this, 0, NULL);
m_hWorkThread = ::CreateThread(NULL, 0, __WorkThreadFunc, this, 0, NULL);
return true;
}
DWORD CALLBACK CIOCPServer::__ListenThreadFunc(LPVOID lpParam)
{
CIOCPServer *pObj = (CIOCPServer *)lpParam;
if (NULL == pObj)
return 1;
int nRet = pObj->__ListenFunc();
return nRet;
}
DWORD CALLBACK CIOCPServer::__WorkThreadFunc(LPVOID lpParam)
{
CIOCPServer *pObj = (CIOCPServer *)lpParam;
if (NULL == pObj)
return 1;
int nRet = pObj->__WorkFunc();
return nRet;
}
DWORD CIOCPServer::__ListenFunc()
{
SOCKADDR_IN sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(m_nPort);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
printf("server start to listen!\n");
while (true)
{
int nSize = sizeof(sa);
SOCKET s = accept(m_sock, (SOCKADDR *)&sa, &nSize); // 接收新客户端连接
CIOCPContext *pContext = new CIOCPContext();
CIOCPBuffer *pBuffer = new CIOCPBuffer();
pContext->s = s;
pBuffer->s = s;
pBuffer->buff = new char[BUF_SIZE];
pBuffer->nOperation = CIOCPBuffer::OP_READ;
pBuffer->nLen = BUF_SIZE;
::CreateIoCompletionPort((HANDLE)s, m_hCompletion, (DWORD)pContext, 0); // 将新连接关联到完成端口对象
_PostRecv(pContext, pBuffer);
printf("connection established! socket: 0x%x\n", s);
}
return 0;
}
DWORD CIOCPServer::__WorkFunc()
{
DWORD dwKey;
DWORD dwTrans;
LPOVERLAPPED lpol;
while (true)
{
bool bRet = ::GetQueuedCompletionStatus(m_hCompletion,
&dwTrans, // 完成字节数
&dwKey, // 完成端口的key
&lpol, // 获取重叠结构
WSA_INFINITE);
if (-1 == dwTrans) // 用户通知退出,?
{
printf("WorkThread exit!\n");
::ExitThread(0);
}
CIOCPBuffer *pBuffer = CONTAINING_RECORD(lpol, CIOCPBuffer, ol);
int nErr = NO_ERROR;
CIOCPContext *pContext = (CIOCPContext *)dwKey;
//printf("socket 0x%x: has new signal!\n", pContext->s);
// 开始处理
switch (pBuffer->nOperation)
{
case CIOCPBuffer::OP_ACCEPT: // 接收
{
if (0 == dwTrans)
{
if (pBuffer->s != INVALID_SOCKET)
{
::closesocket(pBuffer->s);
pBuffer->s = INVALID_SOCKET;
}
}
else
{
}
}
break;
case CIOCPBuffer::OP_READ: // 读取
{
if (0 == dwTrans) // 对方关闭套接字
{
printf("socket 0x%x: disconnect!\n", pContext->s);
delete[] pBuffer->buff;
delete pBuffer;
delete pContext;
}
else // 正常读取
{
pBuffer->nLen = BUF_SIZE; // 字节数
printf("socket 0x%x: %s\n", pBuffer->s, pBuffer->buff);
_PostRecv(pContext, pBuffer);
}
}
break;
case CIOCPBuffer::OP_WRITE: // 写入
{
}
break;
}
}
return 0;
}
bool CIOCPServer::_PostRecv(CIOCPContext *pContext, CIOCPBuffer *pBuffer)
{
WSABUF buf;
buf.buf = pBuffer->buff;
buf.len = pBuffer->nLen;
DWORD dwBytes;
DWORD dwFlags = 0;
if (NO_ERROR !=::WSARecv(pContext->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL))
{
int nErrno = ::WSAGetLastError();
if (nErrno != WSA_IO_PENDING)
{
printf("socket 0x%x: _PostRecv error! errno = %d\n", pContext->s, nErrno);
return FALSE;
}
}
return TRUE;
}