以前一直在使用 UDP 与服务器进行通讯,这次一个新的项目需要采用 TCP 来实现与服务器的通讯。
先写了一个 TCP 客户端的类,同时也做了一个服务器用于测试。先把客户端的 TCP 类代码分分享出来吧。
头文件:
// CeTcpClient.h: interface for the CCeTcpClient class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_CETCPCLIENT_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_) #define AFX_CETCPCLIENT_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <winsock.h> // 连接断开 typedef void (CALLBACK *ONDISCONNECT)(CWnd *); // 数据接收 typedef void (CALLBACK *ONREAD)(CWnd *,const char *,int); // Socket 错误 typedef void (CALLBACK *ONERROR)(CWnd *,int); class CCeTcpClient { public: CCeTcpClient(); virtual ~CCeTcpClient(); public: // 服务器 IP 地址 CString m_csRemoteHost; // 服务器端口 int m_iPort; // 连接断开 ONDISCONNECT OnDisConnect; // 接收数据 ONREAD OnRead; // 发生错误 ONERROR OnError; public: // 打开 Socket bool Open(CWnd *pWnd); // 关闭 Socket bool Close(); // 与服务器端建立连接 bool Connect(); // 向服务器端发送数据 bool SendData(const char *pcBuffer,int iLen); private: // Socket 句柄 SOCKET m_socket; // 通讯线程退出事件 HANDLE m_hExitThreadEvent; // 通讯线程 HANDLE m_hTcpThreadHandle; // 父窗口句柄 CWnd *m_pOwnerWnd; private: // 通讯线程 static DWORD SocketControlThread(LPVOID lparam); }; #endif // !defined(AFX_TCPCLIENT_CE_H__B7856B99_69E7_4868_9BA3_96152245C65E__INCLUDED_)
源文件:
// CeTcpClient.cpp: implementation of the CCeTcpClient class. // Leo.Zheng 2012.04.09 ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "TCPClient.h" #include "CeTcpClient.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CCeTcpClient::CCeTcpClient() { int iInitWSA = 0; // 初始化 Socket, 版本: 1.1 WSADATA wsd; iInitWSA = WSAStartup(MAKEWORD(1,1),&wsd); if(0 != iInitWSA) { RETAILMSG(1,(L"[CCeTcpClient::CCeTcpClient]WSA start up failed: %d\r\n",iInitWSA)); } // 创建线程退出事件 m_hExitThreadEvent = CreateEvent(NULL,FALSE,FALSE,NULL); } CCeTcpClient::~CCeTcpClient() { // 释放 Socket WSACleanup(); // 关闭线程退出事件 CloseHandle(m_hExitThreadEvent); } /* * 功能: 打开 Socket * 参数: pWnd 用于指定父窗口句柄 * 返回值: TRUE 成功; FALSE 失败 */ bool CCeTcpClient::Open(CWnd *pWnd) { ResetEvent(m_hExitThreadEvent); m_pOwnerWnd = pWnd; // 创建 TCP 套接字 m_socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(SOCKET_ERROR == m_socket) { return FALSE; } // 创建通讯线程 m_hTcpThreadHandle = CreateThread(NULL,0,SocketControlThread,this,0,NULL); if(NULL == m_hTcpThreadHandle) { closesocket(m_socket); return FALSE; } return TRUE; } /* * 功能: 关闭 Socket * 参数: 无 * 返回值: TRUE 成功; FALSE 失败 */ bool CCeTcpClient::Close() { // 设置通讯线程结束事件 SetEvent(m_hExitThreadEvent); Sleep(1000); // 关闭 Socket int iError = closesocket(m_socket); if(SOCKET_ERROR == iError) { return FALSE; } return TRUE; } /* * 功能: 与 TCP 服务器建立连接 * 参数: 无 * 返回值: TRUE 成功; FALSE 失败 */ bool CCeTcpClient::Connect() { struct sockaddr_in addr; int iError = 0; char cAnsiRemoteHost[255 + 1]; addr.sin_family = AF_INET; addr.sin_port = htons(m_iPort); ZeroMemory(cAnsiRemoteHost,sizeof(char) * (255 + 1)); WideCharToMultiByte(CP_ACP,WC_COMPOSITECHECK,m_csRemoteHost,wcslen(m_csRemoteHost),cAnsiRemoteHost,wcslen(m_csRemoteHost),NULL,NULL); addr.sin_addr.s_addr = inet_addr(cAnsiRemoteHost); // 采用同步连接方式, connect 直接返回成功或是失败 iError = connect(m_socket,(struct sockaddr *)&addr,sizeof(addr)); if(SOCKET_ERROR == iError) { return FALSE; } // 设置通讯模式为异步模式 DWORD dwMode = 1; ioctlsocket(m_socket,FIONBIO,&dwMode); return TRUE; } /* * 功能: 向服务器端发送数据 * 参数: buf 待发送的数据 len 待发送的数据长度 * 返回值: TRUE 成功; FALSE 失败 */ bool CCeTcpClient::SendData(const char *pcBuffer,int iLen) { int iBytes = 0; int iSendBytes = 0; while(iSendBytes < iLen) { iBytes = send(m_socket,pcBuffer + iSendBytes,iLen - iSendBytes,0); if(SOCKET_ERROR == iBytes) { int iErrorCode = WSAGetLastError(); OnError(m_pOwnerWnd,iErrorCode); OnDisConnect(m_pOwnerWnd); //关闭 Socket Close(); return FALSE; } iSendBytes = iSendBytes + iBytes; if(iSendBytes < iLen) { Sleep(1000); } } return TRUE; } /* * 功能: 此线程用于监听 TCP 客户端通讯的事件 例如: 当接收到数据、连接断开或通讯过程发生错误等事件 * 参数: lparam: 可以通过此参数,向线程中传入需要用到的资源 在这里我们将 CCeTcpClient 类实例指针传进来 * 返回值: 无意义,将返回值设为 0。 */ DWORD CCeTcpClient::SocketControlThread(LPVOID lparam) { CCeTcpClient *pSocket = NULL; fd_set fdRead; TIMEVAL aTime; int iRet = 0; // 得到 CCeTcpClient 实例指针 pSocket = (CCeTcpClient *)lparam; // 事件等待时间设置 aTime.tv_sec = 1; aTime.tv_usec = 0; while(TRUE) { // 退出事件,结束线程 if(WAIT_OBJECT_0 == WaitForSingleObject(pSocket->m_hExitThreadEvent,0)) { break; } // 置 fdRead 为空 FD_ZERO(&fdRead); // Socket 设置读事件 FD_SET(pSocket->m_socket,&fdRead); // 判断是否有读事件 iRet = select(0,&fdRead,NULL,NULL,&aTime); if(SOCKET_ERROR == iRet) { pSocket->OnError(pSocket->m_pOwnerWnd,1); pSocket->OnDisConnect(pSocket->m_pOwnerWnd); //关闭客户端 Socket closesocket(pSocket->m_socket); break; } if(iRet > 0) { if(FD_ISSET(pSocket->m_socket,&fdRead)) { char cRecvBuffer[1024 + 1]; int iRecvLength = 0; ZeroMemory(cRecvBuffer,sizeof(char) * (1024 + 1)); // 接收数据 iRecvLength = recv(pSocket->m_socket,cRecvBuffer,1024,0); if(SOCKET_ERROR == iRecvLength) { int iError = WSAGetLastError(); pSocket->OnError(pSocket->m_pOwnerWnd,iError); pSocket->OnDisConnect(pSocket->m_pOwnerWnd); // 关闭客户端 Socket closesocket(pSocket->m_socket); break; } else if(0 == iRecvLength) { pSocket->OnDisConnect(pSocket->m_pOwnerWnd); // 关闭客户端 Socket closesocket(pSocket->m_socket); break; } else { // 触发数据接收事件 pSocket->OnRead(pSocket->m_pOwnerWnd,cRecvBuffer,iRecvLength); } } } } return 0; }
测试代码如下:
BOOL CTCPClientDlg::OnInitDialog() { //m_bFullScreen = FALSE; CDialog::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon CenterWindow(GetDesktopWindow()); // center to the hpc screen // 初始化输入值 m_csRemoteHost = GetLocalIP(); // 在模拟器上测试时用: 客户端与服务器都运行在模拟器上 m_iRemotePort = 5000; UpdateData(FALSE); return TRUE; // return TRUE unless you set the focus to a control } // 连接断开 void CALLBACK CTCPClientDlg::OnDisConnect(CWnd *pWnd) { CTCPClientDlg *pDlg = (CTCPClientDlg *)pWnd; CStatic *pStatus = (CStatic *)pDlg->GetDlgItem(IDC_LBLCONNSTATUS); ASSERT(NULL != pStatus); pStatus->SetWindowText(L"连接断开"); CButton *pBtnConn = (CButton *)pDlg->GetDlgItem(IDC_BTNCONN); CButton *pBtnDisConn = (CButton *)pDlg->GetDlgItem(IDC_BTNDISCONN); CButton *pBtnSendData = (CButton *)pDlg->GetDlgItem(IDC_BTNSENDDATA); ASSERT(NULL != pBtnConn); ASSERT(NULL != pBtnDisConn); ASSERT(NULL != pBtnSendData); pBtnConn->EnableWindow(TRUE); pBtnDisConn->EnableWindow(FALSE); pBtnSendData->EnableWindow(FALSE); } // 接收的数据显示 void CALLBACK CTCPClientDlg::OnRead(CWnd *pWnd,const char *pcBuffer,int iLen) { CTCPClientDlg *pDlg = (CTCPClientDlg *)pWnd; CString csRecvStr = pcBuffer; CEdit *pEdtRecv = (CEdit *)pDlg->GetDlgItem(IDC_EDTRECV); ASSERT(NULL != pEdtRecv); //将接收的数据显示到接收文本框上 pEdtRecv->SetWindowText(csRecvStr); } // Socket 错误显示 void CALLBACK CTCPClientDlg::OnError(CWnd *pWnd,int iErrorCode) { CString csErrorInfo; csErrorInfo.Format(L"%s:%d",L"客户端socket发生错误",iErrorCode); AfxMessageBox(csErrorInfo); } // 建立连接 void CTCPClientDlg::OnBtnconn() { CStatic *pStatus = (CStatic *)GetDlgItem(IDC_LBLCONNSTATUS); CButton *pBtnConn = (CButton *)GetDlgItem(IDC_BTNCONN); CButton *pBtnDisConn = (CButton *)GetDlgItem(IDC_BTNDISCONN); CButton *pBtnSendData = (CButton *)GetDlgItem(IDC_BTNSENDDATA); ASSERT(NULL != pStatus); ASSERT(NULL != pBtnConn); ASSERT(NULL != pBtnDisConn); ASSERT(NULL != pBtnSendData); UpdateData(TRUE); // 设置 m_tcpClient 属性 m_tcpClient.m_csRemoteHost = m_csRemoteHost; m_tcpClient.m_iPort = m_iRemotePort; m_tcpClient.OnDisConnect = OnDisConnect; m_tcpClient.OnRead = OnRead; m_tcpClient.OnError = OnError; // 打开客户端 socket m_tcpClient.Open(this); // 与服务器端连接 if(m_tcpClient.Connect()) { pStatus->SetWindowText(L"建立连接"); UpdateData(FALSE); } else { AfxMessageBox(L"建立连接失败"); pStatus->SetWindowText(L"连接断开"); return; } pBtnConn->EnableWindow(FALSE); pBtnDisConn->EnableWindow(TRUE); pBtnSendData->EnableWindow(TRUE); } // 断开连接 void CTCPClientDlg::OnBtndisconn() { if(m_tcpClient.Close()) { CStatic *pStatus = (CStatic *)GetDlgItem(IDC_LBLCONNSTATUS); CButton *pBtnConn =(CButton*)GetDlgItem(IDC_BTNCONN); CButton *pBtnDisConn = (CButton*)GetDlgItem(IDC_BTNDISCONN); CButton *pBtnSendData = (CButton*)GetDlgItem(IDC_BTNSENDDATA); ASSERT(NULL != pStatus); ASSERT(NULL != pBtnConn); ASSERT(NULL != pBtnDisConn); ASSERT(NULL != pBtnSendData); pStatus->SetWindowText(L"连接断开"); pBtnConn->EnableWindow(TRUE); pBtnDisConn->EnableWindow(FALSE); pBtnSendData->EnableWindow(FALSE); } else { AfxMessageBox(L"连接断开失败"); } } // 发送数据 void CTCPClientDlg::OnBtnsenddata() { char *pcSendBuf; int iSendLength = 0; UpdateData(TRUE); iSendLength = m_csSendData.GetLength(); pcSendBuf = new char[iSendLength * 2]; ASSERT(NULL != pcSendBuf); wcstombs(pcSendBuf,m_csSendData,iSendLength); if(!m_tcpClient.SendData(pcSendBuf,iSendLength)) { AfxMessageBox(L"发送失败"); } delete[] pcSendBuf; pcSendBuf = NULL; }