Windows下IOCP踩过的一些坑

前段时间在搞win下面的IOCP服务器时发现了一些问题,有些也是折磨了好久才慢慢理解的,今天将这些踩过的坑记录下来,避免以后遇到同样的问题,又要折磨好久。

IOCP目前是性能最好的网络模型,他与其他网络模型的的区别和优缺点就不做赘述了,这些网上随便一搜就有大量的文章去解释,主要说下IOCP的优缺点,一个IOCP对象,在操作系统中可以关联多个socket和(或)文件控制端。它主要是在内部封装了LIFO原则的请求队列、FIFO原则的完成队列、多线程处理,同样IOCP也是唯一个不需要考虑安全属性的Windows对象,因为IO完成端口在设计时就只能在一个进程中运行。IOCP也是异步模型,我们只能将IO操作的事件投递到IOCP上,实际上socket的send和recv操作均是由IOCP完成的,存在的问题就是,我们没办法控制send和recv的成功。还有一个缺点就是只能在Windows环境使用,这样就会存在一点的局限性。

IOCP模型主要是利用了重叠I/O技术,最难的部分也是在重叠I/O的管理上面,因此我们将这块进行了简便处理,将结构体修改成类,这样会比较方便的进行线性管理。

首先我们创建一个重叠I/O结构的类:

#define MAX_BUFFER 8192	//buffer的最大长度,一般的HTTP POST数据用4个页字段完全够用,不够用的话按情况而定

class CCPerIOData
{
public:
	CCPerIOData();
	~CCPerIOData();

	void ResetIO();
public:
	
	//下面是重叠I/O的内部结构
	
	WSAOVERLAPPED	m_Overlappend;
	SOCKET			m_AcceptSocket;
	WSABUF			m_wsaBuf;
	DWORD			m_opType;
	char            m_szBuffer[MAX_BUFFER];
	
	//定义重叠I/O的工作类型,方便操作
	typedef enum
	{
		OP_ACCEPT,
		OP_SEND,
		OP_RECV,
		OP_NULL
	}OPERATION_TYPE;
};

为了线性管理重叠I/O结构,我们需要创建一个socket管理类。

#include "periodata.h"

using namespace std;

class SocketHandle
{
public:

	SocketHandle();
	~SocketHandle();

public:

	SOCKET				m_Socket;			//客户端连接的socket
	SOCKADDR_IN			m_ClientAddr;
	volatile time_t		m_nTimer;			//socket最后一次活跃的时间,设计这个字段的主要目的是socket的关闭可能不及时,HTTP请求短连接情况下可能会存在端口不够用的情况,我们需要及时的关闭这些超过时间无响应的socket
	vector<CCPerIOData*>	m_VSocketIoData;

public:
	
	//从队列中删除一个重叠I/O
	void 		RemoveIOData(CCPerIOData* pPerIoData);
	CPerIOData* GetNewIOData();

};

SocketHandle::SocketHandle()
{
	m_Socket = INVALID_SOCKET;
	ZeroMemory(&m_ClientAddr, sizeof(m_ClientAddr));
	m_nTimer = time(0);
}
SocketHandle::~SocketHandle()
{
	//销毁创建的一些指针,注意,m_VSocketIoData这个vector里面存放的全部是CPerIOData类型的指针,因此需要全部清理,清理方法:
	
	//遍历释放

	std::vector<CPerIOData*>::iterator iter = m_VSocketIoData.begin();
	for(; iter != m_VSocketIoData.end(); iter++)
	{
		//delete ...
	}
	m_VSocketIoData.clear();
}

CPerIOData* SocketHandle::GetNewIOData()
{
	CPerIOData* p = new CPerIOData;
	m_VSocketIoData.push_back(p);	//每次重新创建一个重叠I/O,加入vector
	return p;
}

void SocketHandle::RemoveIOData(CPerIOData* pPerIoData)
{
	if(pPerIoData == NULL || m_VSocketIoData.empty())
	{
		return;
	}
	vector<CPerIOData*>::iterator iter = m_VSocketIoData.begin();
	for(; iter != m_VSocketIoData.end(); iter++)
	{
		if(pPerIoData == *iter)
		{
			// do something ...
			break;
		}
	}
}

接下来我们就要准备进入主题:

