真正与网络相关的操作,都是网卡在做,而API就是网卡的驱动,比如recv,就是程序从网卡中读取数据,send,就是将一块数据通过总线传给网卡,然后由网卡发到网络中。
既然程序本身并不直接与网络交互,那么,怎么知道网络另外那头有数据发送过来了呢?在Windows NT3.5版本之前,就是不断的用每一个socket作为参数去recv,若返回值>0,就去处理业务,但是当客户端socket多了以后怎么办呢?一个线程处理不过来就开多个线程吧。这应该是早期的服务器模型了。但是这样做的效果不好,因为开启的线程是有任务在执行的,尽管执行的任务不复杂,但是的确在消耗CPU资源。例如下面这两段代码:
循环1
while(true)
{
//就是一个死循环,什么也不做;
}
循环2
while(true)
{
getchar();//等待用户输入一个字符;
/*
用户输入完后开始执行一堆操作;
*/
}
都是死循环,循环1就在不停的消耗资源,循环2在等待用户输入,处于挂起状态,不占用CPU资源。直到用户输入字符后,才开始占用CPU执行下面的代码。
官方解释就自行百度吧,针对上面提到的两个问题,IOCP都很好的解决了,因为涉及内核和硬件的范畴,你不用管它是怎么做到的,它的确是做到了。
程序不需要再死循环的去询问每一个socket“你现在有数据吗?”,而是内核主动告诉程序,“这个socket接收到数据了,你看怎么办”,或,“刚才给这个socket发送的数据已经发送完成了,你看怎么办”。
你开启的线程再空闲时期也不会占用CPU资源了,因为此时的线程与上面提到的循环2情况一样,是处于被挂起状态,在等待着内核通知它,“有个socket的操作完成了,你别睡了,起来看看怎么办”。如此一来,线程被高效的调度了,也就不需要开那么多的线程了,理论上讲,开始CPU物理核心*2的线程数,是最佳状态。
官方解释IOCP叫“完成端口”,我觉得,本质就是异步的事件驱动机制。
在Linux平台下也有一种优化方案,与windows IOCP扮演的角色一样,它叫EPOLL,机制都是异步的事件驱动,但内部原理不太一样,有兴趣的可以研读一下。
创建完成端口
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD NumberOfConcurrentThreads
);
创建一个IOCP完成端口句柄,你可以这样理解,CreateIoCompletionPort返回一个IOCP完成端口的指针(但它不是指针,只是代表这个IOCP完成端口而已,后面的操作都需要针对这个代表进行操作)。
接收数据
int WSAAPI WSARecv (
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPINT lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数较多并且比较复杂,暂时只要知道接收数据时使用这个函数,但这个函数并不直接返回接收到的结果,可以理解成向系统提交了一次数据接收任务。
发送数据
int WSASend (
SOCKET s,
LPWSABUF lpBuffers
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数较多并且比较复杂,暂时只要知道发送数据时使用这个函数,但这个函数并不立即发送,可以理解成向系统提交了一次数据发送任务。
获取已完成的任务
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
这个函数是在工作线程中被调用的,每个线程执行到这个函数时,如果没有已完成的任务,就会挂起在这个地方(也可以设置成不挂起或挂起指定时间),当有任务完成时(发送或接受完成),线程自动被唤醒,继续向下执行。
先看两个结构体WNSOperation(异步输入输出信息的结构体)和ClientStruct(客户端链接对象)
//用于异步输入输出信息的结构体
struct WNSOperation
{
OVERLAPPED ol; //IOCP结构体,必须放在第一个;
WSABUF wbuf; //缓冲区;
DWORD dwBytes; //上一次操作的字节数;
DWORD dwFlags; //标志位;
int signal; //标志位;
char OpCode; //操作标识,用来判断这个异步操作是读取('r')还是写入('w');
};
//客户端链接结构体;
class ClientStruct
{
public:
SOCKET socket; //SOCKET对象;
WNSOperation pReadOperation; //异步读取对象;
bool bSend; //是否正在进行发送;
private:
HANDLE sendQueueLock; //数据发送队列lock;
queue<WNSOperation*> sendQueue; //数据发送队列;
public:
ClientStruct(int ReadBufferSize)
{
bSend = false;
pReadOperation.wbuf.buf = new char[ReadBufferSize];
pReadOperation.OpCode = 'r';
}
~ClientStruct()
{
//先取出发送队列中未完成的BUFFER释放掉;
WNSOperation* res = NULL;
WaitForSingleObject(sendQueueLock,INFINITE);
while(sendQueue.empty() == false)
{
res = sendQueue.front();
sendQueue.pop();
delete res;
}
ReleaseMutex(sendQueueLock);
//释放接收BUFFER;
if(pReadOperation.wbuf.buf != NULL)
{
delete[] pReadOperation.wbuf.buf;
pReadOperation.wbuf.buf = NULL;
}
closesocket(socket);
}
//添加发送BUFFER到发送队列中;
void AddSendBuffer(WNSOperation* w)
{
WaitForSingleObject(sendQueueLock,INFINITE);
sendQueue.push(w);
ReleaseMutex(sendQueueLock);
}
//从发送队列中获取一个BUFFER,如果当前客户端正在进行发送任务,则返回NULL;
WNSOperation* GetSendBuffer()
{
WNSOperation* res = NULL;
WaitForSingleObject(sendQueueLock,INFINITE);
if(bSend == true)
{
//如果bSend == true,则表示这个客户端正在执行发送任务;
}
else
{
if(sendQueue.empty() == false)
{
res = sendQueue.front();
sendQueue.pop();
bSend = true;
}
else
{
bSend = false;
printf("发送任务已结束\n");
}
}
ReleaseMutex(sendQueueLock);
return res;
}
//等IOCP发送完成后,需要调用这个函数将bSend设置到false
void ResetSendSignal()
{
WaitForSingleObject(sendQueueLock,INFINITE);
bSend = false;
ReleaseMutex(sendQueueLock);
}
};
#include "stdafx.h"
#include
#include
#include
using namespace std;
#pragma comment(lib, "ws2_32.lib")
//工作线程数;
#define WORK_THREAD_COUNT 4
//客户端接收缓冲区大小;
#define READ_BUFFER_SIZE 8000
SOCKET socket_listen;//监听socket;
HANDLE Handle_Iocp;//iocp句柄;
HANDLE workThread[WORK_THREAD_COUNT];//工作线程句柄;
HANDLE acceptThread;//监听线程句柄;
//创建一个发送BUFFER;
WNSOperation* CreateSendBuff(int bufLen)
{
WNSOperation* sendBuffer = new WNSOperation();
memset(&sendBuffer->ol, 0, sizeof(OVERLAPPED));
sendBuffer->OpCode = 'w';
sendBuffer->dwBytes = 0;
sendBuffer->dwFlags = 0;
sendBuffer->wbuf.buf = new char[bufLen];
return sendBuffer;
}
//使一个ClientStruct开始接收数据;
bool ReadData(ClientStruct* pClientStruct, int offset, int count, int signal)
{
memset(&pClientStruct->pReadOperation.ol, 0, sizeof(OVERLAPPED));
pClientStruct->pReadOperation.wbuf.buf = pClientStruct->pReadOperation.wbuf.buf + offset;
pClientStruct->pReadOperation.wbuf.len = count;
pClientStruct->pReadOperation.dwFlags = 0;
pClientStruct->pReadOperation.dwBytes = 0;
pClientStruct->pReadOperation.signal = signal;
int ret = WSARecv(pClientStruct->socket, &pClientStruct->pReadOperation.wbuf, 1, &pClientStruct->pReadOperation.dwBytes, &pClientStruct->pReadOperation.dwFlags, &pClientStruct->pReadOperation.ol, NULL);
if (ret == -1)
{
ret = WSAGetLastError();
if(ret == 997)
return true;
printf("ReadData >>> %d\n", ret);
return false;
}
return true;
}
//使一个ClientStruct开始发送数据;
bool SendData(ClientStruct* pClientStruct)
{
WNSOperation* obj = pClientStruct->GetSendBuffer();
if(obj == NULL)
return true;
int ret = WSASend(pClientStruct->socket, &obj->wbuf, 1, &obj->dwBytes, obj->dwFlags, &obj->ol, NULL);
if (ret == -1)
{
ret = WSAGetLastError();
if(ret == 997)
return true;
printf("SendData >>> %d\n", ret);
return false;
}
return true;
}
//监听线程;
DWORD __stdcall AcceptThreadFunction(LPVOID Param)
{
sockaddr addr;
int clientlen = sizeof(struct sockaddr);
printf("监听线程已启动\n");
while(true)
{
ClientStruct* newClient = new ClientStruct(READ_BUFFER_SIZE);
if ((newClient->socket = accept(socket_listen,(struct sockaddr *)&addr, &clientlen)) == INVALID_SOCKET)
{
printf("accept失败");
continue;
}
if (CreateIoCompletionPort((HANDLE)newClient->socket, Handle_Iocp, (ULONG_PTR)newClient, 0) == NULL)
{
printf("CreateIoCompletionPort失败");
continue;
}
ReadData(newClient, 0, READ_BUFFER_SIZE, 0);
}
printf("监听线程已退出\n");
return 0;
}
//工作者线程
DWORD __stdcall WorkThreadFunction(LPVOID Param)
{
ClientStruct* pClientStruct;
OVERLAPPED* pOverlap;
WNSOperation* pIOCPOperation;
DWORD berByte;
printf("工作线程已启动\n");
while(true)
{
//线程执行到这里时会被挂起,等待一个异步操作完成;
if(GetQueuedCompletionStatus(Handle_Iocp, &berByte, (LPDWORD)&pClientStruct, (LPOVERLAPPED *)&pOverlap, INFINITE) == ERROR_SUCCESS)
{
printf("GetQueuedCompletionStatus >>> %d\n", WSAGetLastError());
continue;
}
if (berByte <= 0)
{
delete pClientStruct;
continue;
}
if (pClientStruct == NULL)
{
printf("pClient==NULL\n");
continue;
}
pIOCPOperation = (WNSOperation*)pOverlap; //这是一个WNSOperation对象,通过OpCode判断是读取操作还是发送操作;
switch(pIOCPOperation->OpCode)
{
case 'r':
{
//我在buf最后插入一个'\0'字符打印一下;
pClientStruct->pReadOperation.wbuf.buf[berByte] = '\0';
printf("%s\n", pClientStruct->pReadOperation.wbuf.buf);
//调用CreateSendBuff创建一个用于发送的WNSOperation;
WNSOperation* sendBuffer1 = CreateSendBuff(berByte);
//拷贝接收到的数据在发送回去;
memcpy(sendBuffer1->wbuf.buf, pClientStruct->pReadOperation.wbuf.buf, berByte);
sendBuffer1->wbuf.len = berByte;
//sendBuffer1加入到这个客户端的发送队列中;
pClientStruct->AddSendBuffer(sendBuffer1);
{
//为了测试发送队列,我在追加一组数据;
WNSOperation* sendBuffer2 = CreateSendBuff(10);
memcpy(sendBuffer2->wbuf.buf, "1234567890", 10);
sendBuffer2->wbuf.len = 7;//最终发送的数据长度是在这里设定的;
pClientStruct->AddSendBuffer(sendBuffer2);
}
//使这个客户端进入发送状态;
SendData(pClientStruct);
//同时进入读取状态;
ReadData(pClientStruct, 0, READ_BUFFER_SIZE, 0);
}
break;
case 'w':
//发送完成后,这个WNSOperation对象就可以被删除了;
delete[] pIOCPOperation;
pIOCPOperation = NULL;
//重置客户端发送状态;
pClientStruct->ResetSendSignal();
//再次进入发送状态,如果发送队列中有未发送的BUF,则继续发送;
SendData(pClientStruct);
break;
default:
continue;
}
}
printf("工作线程已退出\n");
return 0;
};
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsd;
if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
{
printf("初始化失败");
getchar();
return 0;
}
socket_listen = socket(AF_INET, SOCK_STREAM, 0);
if (socket_listen == INVALID_SOCKET)
{
printf("初始化socket失败");
getchar();
return 0;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9090);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
if (bind(socket_listen, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
{
printf("bind失败");
getchar();
return 0;
}
if (listen(socket_listen, 0) == SOCKET_ERROR)
{
printf("listen失败");
getchar();
return 0;
}
if ((Handle_Iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0)) == NULL)
{
printf("创建完成端口失败");
getchar();
return 0;
}
//启动监听线程;
acceptThread = CreateThread(NULL, 0,AcceptThreadFunction, NULL, 0, NULL);
//启动工作线程;
for(int i=0;i<WORK_THREAD_COUNT;i++)
{
workThread[i] = CreateThread(NULL, 0,WorkThreadFunction, NULL, 0, NULL);
}
//主线程歇了吧
while(true)
{
getchar();
}
return 0;
}