最近做一个关于服务器和客户端互相通信的程序,题目有两个要求:1.服务器只能接收一个客户端的连接,当有第二个客户端连接时就会失败;2.当已经连接的客户端断开后,其他的客户端就可以去连接服务器了。
这样的要求看似很简单,但做的过程中我才发现,客户端程序很好写,可服务器端有点麻烦了。首先只能接收一个客户端连接的服务器程序,这个很另类,网上找了大多都是研究怎么让服务器可以同时与多个客户端打交道,于是只能自己慢慢搞了。
实践过程中,我发现,只要服务器端在调用了accept函数之后把用于listen用的socket给关闭掉,这样就能让第二个客户端连接时返回SOCKET_ERROR。第一个要求算是完成了,下面是第二个要求。要完成第二个要求首先就得能知道客户端断开连接了,然后再重新listen并accept,重复第一次的步骤。这个步骤说来也简单,但到底选用哪个模型来实现呢,细节问题怎么处理呢?可是苦了我这个菜鸟。还好最后找到方法了,把代码贴到这里保存下
// 填充sockaddr_in结构
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(6000);
sin.sin_addr.S_un.S_addr = INADDR_ANY;;
BOOL reuse=TRUE;
setsockopt(sListen,SOL_SOCKET,SO_REUSEADDR,(char*)&reuse,sizeof(BOOL));
// 绑定这个套节字到一个本地地址
if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
MessageBox("Failed bind() /n");
return ;
}
// 将套接字设为窗口通知消息类型。
::WSAAsyncSelect(sListen, m_hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
// 进入监听模式
if(::listen(sListen, 2) == SOCKET_ERROR)
{
MessageBox("Failed listen() /n");
return ;
}
GetDlgItem(IDCSTART)->EnableWindow(FALSE);
// MessageBox("服务器成功开启,等待一个客户接入!");
}
void CTCPservDlg::OnSock(WPARAM wparam,LPARAM lparam)
{
switch(LOWORD(lparam))
{
case FD_ACCEPT: // 监听中的套接字检测到有连接进入
{
SOCKET client = ::accept(sListen, NULL, NULL);
MessageBox("接收到一个连接");
::closesocket(sListen);
::WSAAsyncSelect(client, m_hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
sSend=client;
}
break;
case FD_WRITE:
{
CString s;
GetDlgItemText(IDC_SEND,s);
send(sSend,s.GetBuffer(0),s.GetLength(),0);
}
break;
case FD_READ:
{
char szText[1024] = { 0 };
if(::recv(sListen, szText, 1024, 0) == -1)
::closesocket(sListen);
else
MessageBox(szText);
}
break;
case FD_CLOSE:
{
// MessageBox("对方关闭连接");
OnCstart();
// ::closesocket(sListen);
}
break;
}
}