要使用完成端口,首先需要注意以下几项:
1、创建完成端口
2、根据系统性能创建工作线程
3、创建一个监听套接字,并将监听套接字和完成端口绑定
4、创建n个供客户端连接的socket
5、加载完成端口的函数

为什么需要加载完成端口的函数呢,因为IOCP在设计时就已经确定了他不能直接在WindowsAPI下使用,并且由于winsock的版本原因,需要加载他的函数才能够正常的使用。

先看一个简单的头文件定义:

上面第五点一定要注意,由于winsock的版本问题,完成端口的函数不能够直接使

#include "SocketHandle.h"
class CHttpSerVer
{
public:
	
	CHttpSerVer();
	virtual ~CHttpSerVer();
	bool Start();
	void Stop();
	
private:
	static DWORD WINAPI WorkThread(LPVOID lpParam);			//工作线程,用来不断的循环等待IOCP的状态返回,并处理IOCP的请求
	static DWORD WINAPI CheckClientThread(LPVOID lpParam);  //客户端检测线程,用来检测3秒内没有活跃的socket,并释放这些socket资源

	//程序结束时的一些资源回收函数
	void ClearClientList();
	void DeInitialize();
	void CheckClientList();

	//接收的客户端管理函数
	void AddToClientList(SocketHandle* pSocketHandle);
	void RemoveSocketHandle(SocketHandle* pSocketHandle);
	
	//初始化IOCP和一些错误的处理
	bool AssociateWithIOCP(SocketHandle* pSocketHandle);
	bool HandleError(SocketHandle* pSocketHandle, DWORD dwError);

	//IOCP工作流程的体现
	bool PostAcceptEx(CPerIOData* pAcceptIoContext);
	bool DoAcceptEx(CPerIOData* pIoContext);
	void PostSend(SocketHandle* pSocketHandle, CPerIOData* pPerIOData);
	bool DoSend(SocketHandle* pSocketHandle, CPerIOData* pPerIOData);
	bool PostRecv(SocketHandle* pSocketHandle, CPerIOData* pPerIOData);
	bool DoRecv(SocketHandle* pSocketHandle, CPerIOData* pPerIOData);

protected:
	static bool m_bIsRunning;
	CMutex		m_ClientMutex; 		
	CMutex		m_StartMutex;

private:
	
	int m_nPort;					//监听端口
	int m_nThreadNum;				//工作线程数目
	int m_nSocketNum;				//设置每次能够监听的最大的客户端数量
	int m_nCheckThreadRunTimeOut;	//检测是否无响应的超时时间

	HANDLE*					m_pWorkerThreads;		//工作线程
	HANDLE					m_pCheckClientThread;	//检测客户端是否在设置的时间内无响应的线程
	HANDLE					m_hIOCompletionPort;	//IOCP完成端口
	HANDLE					m_hShutdownEvent;		//程序结束的信号
	SocketHandle*			m_pSocketHandle;		//定义的监听socket的管理
	vector<SocketHandle*>	m_VClientSocket;		//管理已经连接的HTTP请求的客户端

