利用Socket编程是一类典型的网络通信程序,特别是在实时性要求比较高的项目中,Winsock编程方法是非常实用的。下面介绍在VC 6.0环境下开发Winsock程序的方法。 这里并没有直接应用MFC 提供的CSocket类,这是因为考虑到对于类而言,其成员函数调用必然是完全阻塞方式的,因此只能用于人工线程中。基于这种思想,可以在CObject类基础上派生一个套接字类,其使用方式为阻塞方式,虽然增加了使用的条件,但可以保证其正常工作,而不会出现不加控制地使用CSocket对象带来的冲突现象。 下面首先将具体介绍有关的套接字类的定义,新创建的套接字功能主要通过调用CSocket的相关操作实现。 1 套接字类CBlockingSocket 首先需要定义此套接字类,在类中设置了一个属性变量:SOCKET m_hSocket; m_hSocket 表示套接字的句柄。另外还构造了一组方法,其功能与CSocket类是对应的,下面以创建、监听、连接建立和消息的接收和发送为例,介绍其实现方法,。 ● 创建 创建套接字即要求创建相应的连接,缺省类型为面向连接的流,具体实现为: void CBlockingSocket::Create(int nType){ ASSERT(m_hSocket == NULL); if((m_hSocket = socket(AF_INET, nType, 0)) == INVALID_SOCKET) { throw new CBlockingSocketException("创建套接字"); } 北京电脑培训} ● 监听 Listen函数完成监听连接的任务,在实现时要求最多有10个连接请求排队,这在一般的应用中是完全足够的。 void CBlockingSocket::Listen(){ ASSERT(m_hSocket != NULL); if(listen(m_hSocket, 10) == SOCKET_ERROR) { throw new CBlockingSocketException("Listen"); } } ● 建立连接 连接的实际建立可以由Connect实现,同样地,缺省的建立方式为面向连接的流。 void CBlockingSocket::Create(int nType ){ ASSERT(m_hSocket == NULL); if((m_hSocket = socket(AF_INET, nType, 0)) == INVALID_SOCKET) { throw new CBlockingSocketException("创建套接字"); } } ● 发送消息 Send函数的作用是将数据块按一个消息发送,参数pch即为发送的消息,nSize为消息长度,nSecs可以限制操作时间。如果客户方取消读操作,则返回值将小于指定消息长度。 int CBlockingSocket::Send(const char* pch, const int nSize, const int nSecs){ ASSERT(m_hSocket != NULL); FD_SET fd = {1, m_hSocket}; TIMEVAL tv = {nSecs, 0}; if(lect(0, NULL, &fd, NULL, &tv) == 0) { throw new CBlockingSocketException("发送超时"); } if((int nBytesSent = nd(m_hSocket, pch, nSize, 0)) == SOCKET_ERROR) { throw new CBlockingSocketException("发送"); } return nBytesSent; } 此外,如果数据块比较大,可以将数据块分成多个消息发送,此工作由函数Write完成。具体实现时将通过循环调用Send函数来实现部分消息发送,通过对局部量nBytesThisTime 和nBytesSent的维护,保证整个数据块的正常发送。 int CBlockingSocket::Write(const char* pch, const int nSize, const int nSecs){ int nBytesSent = 0,nBytesThisTime; const char* pch1 = pch; do { nBytesThisTime = Send(pch1, nSize - nBytesSent, nSecs); nBytesSent += nBytesThisTime; pch1 += nBytesThisTime; } while(nBytesSent < nSize); return nBytesSent; } ● 接收消息 Receive函数的作用是与发送消息对应的,可以将接收到的消息重组为数据块。 int CBlockingSocket::Receive(char* pch, const int nSize, const int nSecs){ ASSERT(m_hSocket != NULL); 专业美发培训 FD_SET fd = {1, m_hSocket}; TIMEVAL tv = {nSecs, 0}; if(lect(0, &fd, NULL, NULL, &tv) == 0) {5英尺11英寸 throw new CBlockingSocketException("接收超时"); } if((int nBytesReceived = recv(m_hSocket, pch, nSize, 0)) == SOCKET_ERROR) { throw new CBlockingSocketException("接收"); } return nBytesReceived; } 2 地址类CSockAddr 在应用中还实现了一个Winsock地址类,它继承了sockaddr_in结构的主要属性: 属性 | 类型 | 含义 | Sin_family | short | 协议类别smartwatch | Sin_port | u_short | 端口 | Sin_addr | in_addr | 地址 | | | |
CSockAddr类有多种构造函数,如协议类别默认为AF_INET,端口号和地址均初始化为0,比较常用的构造函数实现如下,其中IP地址串参数已经是网络地址顺序形式。 CSockAddr(const char* pchIP, const USHORT ushPort = 0) { sin_family = AF_INET; sin_port = htons(ushPort); sweet什么意思 sin_addr.s_addr = inet_addr(pchIP); } CSockAddr还包括一组成员函数,这些成员函数主要是对属性的存取。如按点分十进制方式返回地址、获得端口、获得地址等等。 3 套接字异常类CBlockingSocketException CBlockingSocketException用于处理套接字阻塞异常,可由CException类派生实现。类中定义了两个属性变量:m_nError表示错误代码,而m_strMessage表示错误信息。在截获到异常时,需要利用这两个属性设置提示信息。异常消息的获得由GetErrorMessage函数完成,实现为: BOOL CBlockingSocketException::GetErrorMessage(LPTSTR lpstrError, UINT nMaxError, PUINT pnHelpContext){ char text[200]; if(m_nError == 0) wsprintf(text, "%s 错误", (const char*) m_strMessage); el wsprintf(text, "%s 错误 #%d", (const char*) m_strMessage, m_nError); strncpy(lpstrError, text, nMaxError - 1); return TRUE; } 4 服务器应用程序 为了利用套接字实现服务器应用程序,需要在应用程序类的初始化实例函数中增加以下代码,进行套接字的初始化工作: if (!AfxSocketInit()) { AfxMessageBox(IDP_SOCKETS_INIT_FAILED); return FALSE; } 我们的目的是当客户发出请求后,在服务器应用程序端显示相应的信息,同时向客户返回确认信息。 首先创建一个全局的套接字对象:CBlockingSocket g_sListen; 另外需要重新实现视类的初始化函数,先建立服务器的地址,设置通令端口为5858,这并不是必要的,只要保证客户方的通信端口与服务器的端口一致就可以了。然后创建g_sListen,并将其绑定到服务器上,调用套接字的监听操作,若监听到客户请求,将创建一个新的线程,并在此线程中处理客户请求。 void CTestsockView::OnInitialUpdate() { CEditView::OnInitialUpdate(); try { xw CSockAddr saServer; saServer = CSockAddr(INADDR_ANY, (USHORT) 5858); g_sListen.Create(); g_sListen.Bind(saServer); g_sListen.Listen();// start listening g_bListening = TRUE; g_nConnection = 0; AfxBeginThread(ServerThreadProc,GetSafeHwnd(), HREAD_PRIORITY_NORMAL); } catch(CBlockingSocketException* e) { toy story 3 g_sListen.Cleanup(); LogBlockingSocketException(GetSafeHwnd(), "VIEW:", e); e->Delete();} } 下面,再来看线程函数ServerThreadProc是如何处理客户请求的: UINT ServerThreadProc(LPVOID pParam){ CSockAddr saClient; CBlockingSocket sConnect; try { if(!g_sListen.Accept(sConnect, saClient)) { g_bListening = FALSE; return 0; } g_nConnection++; AfxBeginThread(ServerThreadProc, pParam, THREAD_PRIORITY_NORMAL); DoRequest(pParam, sConnect, saClient); sConnect.Clo(); }// 析构函数不能关闭它 catch(CBlockingSocketException* pe) { LogBlockingSocketException(pParam, "服务器:", pe); 宇航员的英文 pe->Delete(); } return 0; } 如果g_sListen调用Accept失败,说明视或应用程序关闭了连接的套接字,此时将调整当前状态,在具体处理客户请求之前,为了不影响继续接收其它客户的请求,可以再创建新的线程,即构造多线程,每个线程处理自已的具体事务。具体处理工作由DoRequest函数完成,由于析构函数不能关闭临时建立的套接字,所以在处理完之后,需要主动关闭。 DoRequest的工作包括在服务器应用视中显示相应的提示语句,这是通过向其发送消息完成的,另外还将显示客户发送的信息,信息由套接字的Receive()函数获得,同样通过向窗口发送消息完成,这里的消息发送窗口由参数pParam确定。最后,调用套接字的.Send()函数向客户端发送一条简短的确认信息。 void DoRequest(LPVOID pParam, CBlockingSocket& sockCon, LPCSOCKADDR psa){ char inbuff[500],text1[200]; wsprintf(text1, "建立连接\r\n"); ::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 500, 65535); ::SendMessage((HWND) pParam, EM_REPLACESEL, (WPARAM) 0, (LPARAM) text1); try{ int len=sockCon.Receive(inbuff,500,20); inbuff[len]='\0'; ::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 500, 65535); ::SendMessage((HWND)pParam,EM_REPLACESEL,(WPARAM)0,(LPARAM)inbuff); sockCon.Send("ok!\r\nBye!",9,5); }catch(CBlockingSocketException* pe) { LogBlockingSocketException(pParam, "服务器:", pe); pe->Delete(); } } 5 客户端应用程序 在客户端应用程序中同样使用了前面介绍的套接字类CBlockingSocket,Winsock地址类CSockAddr和套接字异常类CBlockingSocketException。 这里将主要介绍客户如何建立与服务器的连接,如何向服务器发送消息,以及如何接收和处理服务器返回的消息。 为了实现与服务器的连接,需要在菜单上增加一个连接项,资源标识为ID_BEGIN_LINK,消息映射设置为ON_COMMAND(ID_BEGIN_LINK, OnBeginLink),下面再来分析消息处理函数OnBeginLink的实现: void CTestclientsockView::OnBeginLink() { CBlockingSocket sClient; char inbuff[200]; try { CSockAddr saClient("146.127.35.70",5858);西安新东方学校 sClient.Create(); sClient.Connect(saClient); sClient.Send("hello ,你好!\n",5,5); int len=sClient.Receive(inbuff,200,20); inbuff[len]='\0'; SendMessage(EM_SETSEL, (WPARAM) 500, 65535); SendMessage(EM_REPLACESEL, (WPARAM) 0, (LPARAM) inbuff); sClient.Clo();} catch(CBlockingSocketException* e) { sClient.Cleanup(); LogBlockingSocketException(GetSafeHwnd(), "VIEW:", e); e->Delete(); } } 首先需要建立与服务器的连接,这里创建了一个地址对象saClient,在创建时提供了两个参数,其中“146.127.35.70”为服务器的IP地址,另外将端口号设置为5858,即与服务器应用程序保持一致。接下来创建一个客户端的套接字对象,并通过调用函数Connect建立与服务器的连接。连接建立后,可以向服务器发送信息了,这里用套接字的函数Send()发送了一条简短的欢迎信息,同时调用Receive()函数接收服务器返回的信息。最后向视发送显示消息,将服务器所返回的确认信息显示在主窗口中。 这里还需要说明的是,如果在连接或通信过程中出现异常,无论是客户应用程序还是服务器应用程序都将做出相应的响应。具体的处理工作由函数LogBlockingSocketException实现,这里的参数pParam 为拥有目的窗口的 HWND ,这是由另一个线程提供的。 void LogBlockingSocketException(LPVOID pParam, char* pch, CBlockingSocketException* pe) { CString strGmt = CTime::GetCurrentTime().FormatGmt("%m/%d/%y %H:%M:%S GMT"); char text1[200], text2[50]; pe->GetErrorMessage(text2, 49); wsprintf(text1, "WINSOCK 错误--%s %s -- %s\r\n", pch, text2, (const char*) strGmt); ::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 65534, 65535); ::SendMessage((HWND) pParam, EM_REPLACESEL, (WPARAM) 0, (LPARAM) text1); } 通过调用套接字异常的GetErrorMessage函数可以得到具体的错误信息,同样通过向窗口发送消息的方法,可以将错误信息显示在主窗口中。 如在没有打开服务器应用的情况下,选择开始连接,其执行结果如图1所示: 图1 客户端应用异常处理结果 如果打开了服务器应用程序,再选择客户端应用的开始连接菜单项,此时客户端的执行情况如图2所示: 图2 客户端应用正常执行情况 这里在客户端主窗口中显示的信息是在服务器应用中发送的确认信息。同时可以在服务器应用的主窗口中看到客户端返回给服务器的信息,见图3: 图3 服务器端应用执行情况 |