对listen第二个参数的理解,及示例(windows版)
本文受了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)),传达最多受理一个;
5)60秒到来,触发accept函数,第三个客户端套接字才算真的连上(领导盖章了)
6)再次尝试客户端连接,成功(传达又能受理了。其实也是假象,原因同上)
7)60秒后又一次在accept处触发断点,第三个套接字才算真正连上。