	//定义的IOCP的函数指针
	LPFN_ACCEPTEX				m_pFnAcceptThread;
	LPFN_GETACCEPTEXSOCKADDRS	m_pFnGetAcceptExSocketAddr;
};
bool CHttpSerVer::Start()
{
	//为了保证在有多线程调用时,只能启动一个监听线程
	CSingleLock lock(&m_StartMutex);
	if(!lock.Lock(1000))
		return;
	m_bIsRunning = true;	
	
	
	m_nPort = atol(readconfig("监听端口", "80"));
	m_nSocketNum = atol(readconfig("最大连接数", "1000"));		//设置同时能够连接的最大的客户端数

	m_pSocketHandle = new SocketHandle();	
	m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);	//退出事件信号
	
	/**创建完成端口**/
	m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	
	/**根据系统的性能创建工作线程, 完成端口的最大性能的工作线程数为CPU核心数的2倍**/
	SYSTEM_INFO sysTemInfo;
	GetSystemInfo(&sysTemInfo);
	m_nThreadNum = sysTemInfo.dwNumberOfProcessors * 2;
	m_pWorkerThreads = new HANDLE[m_nThreadNum];
	DWORD nThreadID;
	for(int nIndex = 0; nIndex < m_nThreadNum; nIndex++)
	{		
		m_pWorkerThreads[nIndex] = ::CreateThread(0, 0, WorkThread, (void *)this, 0, &nThreadID);
	}
	
	/**创建用于检测客户端连接的线程**/
	m_pCheckClientThread = ::CreateThread(0, 0, CheckClientThread, (void *)this, 0, &nThreadID);
	
	
	/**创建监听套接字**/
	m_pSocketHandle->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	
	/**将监听套接字和完成端口绑定**/
	CreateIoCompletionPort((HANDLE)m_pSocketHandle->m_Socket, m_hIOCompletionPort, (DWORD)m_pSocketHandle, 0);
	
	struct sockaddr_in SerVerAddrs;
	memset(&SerVerAddrs, 0, sizeof(SerVerAddrs));
	SerVerAddrs.sin_addr.S_un.S_addr = INADDR_ANY;
	SerVerAddrs.sin_family = AF_INET;
	SerVerAddrs.sin_port = htons(m_nPort);
	
	::bind(m_pSocketHandle->m_Socket, (sockaddr*)&SerVerAddrs, sizeof(SerVerAddrs));
	
	::listen(m_pSocketHandle->m_Socket, SOMAXCONN);
	
	//致此,监听socket已经在监听了
	
	/**由于IOCP的一些特殊的原因,我们必须首先加载IOCP的函数才能够使用IOCP**/
	DWORD dwBytes = 0;
	GUID guidAcceptEx = WSAID_ACCEPTEX;
	GUID guidGetAcceptExSocketAddr = WSAID_GETACCEPTEXSOCKADDRS;
	WSAIoctl(m_pSocketHandle->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, sizeof(guidAcceptEx), &m_pFnAcceptThread, sizeof(m_pFnAcceptThread),&dwBytes,NULL,NULL);
	WSAIoctl(m_pSocketHandle->m_Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidGetAcceptExSocketAddr, sizeof(guidGetAcceptExSocketAddr), &m_pFnGetAcceptExSocketAddr,sizeof(m_pFnGetAcceptExSocketAddr), &dwBytes, NULL, NULL)
	
	/**IOCP最大的好处是我们能够提前准备n个socket供客户端连接,这样可减少每个socket连接时创建socket的内存消耗**/
	for(int nIndex = 0; nIndex < m_nSocketNum; nIndex++)
	{
		CPerIOData* pListenIOData = m_pSocketHandle->GetNewIOData();
		//创建完成之后将这些socket投递给IOCP的AcceptEx函数等到连接
		if(PostAcceptEx(pListenIOData) == false)
		{
			//投递失败时则要对这些socket做资源回收
			//do something ...
		}
	}	
}

下面看看怎样提前将1000个套接字提前准备:

bool CHttpSerVer::PostAcceptEx(CPerIOData* pPerIOData)
{	
	ASSERT( INVALID_SOCKET! = pPerIOData->m_AcceptSocket);
	
	pPerIOData->m_AcceptSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if( INVALID_SOCKET == pPerIOData->m_AcceptSocket)  
	{  
		//do something ...
		return false;
	}

	//设置这个socket的属性,使其能够重复利用
	int nReuseAddr = 1;
	setsockopt(pPerIOData->m_AcceptSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&nReuseAddr, sizeof(nReuseAddr));
	
	pPerIOData->ResetIO();	//这一步主要是清理这个重叠I/O中的buffer缓存
	DWORD dwBytes = 0;  
	pPerIOData->m_opType = CPerIOData::OP_ACCEPT;  
	
	if(FALSE == m_pFnAcceptThread(m_pSocketHandle->m_Socket, pPerIOData->m_AcceptSocket, pPerIOData->m_wsaBuf.buf, 0, sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, &dwBytes, &pPerIOData->m_Overlappend))  
	{  
		if(WSA_IO_PENDING != WSAGetLastError())  
		{  
			//说明投递失败了
			return false;  
		}  
	} 
	return true;
}

这样我们的端口监听就已经完成了,并且已经提前准备了1000socket套接字准备客户端连接,这样我们的效率会有很大的提升,接下来最重要的还是工作线程的处理:

