以前一直在使用 UDP 与服务器进行通讯,这次一个新的项目需要采用 TCP 来实现与服务器的通讯。
先写了一个 TCP 客户端的类,同时也做了一个服务器用于测试。先把客户端的 TCP 类代码分分享出来吧。
头文件:
1 // CeTcpClient.h: interface for the CCeTcpClient class. 2 // 3 ////////////////////////////////////////////////////////////////////// 4 5 #if !defined(AFX_CETCPCLIENT_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_) 6 #define AFX_CETCPCLIENT_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_ 7 8 #if _MSC_VER > 1000 9 #pragma once 10 #endif // _MSC_VER > 1000 11 12 #include <winsock.h> 13 14 // 连接断开 15 typedef void (CALLBACK *ONDISCONNECT)(CWnd *); 16 // 数据接收 17 typedef void (CALLBACK *ONREAD)(CWnd *,const char *,int); 18 // Socket 错误 19 typedef void (CALLBACK *ONERROR)(CWnd *,int); 20 21 class CCeTcpClient 22 { 23 public: 24 CCeTcpClient(); 25 virtual ~CCeTcpClient(); 26 27 public: 28 // 服务器 IP 地址 29 CString m_csRemoteHost; 30 // 服务器端口 31 int m_iPort; 32 33 // 连接断开 34 ONDISCONNECT OnDisConnect; 35 // 接收数据 36 ONREAD OnRead; 37 // 发生错误 38 ONERROR OnError; 39 40 public: 41 // 打开 Socket 42 bool Open(CWnd *pWnd); 43 // 关闭 Socket 44 bool Close(); 45 // 与服务器端建立连接 46 bool Connect(); 47 // 向服务器端发送数据 48 bool SendData(const char *pcBuffer,int iLen); 49 50 private: 51 // Socket 句柄 52 SOCKET m_socket; 53 // 通讯线程退出事件 54 HANDLE m_hExitThreadEvent; 55 // 通讯线程 56 HANDLE m_hTcpThreadHandle; 57 // 父窗口句柄 58 CWnd *m_pOwnerWnd; 59 60 private: 61 // 通讯线程 62 static DWORD SocketControlThread(LPVOID lparam); 63 }; 64 65 #endif // !defined(AFX_TCPCLIENT_CE_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_)
源文件:
1 // CeTcpClient.cpp: implementation of the CCeTcpClient class. 2 // Leo.Zheng 2012.04.09 3 ////////////////////////////////////////////////////////////////////// 4 5 #include "stdafx.h" 6 #include "TCPClient.h" 7 #include "CeTcpClient.h" 8 9 #ifdef _DEBUG 10 #undef THIS_FILE 11 static char THIS_FILE[]=__FILE__; 12 #define new DEBUG_NEW 13 #endif 14 15 ////////////////////////////////////////////////////////////////////// 16 // Construction/Destruction 17 ////////////////////////////////////////////////////////////////////// 18 CCeTcpClient::CCeTcpClient() 19 { 20 int iInitWSA = 0; 21 // 初始化 Socket, 版本: 1.1 22 WSADATA wsd; 23 24 iInitWSA = WSAStartup(MAKEWORD(1,1),&wsd); 25 if(0 != iInitWSA) 26 { 27 RETAILMSG(1,(L"[CCeTcpClient::CCeTcpClient]WSA start up failed: %d ",iInitWSA)); 28 } 29 // 创建线程退出事件 30 m_hExitThreadEvent = CreateEvent(NULL,FALSE,FALSE,NULL); 31 } 32 33 CCeTcpClient::~CCeTcpClient() 34 { 35 // 释放 Socket 36 WSACleanup(); 37 // 关闭线程退出事件 38 CloseHandle(m_hExitThreadEvent); 39 } 40 41 /* 42 * 功能: 打开 Socket 43 * 参数: pWnd 用于指定父窗口句柄 44 * 返回值: TRUE 成功; FALSE 失败 45 */ 46 bool CCeTcpClient::Open(CWnd *pWnd) 47 { 48 ResetEvent(m_hExitThreadEvent); 49 m_pOwnerWnd = pWnd; 50 51 // 创建 TCP 套接字 52 m_socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 53 if(SOCKET_ERROR == m_socket) 54 { 55 return FALSE; 56 } 57 58 // 创建通讯线程 59 m_hTcpThreadHandle = CreateThread(NULL,0,SocketControlThread,this,0,NULL); 60 if(NULL == m_hTcpThreadHandle) 61 { 62 closesocket(m_socket); 63 return FALSE; 64 } 65 66 return TRUE; 67 } 68 69 /* 70 * 功能: 关闭 Socket 71 * 参数: 无 72 * 返回值: TRUE 成功; FALSE 失败 73 */ 74 bool CCeTcpClient::Close() 75 { 76 // 设置通讯线程结束事件 77 SetEvent(m_hExitThreadEvent); 78 Sleep(1000); 79 80 // 关闭 Socket 81 int iError = closesocket(m_socket); 82 if(SOCKET_ERROR == iError) 83 { 84 return FALSE; 85 } 86 87 return TRUE; 88 } 89 90 /* 91 * 功能: 与 TCP 服务器建立连接 92 * 参数: 无 93 * 返回值: TRUE 成功; FALSE 失败 94 */ 95 bool CCeTcpClient::Connect() 96 { 97 struct sockaddr_in addr; 98 int iError = 0; 99 char cAnsiRemoteHost[255 + 1]; 100 101 addr.sin_family = AF_INET; 102 addr.sin_port = htons(m_iPort); 103 ZeroMemory(cAnsiRemoteHost,sizeof(char) * (255 + 1)); 104 WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,m_csRemoteHost,wcslen(m_csRemoteHost),cAnsiRemoteHost,wcslen(m_csRemoteHost),NULL,NULL); 105 106 addr.sin_addr.s_addr = inet_addr(cAnsiRemoteHost); 107 // 采用同步连接方式, connect 直接返回成功或是失败 108 iError = connect(m_socket,(struct sockaddr *)&addr,sizeof(addr)); 109 if(SOCKET_ERROR == iError) 110 { 111 return FALSE; 112 } 113 // 设置通讯模式为异步模式 114 DWORD dwMode = 1; 115 ioctlsocket(m_socket,FIONBIO,&dwMode); 116 117 return TRUE; 118 } 119 120 /* 121 * 功能: 向服务器端发送数据 122 * 参数: buf 待发送的数据 123 len 待发送的数据长度 124 * 返回值: TRUE 成功; FALSE 失败 125 */ 126 bool CCeTcpClient::SendData(const char *pcBuffer,int iLen) 127 { 128 int iBytes = 0; 129 int iSendBytes = 0; 130 131 while(iSendBytes < iLen) 132 { 133 iBytes = send(m_socket,pcBuffer + iSendBytes,iLen - iSendBytes,0); 134 if(SOCKET_ERROR == iBytes) 135 { 136 int iErrorCode = WSAGetLastError(); 137 OnError(m_pOwnerWnd,iErrorCode); 138 OnDisConnect(m_pOwnerWnd); 139 //关闭 Socket 140 Close(); 141 return FALSE; 142 } 143 144 iSendBytes = iSendBytes + iBytes; 145 146 if(iSendBytes < iLen) 147 { 148 Sleep(1000); 149 } 150 } 151 return TRUE; 152 } 153 154 /* 155 * 功能: 此线程用于监听 TCP 客户端通讯的事件 156 例如: 当接收到数据、连接断开或通讯过程发生错误等事件 157 * 参数: lparam: 可以通过此参数,向线程中传入需要用到的资源 158 在这里我们将 CCeTcpClient 类实例指针传进来 159 * 返回值: 无意义,将返回值设为 0。 160 */ 161 DWORD CCeTcpClient::SocketControlThread(LPVOID lparam) 162 { 163 CCeTcpClient *pSocket = NULL; 164 fd_set fdRead; 165 TIMEVAL aTime; 166 int iRet = 0; 167 168 // 得到 CCeTcpClient 实例指针 169 pSocket = (CCeTcpClient *)lparam; 170 // 事件等待时间设置 171 aTime.tv_sec = 1; 172 aTime.tv_usec = 0; 173 174 while(TRUE) 175 { 176 // 退出事件,结束线程 177 if(WAIT_OBJECT_0 == WaitForSingleObject(pSocket->m_hExitThreadEvent,0)) 178 { 179 break; 180 } 181 // 置 fdRead 为空 182 FD_ZERO(&fdRead); 183 // Socket 设置读事件 184 FD_SET(pSocket->m_socket,&fdRead); 185 186 // 判断是否有读事件 187 iRet = select(0,&fdRead,NULL,NULL,&aTime); 188 if(SOCKET_ERROR == iRet) 189 { 190 pSocket->OnError(pSocket->m_pOwnerWnd,1); 191 pSocket->OnDisConnect(pSocket->m_pOwnerWnd); 192 //关闭客户端 Socket 193 closesocket(pSocket->m_socket); 194 break; 195 } 196 197 if(iRet > 0) 198 { 199 if(FD_ISSET(pSocket->m_socket,&fdRead)) 200 { 201 char cRecvBuffer[1024 + 1]; 202 int iRecvLength = 0; 203 204 ZeroMemory(cRecvBuffer,sizeof(char) * (1024 + 1)); 205 // 接收数据 206 iRecvLength = recv(pSocket->m_socket,cRecvBuffer,1024,0); 207 if(SOCKET_ERROR == iRecvLength) 208 { 209 int iError = WSAGetLastError(); 210 pSocket->OnError(pSocket->m_pOwnerWnd,iError); 211 pSocket->OnDisConnect(pSocket->m_pOwnerWnd); 212 // 关闭客户端 Socket 213 closesocket(pSocket->m_socket); 214 break; 215 } 216 else if(0 == iRecvLength) 217 { 218 pSocket->OnDisConnect(pSocket->m_pOwnerWnd); 219 // 关闭客户端 Socket 220 closesocket(pSocket->m_socket); 221 break; 222 } 223 else 224 { 225 // 触发数据接收事件 226 pSocket->OnRead(pSocket->m_pOwnerWnd,cRecvBuffer,iRecvLength); 227 } 228 } 229 } 230 } 231 232 return 0; 233 }
测试代码如下:
1 BOOL CTCPClientDlg::OnInitDialog() 2 { 3 //m_bFullScreen = FALSE; 4 CDialog::OnInitDialog(); 5 6 // Set the icon for this dialog. The framework does this automatically 7 // when the application's main window is not a dialog 8 SetIcon(m_hIcon, TRUE); // Set big icon 9 SetIcon(m_hIcon, FALSE); // Set small icon 10 11 CenterWindow(GetDesktopWindow()); // center to the hpc screen 12 13 // 初始化输入值 14 m_csRemoteHost = GetLocalIP(); // 在模拟器上测试时用: 客户端与服务器都运行在模拟器上 15 m_iRemotePort = 5000; 16 UpdateData(FALSE); 17 return TRUE; // return TRUE unless you set the focus to a control 18 } 19 20 21 // 连接断开 22 void CALLBACK CTCPClientDlg::OnDisConnect(CWnd *pWnd) 23 { 24 CTCPClientDlg *pDlg = (CTCPClientDlg *)pWnd; 25 26 CStatic *pStatus = (CStatic *)pDlg->GetDlgItem(IDC_LBLCONNSTATUS); 27 ASSERT(NULL != pStatus); 28 pStatus->SetWindowText(L"连接断开"); 29 30 CButton *pBtnConn = (CButton *)pDlg->GetDlgItem(IDC_BTNCONN); 31 CButton *pBtnDisConn = (CButton *)pDlg->GetDlgItem(IDC_BTNDISCONN); 32 CButton *pBtnSendData = (CButton *)pDlg->GetDlgItem(IDC_BTNSENDDATA); 33 ASSERT(NULL != pBtnConn); 34 ASSERT(NULL != pBtnDisConn); 35 ASSERT(NULL != pBtnSendData); 36 pBtnConn->EnableWindow(TRUE); 37 pBtnDisConn->EnableWindow(FALSE); 38 pBtnSendData->EnableWindow(FALSE); 39 } 40 41 // 接收的数据显示 42 void CALLBACK CTCPClientDlg::OnRead(CWnd *pWnd,const char *pcBuffer,int iLen) 43 { 44 CTCPClientDlg *pDlg = (CTCPClientDlg *)pWnd; 45 CString csRecvStr = pcBuffer; 46 CEdit *pEdtRecv = (CEdit *)pDlg->GetDlgItem(IDC_EDTRECV); 47 48 ASSERT(NULL != pEdtRecv); 49 //将接收的数据显示到接收文本框上 50 pEdtRecv->SetWindowText(csRecvStr); 51 } 52 53 // Socket 错误显示 54 void CALLBACK CTCPClientDlg::OnError(CWnd *pWnd,int iErrorCode) 55 { 56 CString csErrorInfo; 57 58 csErrorInfo.Format(L"%s:%d",L"客户端socket发生错误",iErrorCode); 59 AfxMessageBox(csErrorInfo); 60 } 61 62 // 建立连接 63 void CTCPClientDlg::OnBtnconn() 64 { 65 CStatic *pStatus = (CStatic *)GetDlgItem(IDC_LBLCONNSTATUS); 66 CButton *pBtnConn = (CButton *)GetDlgItem(IDC_BTNCONN); 67 CButton *pBtnDisConn = (CButton *)GetDlgItem(IDC_BTNDISCONN); 68 CButton *pBtnSendData = (CButton *)GetDlgItem(IDC_BTNSENDDATA); 69 70 ASSERT(NULL != pStatus); 71 ASSERT(NULL != pBtnConn); 72 ASSERT(NULL != pBtnDisConn); 73 ASSERT(NULL != pBtnSendData); 74 75 UpdateData(TRUE); 76 // 设置 m_tcpClient 属性 77 m_tcpClient.m_csRemoteHost = m_csRemoteHost; 78 m_tcpClient.m_iPort = m_iRemotePort; 79 m_tcpClient.OnDisConnect = OnDisConnect; 80 m_tcpClient.OnRead = OnRead; 81 m_tcpClient.OnError = OnError; 82 // 打开客户端 socket 83 m_tcpClient.Open(this); 84 85 // 与服务器端连接 86 if(m_tcpClient.Connect()) 87 { 88 pStatus->SetWindowText(L"建立连接"); 89 UpdateData(FALSE); 90 } 91 else 92 { 93 AfxMessageBox(L"建立连接失败"); 94 pStatus->SetWindowText(L"连接断开"); 95 return; 96 } 97 98 pBtnConn->EnableWindow(FALSE); 99 pBtnDisConn->EnableWindow(TRUE); 100 pBtnSendData->EnableWindow(TRUE); 101 } 102 103 // 断开连接 104 void CTCPClientDlg::OnBtndisconn() 105 { 106 if(m_tcpClient.Close()) 107 { 108 CStatic *pStatus = (CStatic *)GetDlgItem(IDC_LBLCONNSTATUS); 109 CButton *pBtnConn =(CButton*)GetDlgItem(IDC_BTNCONN); 110 CButton *pBtnDisConn = (CButton*)GetDlgItem(IDC_BTNDISCONN); 111 CButton *pBtnSendData = (CButton*)GetDlgItem(IDC_BTNSENDDATA); 112 113 ASSERT(NULL != pStatus); 114 ASSERT(NULL != pBtnConn); 115 ASSERT(NULL != pBtnDisConn); 116 ASSERT(NULL != pBtnSendData); 117 118 pStatus->SetWindowText(L"连接断开"); 119 pBtnConn->EnableWindow(TRUE); 120 pBtnDisConn->EnableWindow(FALSE); 121 pBtnSendData->EnableWindow(FALSE); 122 } 123 else 124 { 125 AfxMessageBox(L"连接断开失败"); 126 } 127 } 128 129 // 发送数据 130 void CTCPClientDlg::OnBtnsenddata() 131 { 132 char *pcSendBuf; 133 int iSendLength = 0; 134 UpdateData(TRUE); 135 136 iSendLength = m_csSendData.GetLength(); 137 pcSendBuf = new char[iSendLength * 2]; 138 ASSERT(NULL != pcSendBuf); 139 wcstombs(pcSendBuf,m_csSendData,iSendLength); 140 if(!m_tcpClient.SendData(pcSendBuf,iSendLength)) 141 { 142 AfxMessageBox(L"发送失败"); 143 } 144 delete[] pcSendBuf; 145 pcSendBuf = NULL; 146 }