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( "" ); } }