DWORD WINAPI CHttpSerVer::WorkThread(LPVOID pParam)
{
	CHttpSerVer* pHttpServer = (CHttpSerVer*)pParam;
	SocketHandle*	pSocketHandle = NULL;
	DWORD			dwIOSize = 0;
	OVERLAPPED*		pOverlapped =NULL;

	//循环不断的等待退出线程事件的信号,WaitForSingleObject函数的功能是在等待信号的触发,如果在设定的超时时间内指定的信号有触发,则立即返回,如果在超时时间内内有触发,则等待时间超时后立即返回,具体的函数解释可以看下Windows的API。
	while (WAIT_OBJECT_0 != WaitForSingleObject(pHttpServer->m_hShutdownEvent, 0))	
	{	
		/**获取IOCP的状态**/
		BOOL bRet = GetQueuedCompletionStatus(pHttpServer->m_hIOCompletionPort, &dwIOSize, (PULONG_PTR)&pSocketHandle, &pOverlapped, WSA_INFINITE);
		
		if ( EXIT_CODE == (DWORD)pSocketHandle)
		{
			break;		//如果收到的是退出请求,则停止循环,退出完成端口的监听
		}
		if(bRet == FALSE)	//返回false,则说明接收失败了,处理这个错误后继续循环
		{
			DWORD dwError = WSAGetLastError();
			pHttpServer->HandleError(pSocketHandle, dwError);
			continue;	
		}
		else
		{
			/**下面这行代码是将完成端口接收到的数据读出来**/
			CPerIOData* pPerIOData = CONTAINING_RECORD(pOverlapped, CPerIOData, m_Overlappend);
			if((0 == dwIOSize) && CPerIOData::OP_ACCEPT != pPerIOData->m_opType)  
			{  
				//满足该条件的时,说明完成端口接收到的是客户端退出的请求,需要将维护的客户端列表里面的该套接字做资源回收
				pHttpServer->RemoveSocketHandle(pSocketHandle);	
				continue;  
			}
			else
			{
				pSocketHandle->m_nTimer = time(0);	//每次有新数据来时,更新活跃时间
				switch(pPerIOData->m_opType)
				{
					case CPerIOData::OP_ACCEPT:
					{
						pHttpServer->DoAcceptEx(pPerIOData);	//说明有新的客户端连接
						break;
					}
					case CPerIOData::OP_SEND:
					{
						//do something ...
						//send
						//recv	//数据发送完后投递recv,这样就能保证下一次数据过来时能够直接读取到
						break;
					}
					case CPerIOData::OP_RECV:
					{
						//do something ...
						break;
					}	
					default:
					{
						break;
					}
				}
			}
		}
	}
	return 0;
}

接下来我们看下有新客户端连接时怎么处理:

bool CHttpSerVer::DoAcceptEx(CPerIOData* pPerIOData)
{
	CString strTemp;
	SOCKADDR_IN *addrClient = NULL;
	SOCKADDR_IN	*addrLocal = NULL;
	int nClientLen = sizeof(SOCKADDR_IN);
	int nLocalLen = sizeof(SOCKADDR_IN);

	//该函数能够将客户端、本地的相关信息获取到,同时也能够接收到客户端第一次发送的数据
	//第二个参数设置为0, 则直接返回
	//如果第二个参数设置为 pPerIOData->m_wsaBuf.buf - ((sizeof(SOCKADDR_IN) + 16) * 2), 则会在等到接收到客户端第一次发送的数据后才能返回
	this->m_pFnGetAcceptExSocketAddr(pPerIOData->m_wsaBuf.buf,
		0,
		sizeof(SOCKADDR_IN) + 16,
		sizeof(SOCKADDR_IN) + 16,
		(LPSOCKADDR*)&addrLocal,
		&nLocalLen,
		(LPSOCKADDR*)&addrClient,
		&nClientLen);
	
	//将监听的socket的数据复制出来,然后把这个socket继续投递到下一个监听
	SocketHandle* pSocketHandle = new SocketHandle();
 	pSocketHandle->m_Socket = pPerIOData->m_AcceptSocket;
	
	int nReuseAddr = 1;
	setsockopt(pSocketHandle->m_Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&nReuseAddr, sizeof(nReuseAddr));

	if(!AssociateWithIOCP(pSocketHandle))
	{
		DELETE_PT(pSocketHandle);
		return false;
	}
	
	//对复制之后的socket投递recv,因为刚建立连接时没有数据传过来,需要投递给IOCP去接收数据
	CPerIOData* pConIOData = pSocketHandle->GetNewIOData();
	if(!PostRecv(pSocketHandle, pConIOData))
	{
		//投递失败,则需要更新客户端列表
		//do something ...
		return false;
	}
	
	//为了方便管理,将这个客户端的信息添加到列表
	this->AddToClientList(pSocketHandle);

	//然后将listenSocket的I/O数据清理一下,准备投递到下一次客户端的连接,这样做的好处是,我们一直会有1000个socket准备接收新的客户端连接
	pPerIOData->ResetIO();	
	PostAcceptEx(pPerIOData);

	return true;
}

