一些好设计的经验:
linux网络:
高性能网络编程IO复用和Epoll高效率之处-遍历的集合更小空间换时间/水平触发和边缘触发主动返回。
反应堆的设计模式-避免C风格的一个应用逻辑都需要处理多个对象而是用OO设计模式方式分离。
windows网络:
select模型,WSAAsyncSelect模型,WSAEventSelect模型,重叠Overlapped IO模型,完全端口IO Completion Port模型。
是遵循定期检查,窗口事件通知,事件对象通知,多线程重叠IO和事件对象完成通知,事件对象完成通知和通过完成端口消息队列有效的管理外部和内部的线程池,进化来提高网络通信模型。
0.客户端设计
#include "stdafx.h"
#include <WINSOCK2.H>
#include <stdio.h>
#define SERVER_ADDRESS "127.0.0.1"
#define PORT 5150
#define MSGSIZE 8192 // window操作系统默认socket收发缓存大小是8K
#pragma comment(lib, "ws2_32.lib")
void CheckBuffer(SOCKET &socket)
{
//window 7,sock2,默认内核发送缓存和接收缓存都是8K.
int sendbuflen = 0;
int len = sizeof(sendbuflen);
getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&sendbuflen, &len);
printf("default,sendbuf:%d\n", sendbuflen);
getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&sendbuflen, &len);
printf("default,recvbuf:%d\n", sendbuflen);
/*sendbuflen = 10240;
setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len); */
}
bool LoadWSAData(WSADATA &wsaData)
{
// Initialize Windows socket library
WORD wVersionRequested = MAKEWORD(2, 2);
// MAKEWORD的作用,类似下面
WORD wHigh = 2;
WORD wLow = 2;
WORD wAll = ((wHigh << 8) | wLow);
// 初始化只需要传入版本号,和WSADATA就可以了
int reqErr = ::WSAStartup(wVersionRequested, &wsaData);
if(reqErr != 0)
{
printf("加载请求指定版本的windows socket api DLL 失败");
return false;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
::WSACleanup();
return false;
}
else
{
printf("The Winsock 2.2 dll was found okay\n");
return true;
}
}
void ReleaseWSAData()
{
::WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
SOCKET sClient;
SOCKADDR_IN server;
char szMessage[MSGSIZE];// 所有整数浮点数,非数值编码类型都可以转换为char/unsigned char的十六进制
if(!LoadWSAData(wsaData))
{
return 0;
}
// Create client socket
// 返回一个socket描述符,类似文件描述符,指针
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 协议族,socket类型,具体协议
if(INVALID_SOCKET == sClient)
{
printf("Get Socket Error: INVALID_SOCKET.\n");
return 0;
}
CheckBuffer(sClient);
// Connect to server
memset(&server, 0, sizeof(SOCKADDR_IN));
// 网络socket三元组,网络类型,地址,端口
server.sin_family = AF_INET;
server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
server.sin_port = htons(PORT);
// 协议地址端口,来标识一个server进程,都要转换为网络字节顺序
int nLen = sizeof(SOCKADDR_IN);// 大小为16 Byte,刚好是内存对齐模式,sockaddr也是16 Byte
// 阻塞模式connect会阻塞程序,客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
// 非阻塞会马上返回
// connect时候会随机分配一个端口和地址给当前客户端网络进程,服务器会收到
int nConnect = connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
if(SOCKET_ERROR == nConnect)
{
printf("Socket connnect Error.\n");
return 0;
}
while (TRUE)
{
printf("Send Msg:");
gets(szMessage);
// Send message
// 阻塞模式下:发送前会先检查发送缓存是否在发送数据,是等待,不在发送则检查内核发送缓存,比较大小,
// 如果小于那么拷贝到发送缓存,否则等待。
// 非阻塞模式下:先检查发送缓存是否在发送,是等待,不是马上拷贝发送,能拷贝多少就拷贝多少。
// 拷贝到内核发送缓存出错,那么返回SOCKET_ERROR,等待或者拷贝的过程中网络断开也返回SOCKET_ERROR
int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字符串长度不包含'\0'
if(SOCKET_ERROR == nSendRes)
{
printf("Send Copy data kernel buffer is too small or network shutdown!\n");
}
// Receive message
// 接收消息前会先检查发送缓存区,如果正在发送,那么等待发送缓冲区的数据发送完毕,期间网络出错返回SOCKET_ERROR.
// 阻塞模式下:按上面检查,recv收到数据完毕(协议会把一整个TCP包接收完毕,大包会重组后才算完毕)才返回,没收到则一直等待。
// 非阻塞模式下:按上面检查,recv没收到马上返回不会阻塞,收到等接收完毕才返回。
// 返回值小于0的SOCKET_ERROR检查是否EAGAIN 接收期间网络断开,非阻塞下没有收到数据的返回10035错误码。
// 返回值等于0,表示对方socket已经正常断开。
// 返回值不等于请求的缓存值,那么再次接收。
int nRecvRes = recv(sClient, szMessage, MSGSIZE, 0);
if(nRecvRes > 0)
{
szMessage[nRecvRes] = '\0';
printf("Bytes receive : %s\n", szMessage);
}
else if(nRecvRes== 0 || (nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
printf("Connection Close.\n");
break; // 调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
}
else if(nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
// 非阻塞类型返回
continue;
}
else
{
printf("Unknow recv error code: %d\n", WSAGetLastError());
break; // 调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
}
}
// Clean up
closesocket(sClient);
ReleaseWSAData();
return 0;
}
1.select模型
#ifndef _SELECTMODEL_H_
#define _SELECTMODEL_H_
#include <windows.h>
class SelectModel
{
public:
static DWORD WINAPI WorkerThread(LPVOID lpParameter);
int Process();
};
#endif
/*
总结:第一个accept线程阻塞,第二个线程select,FD_ISSET非阻塞的等待时间来处理socket网络IO。
1. 第一线程accept会阻塞,只有一个服务端socket对应多个客户端Socket,服务器需要获得客户端的socket并可关闭它。
2. 第二线程select可以根据传入时间,如果是NULL那么是完全阻塞的,如果是0那么是完全非阻塞的。
注意:处理大于64个的情况,在accept时候分组和开辟多个线程,或者是线程内分组,总是可以处理好的;
没有连接和延迟导致cpu不停空转情况,虽然不会导致cpu 100%,但是也可以通过延迟sleep来避免或者干脆不处理这种情况。
*/
#include "stdafx.h"
#include <winsock.h>
#include <stdio.h>
#include "SelectModel.h"
#define PORT 5150
#define MSGSIZE 8192
#pragma comment(lib, "ws2_32.lib")
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];// FD是File Describle文件描述符,也就是socket文件描述符(句柄)
//在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,
//比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
//
//对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,
//在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.
//这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
int SelectModel::Process()
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
int iaddrSize = sizeof(SOCKADDR_IN);
DWORD dwThreadId;
// Initialize Windows socket library
::WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
int opt = 1;
if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
{
printf("setsockopt Failed.\n");
return false;
}
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection,接收到连接才存放到数组里面,否则一直阻塞
// 这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。
// 一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
// Add socket to g_CliSocketArr
g_CliSocketArr[g_iTotalConn++] = sClient;
}
return 0;
}
DWORD SelectModel::WorkerThread(LPVOID lpParam)
{
int i = 0;
fd_set fdread;
int ret = 0;
struct timeval tv = {1, 0};// 1是阻塞等待1秒钟返回一次,后面的0是0毫秒
char szMessage[MSGSIZE];
while (TRUE)
{
FD_ZERO(&fdread);//将fdread初始化空集
for (i = 0; i < g_iTotalConn; i++)// 可以在这里分段处理64个,用以支持多于64个的连接select.
{
FD_SET(g_
linux网络:
高性能网络编程IO复用和Epoll高效率之处-遍历的集合更小空间换时间/水平触发和边缘触发主动返回。
反应堆的设计模式-避免C风格的一个应用逻辑都需要处理多个对象而是用OO设计模式方式分离。
windows网络:
select模型,WSAAsyncSelect模型,WSAEventSelect模型,重叠Overlapped IO模型,完全端口IO Completion Port模型。
是遵循定期检查,窗口事件通知,事件对象通知,多线程重叠IO和事件对象完成通知,事件对象完成通知和通过完成端口消息队列有效的管理外部和内部的线程池,进化来提高网络通信模型。
0.客户端设计
#include "stdafx.h"
#include <WINSOCK2.H>
#include <stdio.h>
#define SERVER_ADDRESS "127.0.0.1"
#define PORT 5150
#define MSGSIZE 8192 // window操作系统默认socket收发缓存大小是8K
#pragma comment(lib, "ws2_32.lib")
void CheckBuffer(SOCKET &socket)
{
//window 7,sock2,默认内核发送缓存和接收缓存都是8K.
int sendbuflen = 0;
int len = sizeof(sendbuflen);
getsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&sendbuflen, &len);
printf("default,sendbuf:%d\n", sendbuflen);
getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&sendbuflen, &len);
printf("default,recvbuf:%d\n", sendbuflen);
/*sendbuflen = 10240;
setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len); */
}
bool LoadWSAData(WSADATA &wsaData)
{
// Initialize Windows socket library
WORD wVersionRequested = MAKEWORD(2, 2);
// MAKEWORD的作用,类似下面
WORD wHigh = 2;
WORD wLow = 2;
WORD wAll = ((wHigh << 8) | wLow);
// 初始化只需要传入版本号,和WSADATA就可以了
int reqErr = ::WSAStartup(wVersionRequested, &wsaData);
if(reqErr != 0)
{
printf("加载请求指定版本的windows socket api DLL 失败");
return false;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
::WSACleanup();
return false;
}
else
{
printf("The Winsock 2.2 dll was found okay\n");
return true;
}
}
void ReleaseWSAData()
{
::WSACleanup();
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
SOCKET sClient;
SOCKADDR_IN server;
char szMessage[MSGSIZE];// 所有整数浮点数,非数值编码类型都可以转换为char/unsigned char的十六进制
if(!LoadWSAData(wsaData))
{
return 0;
}
// Create client socket
// 返回一个socket描述符,类似文件描述符,指针
sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 协议族,socket类型,具体协议
if(INVALID_SOCKET == sClient)
{
printf("Get Socket Error: INVALID_SOCKET.\n");
return 0;
}
CheckBuffer(sClient);
// Connect to server
memset(&server, 0, sizeof(SOCKADDR_IN));
// 网络socket三元组,网络类型,地址,端口
server.sin_family = AF_INET;
server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);
server.sin_port = htons(PORT);
// 协议地址端口,来标识一个server进程,都要转换为网络字节顺序
int nLen = sizeof(SOCKADDR_IN);// 大小为16 Byte,刚好是内存对齐模式,sockaddr也是16 Byte
// 阻塞模式connect会阻塞程序,客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
// 非阻塞会马上返回
// connect时候会随机分配一个端口和地址给当前客户端网络进程,服务器会收到
int nConnect = connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));
if(SOCKET_ERROR == nConnect)
{
printf("Socket connnect Error.\n");
return 0;
}
while (TRUE)
{
printf("Send Msg:");
gets(szMessage);
// Send message
// 阻塞模式下:发送前会先检查发送缓存是否在发送数据,是等待,不在发送则检查内核发送缓存,比较大小,
// 如果小于那么拷贝到发送缓存,否则等待。
// 非阻塞模式下:先检查发送缓存是否在发送,是等待,不是马上拷贝发送,能拷贝多少就拷贝多少。
// 拷贝到内核发送缓存出错,那么返回SOCKET_ERROR,等待或者拷贝的过程中网络断开也返回SOCKET_ERROR
int nSendRes = send(sClient, szMessage, strlen(szMessage), 0);// strlen求得的字符串长度不包含'\0'
if(SOCKET_ERROR == nSendRes)
{
printf("Send Copy data kernel buffer is too small or network shutdown!\n");
}
// Receive message
// 接收消息前会先检查发送缓存区,如果正在发送,那么等待发送缓冲区的数据发送完毕,期间网络出错返回SOCKET_ERROR.
// 阻塞模式下:按上面检查,recv收到数据完毕(协议会把一整个TCP包接收完毕,大包会重组后才算完毕)才返回,没收到则一直等待。
// 非阻塞模式下:按上面检查,recv没收到马上返回不会阻塞,收到等接收完毕才返回。
// 返回值小于0的SOCKET_ERROR检查是否EAGAIN 接收期间网络断开,非阻塞下没有收到数据的返回10035错误码。
// 返回值等于0,表示对方socket已经正常断开。
// 返回值不等于请求的缓存值,那么再次接收。
int nRecvRes = recv(sClient, szMessage, MSGSIZE, 0);
if(nRecvRes > 0)
{
szMessage[nRecvRes] = '\0';
printf("Bytes receive : %s\n", szMessage);
}
else if(nRecvRes== 0 || (nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
printf("Connection Close.\n");
break; // 调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
}
else if(nRecvRes == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
// 非阻塞类型返回
continue;
}
else
{
printf("Unknow recv error code: %d\n", WSAGetLastError());
break; // 调用closesocket避免四次挥手时候,主动关闭端一直在TIME_WAIT状态,被动端在CLOSE_WAIT状态。
}
}
// Clean up
closesocket(sClient);
ReleaseWSAData();
return 0;
}
1.select模型
#ifndef _SELECTMODEL_H_
#define _SELECTMODEL_H_
#include <windows.h>
class SelectModel
{
public:
static DWORD WINAPI WorkerThread(LPVOID lpParameter);
int Process();
};
#endif
/*
总结:第一个accept线程阻塞,第二个线程select,FD_ISSET非阻塞的等待时间来处理socket网络IO。
1. 第一线程accept会阻塞,只有一个服务端socket对应多个客户端Socket,服务器需要获得客户端的socket并可关闭它。
2. 第二线程select可以根据传入时间,如果是NULL那么是完全阻塞的,如果是0那么是完全非阻塞的。
注意:处理大于64个的情况,在accept时候分组和开辟多个线程,或者是线程内分组,总是可以处理好的;
没有连接和延迟导致cpu不停空转情况,虽然不会导致cpu 100%,但是也可以通过延迟sleep来避免或者干脆不处理这种情况。
*/
#include "stdafx.h"
#include <winsock.h>
#include <stdio.h>
#include "SelectModel.h"
#define PORT 5150
#define MSGSIZE 8192
#pragma comment(lib, "ws2_32.lib")
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];// FD是File Describle文件描述符,也就是socket文件描述符(句柄)
//在大规模的网络连接方面,还是推荐使用IOCP或EPOLL模型.但是Select模型可以使用在诸如对战类游戏上,
//比如类似星际这种,因为它小巧易于实现,而且对战类游戏的网络连接量并不大.
//
//对于Select模型想要突破Windows 64个限制的话,可以采取分段轮询,一次轮询64个.例如套接字列表为128个,
//在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.
//这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
int SelectModel::Process()
{
WSADATA wsaData;
SOCKET sListen, sClient;
SOCKADDR_IN local, client;
int iaddrSize = sizeof(SOCKADDR_IN);
DWORD dwThreadId;
// Initialize Windows socket library
::WSAStartup(0x0202, &wsaData);
// Create listening socket
sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Bind
local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
local.sin_family = AF_INET;
local.sin_port = htons(PORT);
int opt = 1;
if ( setsockopt(sListen, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0 )
{
printf("setsockopt Failed.\n");
return false;
}
bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
// Listen
listen(sListen, 3);
// Create worker thread
CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
while (TRUE)
{
// Accept a connection,接收到连接才存放到数组里面,否则一直阻塞
// 这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。
// 一种比较好的实现方案就是采用WSAAccept函数,而且让WSAAccept回调自己实现的Condition Function。
sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
// Add socket to g_CliSocketArr
g_CliSocketArr[g_iTotalConn++] = sClient;
}
return 0;
}
DWORD SelectModel::WorkerThread(LPVOID lpParam)
{
int i = 0;
fd_set fdread;
int ret = 0;
struct timeval tv = {1, 0};// 1是阻塞等待1秒钟返回一次,后面的0是0毫秒
char szMessage[MSGSIZE];
while (TRUE)
{
FD_ZERO(&fdread);//将fdread初始化空集
for (i = 0; i < g_iTotalConn; i++)// 可以在这里分段处理64个,用以支持多于64个的连接select.
{
FD_SET(g_