1.服务端代码
a)初始化winsock库,在Server.cpp中添加
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 0);
::WSAStartup(sockVersion, &wsaData);
CserverDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: 在此放置处理何时用
// “确定”来关闭对话框的代码
}
b)创建Server端的监听socket
void CserverDlg::OnBnClickedButton1()
{
m_ip.ResetContent();
m_list.ResetContent();
// TODO: 在此添加控件通知处理程序代码
if(m_socket == INVALID_SOCKET) // 开启服务
{
// 取得端口号
CString sPort;
m_port.GetWindowText( sPort );
int nPort = atoi(sPort);
if(nPort < 1 || nPort > 65535)
{
m_info.AddString( "端口号错误!" );
return;
}
// 创建监听套节字,使它进入监听状态
if( !CreateAndListen(nPort) )
{
m_info.AddString( "启动服务出错!" );
return;
}
}
else // 停止服务
{
// 关闭所有连接
CloseAllSocket();
m_info.AddString( "所有连接已断开!已停止监听!" );
// 设置相关子窗口控件状态
m_start.SetWindowText( "开启服务" );
m_port.EnableWindow( TRUE );
m_msg.EnableWindow( FALSE );
m_send.EnableWindow( FALSE );
}
}
c)绑定地址端口等信息
BOOL CserverDlg::CreateAndListen(int nPort)
{
if(m_socket == INVALID_SOCKET)
::closesocket(m_socket);
// 创建套节字
m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_socket == INVALID_SOCKET)
return FALSE;
// 填写要关联的本地地址
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(nPort);
sin.sin_addr.s_addr = INADDR_ANY;
// 绑定端口
if(::bind(m_socket, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
return FALSE;
}
// 设置socket为窗口通知消息类型
::WSAAsyncSelect(m_socket, m_hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
// 进入监听模式
::listen(m_socket, 5);
m_info.AddString( "已开启监听!监听中..." );
m_start.SetWindowText( "关闭服务" );
m_port.EnableWindow( FALSE );
m_msg.EnableWindow( TRUE );
m_send.EnableWindow( TRUE );
return TRUE;
}
d)客户端请求连接时
BOOL CserverDlg::AddClient(SOCKET s)
{
if( m_client<MAX_SOCKET)
{
// 添加新的成员
m_arClient[m_client++] = s;
return TRUE;
}
return FALSE;
}
e)客户端断开连接时
void CserverDlg::RemoveClient(SOCKET s)
{
BOOL bFind = FALSE;
int i=0;
for( i=0; i<m_client; i++ )
{
if( m_arClient[i]==s )
{
bFind = TRUE;
break;
}
}
// 如果找到就将此成员从列表中移除
if(bFind)
{
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
::getpeername(m_arClient[i], (SOCKADDR*)&sockAddr, &nSockAddrLen);
// 转化为主机字节顺序
int nPeerPort = ::ntohs(sockAddr.sin_port);
// 转化为字符串IP
CString sPeerIP = ::inet_ntoa(sockAddr.sin_addr);
m_info.AddString( "##客户端["+sPeerIP+"]已断开连接!" );
CString sTmp;
for( int k=0; k<m_ip.GetCount(); k++ )
{
m_list.GetText( k, sTmp );
if( sTmp==sPeerIP ) m_list.DeleteString( k );
m_ip.GetLBText( k, sTmp ) ;
if( sTmp==sPeerIP )
{
m_ip.DeleteString( k );
break;
}
}
m_client--;
// 将此成员后面的成员都向前移动一个单位
for(int j=i; j<m_client; j++)
{
m_arClient[j] = m_arClient[j+1];
}
}
if( m_ip.GetCount()==0 ) m_ip.SetWindowText( "" );
}
f)消息响应函数
afx_msg LRESULT CserverDlg::OnSocket(WPARAM wParam, LPARAM lParam)
{
// 取得有事件发生的套节字句柄
SOCKET s = wParam;
// 查看是否出错
if( WSAGETSELECTERROR(lParam) )
{
RemoveClient(s);
::closesocket(s);
return 0;
}
// 处理发生的事件
switch( WSAGETSELECTEVENT(lParam) )
{
case FD_ACCEPT: // 监听中的套接字检测到有连接进入
{
if( m_client<MAX_SOCKET )
{
// 接受连接请求,新的套节字client是新连接的套节字
SOCKET client = ::accept(s, NULL, NULL);
// 设置新的套节字为窗口通知消息类型
int i = ::WSAAsyncSelect(client,
m_hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE );
AddClient(client);
// 取得对方的IP地址和端口号(使用getpeername函数)
// Peer对方的地址信息
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
::getpeername(client, (SOCKADDR*)&sockAddr, &nSockAddrLen);
int nPeerPort = ::ntohs(sockAddr.sin_port);
// 转化为字符串IP
CString sPeerIP = ::inet_ntoa(sockAddr.sin_addr);
CString info;
info.Format( "检测到已建立新连接,客户端IP:%s", sPeerIP );
m_list.AddString( sPeerIP );
m_ip.AddString( sPeerIP );
m_info.AddString( info );
if( m_ip.GetCount()==1 ) m_ip.SetCurSel( 0 );
}
else
{
MessageBox("连接客户太多!");
}
}
break;
case FD_CLOSE: // 检测到套接字对应的连接被关闭。
{
RemoveClient(s);
::closesocket(s);
}
break;
case FD_READ: // 套接字接受到对方发送过来的数据包
{
// 取得对方的IP地址和端口号(使用getpeername函数)
// Peer对方的地址信息
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
::getpeername(s, (SOCKADDR*)&sockAddr, &nSockAddrLen);
// 转化为主机字节顺序
int nPeerPort = ::ntohs(sockAddr.sin_port);
// 转化为字符串IP
CString sPeerIP = ::inet_ntoa(sockAddr.sin_addr);
// 接受真正的网络数据
char szText[1024] = { 0 };
::recv(s, szText, 1024, 0);
// 显示给用户
CString strItem = "客户端["+sPeerIP+ "]: " + CString(szText);
m_info.AddString( strItem );
}
break;
case FD_WRITE:
break;
}
return 0;
}
g)服务器端发送数据
void CserverDlg::OnBnClickedButton3()
{
// TODO: 在此添加控件通知处理程序代码
if( m_ip.GetCount()==0 ) return;
m_CurClient = INVALID_SOCKET;
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
int nSockAddrLen = sizeof(sockAddr);
CString sPeerIP ,sCurIP;
m_ip.GetWindowText( sCurIP );
for( int i=0; i<m_client; i++ )
{
::getpeername( m_arClient[i], (SOCKADDR*)&sockAddr, &nSockAddrLen);
// 转化为主机字节顺序
int nPeerPort = ::ntohs(sockAddr.sin_port);
// 转化为字符串IP
sPeerIP = ::inet_ntoa(sockAddr.sin_addr);
if( sPeerIP==sCurIP )
{
m_CurClient = m_arClient[i];
break;
}
}
if( m_CurClient==INVALID_SOCKET ) return;
// 取得要发送的字符串
CString sText;
m_msg.GetWindowText( sText );
// 添加一个“回车换行”
// 注意,添加它并不是必须的,但是如果使用本软件作为客户端调试网络协议,
// 比如SMTP、FTP等,就要添加它了。因为这些协议都要求使用“回车换行”作为一个命令的结束标记
sText += "\r\n";
// 发送数据到服务器
if( ::send( m_CurClient, sText, sText.GetLength(), 0)!= -1 )
{
m_info.AddString( "本地发送->"+sPeerIP+":"+sText );
m_msg.SetWindowText( "" );
}
}