下面看下客户端的检测是怎么实现的,为什么需要检测呢,是因为http请求是短连接,如果发现客户端列表里面有超过3秒钟时间没有活跃的客户端,我们默认这个客户端已经被关闭了,只是在关闭的过程中由于一些原因,导致没有关闭完成,因此,我们给IOCP投递一个recv事件,让IOCP去关闭这个链接。

DWORD WINAPI CHttpSerVer::CheckClientThread(LPVOID pParam)
{
	CHttpSerVer* pHttpServer = (CHttpSerVer*)pParam;
	//设置该线程1s执行一次,如果m_hShutdownEvent有信号,则立即结束线程循环 pHttpServer->m_nCheckThreadRunTimeOut = 1000
	while (WAIT_OBJECT_0 != WaitForSingleObject(pHttpServer->m_hShutdownEvent, pHttpServer->m_nCheckThreadRunTimeOut))
	{
		pHttpServer->CheckClientList();
	}
	return 0;
}

void CHttpSerVer::CheckClientList()
{
	CSingleLock lock(&m_ClientMutex);
	if(!lock.Lock(1000))
		return;
	if(m_VClientSocket.empty())
		return;
	vector<SocketHandle*>::iterator iter = m_VClientSocket.begin();
	for(; iter != m_VClientSocket.end(); iter++)
	{
		if((time(0) - (*iter)->m_nTimer) > 3)
		{
			//如果某一个socket的活跃时间已经超过了我们设置的超时时间,则将该socket投递给IOCP,投递类型是RECV,这样做的好处是,我们并没有直接去关闭这些socket,而是让IOCP去释放
			CPerIOData* pPerIOData = (*iter)->GetNewIOData();
			pPerIOData->m_opType = CPerIOData::OP_RECV;
			PostQueuedCompletionStatus(m_hIOCompletionPort, 0, (DWORD)(*iter), (LPOVERLAPPED)pPerIOData);
		}
	}
	return;
}

下面是有客户端数据传过来时的操作,接收数据,然后投递一个异步的发送事件

bool CHttpSerVer::DoRecv(SocketHandle* pSocketHandle, CPerIOData* pPerIOData)
{
	if(pSocketHandle == NULL || pPerIOData == NULL)
	{
		return false;
	}
	char szResponse[70] = {0};

	//该函数执行,说明已经读到数据了,我们按照自己的要求去解析数据,如果发现接收到的数据为空,则将这个socket投递给IOCP,投递类型为RECV,目的是为了关闭这个socket
	if(!ParseRequest(pPerIOData->m_wsaBuf.buf, szResponse))
	{
		pPerIOData->m_opType = CPerIOData::OP_RECV;
		PostQueuedCompletionStatus(m_hIOCompletionPort, 0, (DWORD)pSocketHandle, (LPOVERLAPPED)pPerIOData);
		return false;
	}

	//解析完数据后,投递一个WSASend,给客户端一个回应
	pPerIOData->m_wsaBuf.buf = szResponse;
	pPerIOData->m_opType = CPerIOData::OP_SEND;
	pPerIOData->m_wsaBuf.len = strlen(szResponse) + 1;
	this->PostSend(pSocketHandle, pPerIOData);
	return true;
}

void CHttpSerVer::PostSend(SocketHandle* pSocketHandle, CPerIOData* pPerIOData)
{
	DWORD dwFlags = 0;
	DWORD dwIOSize = 0;
	pPerIOData->ResetIO();
	pPerIOData->m_opType = CPerIOData::OP_SEND;
	if(SOCKET_ERROR == WSASend(pSocketHandle->m_Socket, &pPerIOData->m_wsaBuf, 1, &dwIOSize, dwFlags, &pPerIOData->m_Overlappend, NULL))
	{
		//说明投递WASSend失败了
		//do something ...
	}
}

