对listen第二个参数的理解,及示例(windows版)

时间:2024-04-06 16:58:53

本文受了https://blog.csdn.net/yangbodong22011/article/details/60399728 的启发

对tcp的三次握手,我也不太明白。但是阐述listen函数的第二个参数的意义,可以用一个简单的例子来解释,用不到三次握手的知识:

长话短说。tcp服务器好比是一个机关单位,单位的领导好比是accept函数,只有accept函数返回了值,才算是客户端与服务器建立了联系。但是领导下面还有一个“传达室”,当领导不在时,传达室负责受理客户端发来的请求。当领导回来后,再把请求上报给领导,由领导盖章确认。listen函数的第二个参数-backlog,相当于是传达室一次最多能受理的申请数目。假如传达室受理的申请数目已经达到backlog的取值,那么再来到传达室的申请会被直接退回。对于客户端来说,一旦申请被受理,客户端程序就认为已经建立了连接(但是真实情况是,领导可能还没回家,只是申请被接收而已,连接建立只是一种假象,领导回家才算正式办理)。

看下面的代码:

#include "stdafx.h"
#include "TCPServer.h"


TCPServer *		g_pTCPServer = NULL;

UINT uiListenThread(LPVOID param)
{
	// 一直等待客户端
	TCPServer * pTCPServer = TCPServer::pGetInstance();
	pTCPServer->m_bThrdRunning = true;
	if(pTCPServer)
	{
		while (true)
		{
			pTCPServer->vListen();
			Sleep(60000);
		}
	}

	pTCPServer->m_bThrdRunning = false;
	return 0;
}

TCPServer::TCPServer(char * pIP, int iPort)
{
	m_pThrd = NULL;
	m_bThrdRunning = false; 
	m_iClientSock = -1;
	m_iLocalSock = -1;
	WSAData wsa;
	m_iRetWSA = WSAStartup(MAKEWORD(2,2), &wsa);
	g_pTCPServer = this;

	m_iLocalSock = iInit(pIP, iPort);
	if(m_iLocalSock > 0)
	{
		 m_pThrd = AfxBeginThread(uiListenThread,NULL);
	}
}

TCPServer::~TCPServer()
{
	if(m_bThrdRunning)
	{
		TerminateThread(m_pThrd, 0);
		m_bThrdRunning = false;
	}

	if(m_iClientSock > 0)
	{
		closesocket(m_iClientSock);
		m_iClientSock = -1;
	}

	if(m_iLocalSock > 0)
	{
		closesocket(m_iLocalSock);
		m_iLocalSock = -1;
	}

	WSACleanup();
}

int TCPServer::iInit(char * pIP, int iPort)
{
	int iSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int iRet = -1;
	if(iSock > 0)
	{
		struct sockaddr_in local;
		local.sin_family = AF_INET;
		local.sin_port = htons(iPort);
		local.sin_addr.s_addr = inet_addr(pIP);

		int iLen = sizeof(local);

		if(bind(iSock, (struct sockaddr *)&local, iLen) >= 0)
		{
			if(listen(iSock, 1) == 0)
			{
				iRet = iSock;
			}
		}
	}

	return iSock;
}

void TCPServer::vListen(void)
{
	//addrClient 用于保存客户端的IP以及端口号
	struct sockaddr_in addrClient;
	int iLen = sizeof(addrClient);

	m_iClientSock = accept(m_iLocalSock, (struct sockaddr *)&addrClient, &iLen);
}

TCPServer * TCPServer::pGetInstance(void)
{
	return g_pTCPServer;
}

int TCPServer::iSend(char * pData, int iLen)
{
	if(m_iClientSock > 0 && m_iLocalSock > 0)
	{
		return send(m_iClientSock, pData, iLen, 0);
	}
	else
	{
		return 0;
	}
}

这里要注意两处:一是if(listen(iSock,1) == 0),1代表传达室最多受理一个申请。另一处是sleep(60000),就是说,每60秒调用一次accept函数(领导每60秒来一趟单位)。下面看下程序实际运行情况:

1)启动服务端程序,用网络助手建立一个客户端套接字,并与服务端相连;

2)accept调用后,要过60秒才能下一次调用(领导不在家);

3)在下一个60秒到来前,网络助手再建立一个套接字,与服务端相连,也连上了(这就是前面说的假象,其实只是被传达受理)

4)在下一个60秒到来之前,网络助手建立第三个套接字,与服务端相连,连不上(因为传达已经受理了一个申请尚未处理,而且(listen(iSock,1) == 0)),传达最多受理一个;

对listen第二个参数的理解,及示例(windows版)

5)60秒到来,触发accept函数,第三个客户端套接字才算真的连上(领导盖章了)

对listen第二个参数的理解,及示例(windows版)

6)再次尝试客户端连接,成功(传达又能受理了。其实也是假象,原因同上)

对listen第二个参数的理解,及示例(windows版)

7)60秒后又一次在accept处触发断点,第三个套接字才算真正连上。