实在搞不懂老方写的是什么东西了!
在Server端:
启动Server,创建socket变量,socket(AF_INIT, SOCK_STREAM, IPPROTO_IP);三个参数设置:地址规范,socket类型,协议类型。
接下来要bind(SOCKET s, const atruct sockaddr FAR *name, int namelen);三个参数设置:空闲的socket, 指派socket地址, name参数的长度值。对于第二个参数name机构体:需要提前设置其参数:地址规范:name.sin_family = AF_INIT, 端口:name.sin_port = htons(1234), 网络地址:name.sin_addr.S_un.S_addr = inet_addr(“127.0. 0.1” );
但对于name初定义却声明为:SOCKADDR_IN stockaddrin = {0};
所以在bind 时对stockaddrin 需进行强制类型转换即:(sockaddr*)&stockaddrin.
所以整体为:
Bind(m_sock_server,(sockaddr*)&stockaddrin, sizeof(SOCKADDR_IN));
对于错误处理,可用WSAGetLastError()获得当前的错误,如果发生的话!
String CsErr ;
CsErr.Format(_T(“%d”), WSAGetLastError());
将错误标示放入CsErr中显示出来:AfxMssageBox(“Bind Error !”+CSErr);
接下来进行listen(socket s, int backlog);
第一个参数:当前的套接字,backlog默认设置为SOMAXCONN,看MSDN说的意思应该是随机分配其最大值(原文:There is no standard provision to find out the actual backlog value.)
同样的对于listen 失败也可以用WSAGetLastError()来返回错误!
设置好了这些就要创建一个线程做些事情了,
CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);此为函数原型。
第一个参数设置为线程的属性,因为Must be NULL,所以可以暂且将其抛开不论。
第二参数也可以Ignored(不理睬):MSDN原文:Ignored.TheDefault stack size for a thread is determined by the linker setting /STACK.
接下来看第三个参数:语义就是线程开始地址,查MSDN需要我们提供出一个函数的地址,即为:DWORD WINAPI ThreadProc(
LPVOID lpParameter);既然知道要做一个函数出来,现在就继续往后看。
第四个参数,可以设置一个32位的指针指向当前线程(MSDN原文:Long pointer to a single 32-bit parameter value passed to eht thread),看 孙鑫 老师的教程说:我们可以通过这个参数给创建的新县城传递参。该参数提供了一种将初始化值传递给线程函数的手段。这个参数的值既可以是一个数值,也可以是一个指向其他信息的指针。回过头再看 方 老师的代码将此值设为:this,似乎是要在当前线程中使用当前CserverDlg类的某些信息,但看ThreadProc_Accept函数中并未用到,而在 孙鑫 老师给出的示例中将其设为NULL,此处暂不深究了吧~!
第5个参数:设置当前创建的线程的控制标记:0:表示创建后立即执行;CREATE_SUSPENDED :表示暂不执行直到ResumeThread (function is called)被调用.
第6个参数:是一个返回值,指向一个变量,用来接收线程ID。
函数成功则返回该线程的句柄,失败则返回NULL。
DWORD dwThread = 0;
HANDLE hAcceptThread = CreatThread(NULL, 0, ThreadProc_Accept, this, 0, &dwTheadId);
好了现在要回过头来看第三个参数函数的定义:
DWORD WINAPI ThreadProc_Accept(LPVOID lpParameter);
该线程用于接收客户端的连接,lpParameter参数指向Thread data(MSDN原文:Receives the thread data passed to the function using the lpParameter parameter of the CreateThread or CreateRemoteThread function.).
Ioctlsocket(SOCKET socket, long cmd, u_long FAR *argp);该函数用于设置套接字的I/O模式。
第二个参数MSDN提供出来的可选项有:FIONBIO, FIONREAD , SIOCATMARK.这个三个参数的MSDN解释看的我好吃力,按照老方的说法使用FIONBIO,对于该程序已经够了。
或许我该到网上查下这三个参数的中文释义。做个标记#######设个?
第三个参数Pointer to a parameter for cmd.此处也颇为迷惑为何定义了一个u_long uAsync = 1;同时:ioctlsocket(p->m_sock_server, FIONBIO, &p->uAsync);
接下来就是接收客户端的连接:
SOCKADDR_IN Client_stockaddrin = {0};
int nLenStockAddr = sizeof(SOCKADDR_IN);
SOCKET socket_Client = 0;
SOCKET socket_Client = accept(p->m_sock_server, (SOCKADDR*)&Client_stockaddrin, &nLenStockAddr);
剖析下accept:第一个参数(s)是套接字描述符,该套接字已经通过listen函数将其设置为监听状态;第二个参数(addr)是指向一个缓冲区的指针,该缓冲区用来接收连接实体的地址,也就是当前当客户端发起连接,服务器端接受这个连接时,保存发起连接的这个库护短的IP地址信息和端口信息;第三个参数(addrlen)也是一个返回值,指向一个整型的指针,返回包含地址信息的长度。
class RecvParam
{
public:
CServerDlg* m_this;
SOCKET m_socket_Client;
in_addr m_addrin;
};
if ( INVALID_SOCKET == socket_Client )
{
//异步处理
if ( p->uAsync == 1)
{
Sleep(50);
continue;
}
else
{
AfxMessageBox("Accept error!");
break;
}
}
else
{
RecvParam* NewParam = new RecvParam;
NewParam->m_this = p;
NewParam->m_socket_Client = socket_Client;
NewParam->m_addrin = Client_stockaddrin.sin_addr;
DWORD dwTheadId = 0 ;
HANDLE hRecvThread = 0;
if ( !(hRecvThread = CreateThread(NULL,0,
ThreadProc_Recv, NewParam,0,&dwTheadId)))
{
AfxMessageBox("Create Recv Thread!");
break;
}
//Add List Node
NodeClient node;
node.Socket = socket_Client;
node.Inaddr = Client_stockaddrin.sin_addr;
node.Recvthreadhandle = hRecvThread;
node.recvparam = NewParam;
p->m_ClientList.AddTail(node);
p->ListClientUsers();
}
/
class NodeClient
{
public:
SOCKET Socket;
in_addr Inaddr;
HANDLE Recvthreadhandle;
RecvParam* recvparam;
};
//Add List Node
NodeClient node;
node.Socket = socket_Client;
node.Inaddr = Client_stockaddrin.sin_addr;
node.Recvthreadhandle = hRecvThread;
node.recvparam = NewParam;
p->m_ClientList.AddTail(node);
p->ListClientUsers();
除去所要进行的判断之后,老方又创建了一个线程以进行接收到的数据的处理,同时对所开线程进行了异步处理。
hRecvThread = CreateThread(NULL,0,
ThreadProc_Recv, NewParam,0,&dwTheadId);
DWORD WINAPI ThreadProc_Recv(LPVOID lpParameter); 在处理接收到的消息函数中进行数据包的解析,解析中用到了Windows Sockets 的recv函数从一个已连接的套接字接收数据。
int recv(SOCKET s, char FAR* buf,int len, int flags);第一个参数是建立连接后准备接收数据的那个套接字,第二个参数是指向缓冲区的指针,用来保存接收的数据,第三个参数是缓冲区长度,第四个参数与send 函数的第四个擦火速类似,通过设置这个值可以影响这写函数调用的行为,一般设为0.
哦,在后边有while(TRUE)循环接收recv()数据,应该是此后传来的数据了,啊,有点意思了,数据包一个一个的传过来,接收一个一个的接收,第一个是数据包里是该数据类型,以后的包都是数据!
那么后边的添加显示消息就好理解的多了,int CServerDlg::GetRecvData1(CDataPack& m_DataPack,
//recv client data
DWORD WINAPI ThreadProc_Recv(LPVOID lpParameter)
{
RecvParam * param = (RecvParam*)lpParameter;
CServerDlg* p = ((RecvParam*)lpParameter)->m_this;
SOCKET socket_Client = ((RecvParam*)lpParameter)->m_socket_Client;
in_addr Client_stockaddrin = ((RecvParam*)lpParameter)->m_addrin;
while (TRUE)
{
// char buffTotal[1024] = {0};
// BOOL BGet = p->GetRecvData(socket_Client,buffTotal);
//数据包头部
CDataPack m_DataPack;
//数据
char* buffTotal = NULL;
int nGetLen = p->GetRecvData1(m_DataPack,
socket_Client,
&buffTotal);
if ( nGetLen >= 0 )
{
while(TRUE)
{
if ( m_DataPack.nType == CMD_MSG )
{
if ( strcmp(buffTotal,"server stop") == 0 )
{
//remove client node
p->RemoveClientList(socket_Client);
//refresh user login list
p->ListClientUsers();
break;
}
else if ( strcmp(buffTotal,"quit") == 0 )
{
//remove client node
p->RemoveClientList(socket_Client);
//refresh user login list
p->ListClientUsers();
//print user logout info
CString csTxt;
csTxt.Format("%s quit",
inet_ntoa(Client_stockaddrin));
p->AddMsg(csTxt);
break;
}
else
{
//正常消息
p->AddMsg(CString(buffTotal));
break;
}
}
else if ( m_DataPack.nType == CMD_SENDSCREENREQUEST )
{
//remove client node
p->SendMyScreen(socket_Client);
break;
}
}
if ( buffTotal )
{
delete buffTotal;
buffTotal = NULL;
}
}
}
return 0;
}
SOCKET socket_Client,
char** ppNewBuffOut)
{
char szBuff[10] = {0};
while(TRUE)
{
//先接收数据包的类型 CDataPack
int nRecvNum = recv(socket_Client,(char*)&m_DataPack,
sizeof(CDataPack),0);
if ( nRecvNum == SOCKET_ERROR )
{
if ( uAsync == 1 )
{
Sleep(50);
continue;
}
else
{
return -1;
}
}
if ( nRecvNum != SOCKET_ERROR )
{
break;
}
}
//解包CDataPack
if ( m_DataPack.nLenData == 0 ) return 0;
*ppNewBuffOut = new char[m_DataPack.nLenData];
memset(*ppNewBuffOut,0,m_DataPack.nLenData);
char* pBuffTotal = *ppNewBuffOut;
DWORD nLenRecv = 0;
while (TRUE)
{
int nRecvNum = recv(socket_Client,szBuff,10,0);
if ( nRecvNum == SOCKET_ERROR )
{
if ( uAsync == 1 )
{
Sleep(50);
continue;
}
else
return -1;
}
else
{
memcpy(pBuffTotal,szBuff,nRecvNum);
pBuffTotal += nRecvNum;
nLenRecv += nRecvNum;
if ( m_DataPack.nLenData == nLenRecv )
break;
}
}
return m_DataPack.nLenData;
}