下面看下数据的解析,只实现了POST数据的解析:

bool CHttpSerVer::ParseRequest(const char* szRequest, char* szResponse)
{
	char szResponseHeader[70] = {0};
	CString strRequest(szRequest);

	CString strMethod = strRequest.Left(4);
	if(strMethod != "POST")
	{
		sprintf(szResponseHeader, "HTTP/1.0 404 ERROR\r\nContent-Length: 0\r\nConnection:close\r\n\r\n");
		strcpy(szResponse, szResponseHeader);
		return false;
	}
	int nConLenStart = strRequest.Find("Content-Length: ");
	int nConLenEnd = strRequest.Find("\r\n", nConLenStart);
	int nConLenPos = nConLenStart + strlen("Content-Length: ");
	
	CString strDataLen = strRequest.Mid(nConLenPos, nConLenEnd - nConLenPos);
	
	long nDataLen = atoi(strDataLen);
	
	int nDataPos = strRequest.Find("\r\n\r\n") + strlen("\r\n\r\n");
	CString strData = strRequest.Mid(nDataPos, nDataLen);
	if(strData.IsEmpty())
	{
		sprintf(szResponseHeader, "HTTP/1.0 200 OK\r\nContent-Length: 9\r\nConnection:close\r\n\r\nmsg empty");
		strcpy(szResponse, szResponseHeader);
		return true;
	}
	//下面是接收到的数据调用自己的业务函数,该业务函数目前没有实现
	if(this is you function (strData.GetBuffer(0), nDataLen))
	{
		sprintf(szResponseHeader, "HTTP/1.0 200 OK\r\nContent-Length: 0\r\nConnection:close\r\n\r\n"); 
		strcpy(szResponse, szResponseHeader);
	}
	else
	{
		sprintf(szResponseHeader, "HTTP/1.0 404 ERROR\r\nContent-Length: 0\r\nConnection:close\r\n\r\n");
		strcpy(szResponse, szResponseHeader);
	}
	return true;
}

在做这个模型的是中间遇到了一些问题,主要是服务端产生大量的CLOSE_WAIT状态,这样会极大的影响性能,或者在这些socket没有在完全关闭的情况下,会造成系统的崩溃。下面我们分析下产生这个问题的原因,看下下面的这张图基本就能够明白这种状态是怎么产生的。在这里插入图片描述
产生这个问题主要是因为客户端关闭连接,服务器被关闭连接,而服务端并没有发送FIN包给客户端(可能是有一些send或者recv的操作没有完成),这时候服务端就会处于CLOS_WAIT状态,而客户端处于FIN_WAIT_1状态。

问题产生的原因就是代码本身的问题:需要仔细检查代码,该关闭socket的时候就要主动去关闭,因此在本模块中才会有检测不活跃的socket线程出现。

在这个状态下时:系统会在等待时间超过2MSL(报文最大生存时间)后自动回收,但系统的设置这个时间一般是30min,因此,需要修改系统时间:

Linux系统下:
	vim /etc/sysctl.conf

	net.ipv4.tcp_tw_reuse=1
	net.ipv4.tcp_tw_recycle=0
	net.ipv4.tcp_fin_timeout=30
	net.ipv4.tcp_max_tw_buckets=80000
	net.ipv4.tcp_timestamps=1
	net.ipv4.ip_local_port_range=10000 65535
	
	修改完后执行命令 sysctl -p 使其生效。
window系统下:
	在HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters,添加名为TcpTimedWaitDelay的
	DWORD键,设置为30,以缩短TIME_WAIT的等待时间 

同时在做这个时候出现了一个很奇怪的现象,在Windows下面资源管理器中可以发现,每当有一个HTTP请求结束后,程序的句柄数会增加2,只增不减,这样的后果是一定量的HTTP请求后,系统可用的句柄数会消耗完,程序将无法正常工作,后来使用windbg调试,发现在代码中使用了一个无用的变量指针,这个指针是继承的,并且每一个HTTP请求都会new一个这个指针,调用结束后句柄数增加2,将这个指针采用全局对象的方式,所有HTTP请求共用同一个对象,句柄增加的问题解决了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值