socket网络通信(重叠I/O方式实现)包含4种方式
采用重叠I/O方式实现的socket网络编程,异步非阻塞方式,代码效率比阻塞式的socket编程方式高。实现了TCP server,TCP client,UDP server,UDP client,四种方式可选,可以使用在各种场合用于监控网络数据。本程序只支持单客户端和服务端的应用场合。代码封装成库形式,非常方便移植。平台使用的是VC6.0,语言用的是C++,MFC做的界面。使用ws2_32.lib实现的各种功能,并没有使用MFC的socket库。
先致敬博主小猪,相关理论知识可以看他的相关文章(https://www.geek-share.com/detail/2306952020.html),这里直接上代码。
一、界面大概如下:
二、TCP server 服务端实现代码如下:
2.1创建socket部分:
[code] //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if( WSAStartup(sockVersion, &wsaData) != 0 ) //加载套接字版本 { AfxMessageBox("load tcp server socket error !"); return 0; } AfxMessageBox("load tcp server socket successfully!"); //创建套接字 SOCKET TcpSrvSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(TcpSrvSocket == INVALID_SOCKET) { AfxMessageBox("create tcp server socket error !"); return 0; } pThread->m_Srvsock = TcpSrvSocket; //传递套接字给主线程,用于关闭套接字。 AfxMessageBox("create tcp server socket successfully !"); //绑定套接字 sockaddr_in TcpSrvAddr; memset( &TcpSrvAddr,0,sizeof(TcpSrvAddr) ); TcpSrvAddr.sin_addr.S_un.S_addr = /*htonl(INADDR_ANY);*/inet_addr(pThread->m_LocalIpAddr); TcpSrvAddr.sin_family = AF_INET; TcpSrvAddr.sin_port = htons(pThread->m_LocalPort); if( bind(TcpSrvSocket,(SOCKADDR*)&TcpSrvAddr,sizeof(TcpSrvAddr)) == SOCKET_ERROR ) { AfxMessageBox("bind tcp server socket error !"); return 0; } AfxMessageBox("bind tcp server socket successfully."); //监听套接字 if( listen(TcpSrvSocket,5) == SOCKET_ERROR ) { AfxMessageBox("listen tcp server socket error !"); return 0; } AfxMessageBox("listen tcp server socket successfully.");
2.2建立socket连接部分:
[code]//服务端开始通信 SOCKET CommSocket; sockaddr_in RemoteAddr; memset( &RemoteAddr,0,sizeof(RemoteAddr) ); int nAddrlen = sizeof(RemoteAddr); //开始连接,等待客户端连接得到accept套接字 CommSocket = accept(TcpSrvSocket,(SOCKADDR*)&RemoteAddr,&nAddrlen); //blocking the thread until accept successfully. if(CommSocket == INVALID_SOCKET) { AfxMessageBox("accept tcp server socket error !"); return 0; } pThread->m_sock = CommSocket; //传递accept套接字给主线程,用于发送数据。 AfxMessageBox("accept tcp server socket successfully.");
2.3正式通信部分
[code]while(1) { //发出操作请求,操作指:接收数据。 int tmpResult = 0; tmpResult = WSARecv(CommSocket,&pThread->m_databuf,1,&recvedLength,&Flags,&pThread->m_TcpSrvOverlapped,NULL); if( tmpResult == SOCKET_ERROR ) { //发生错误,关闭套接字, 结束线程。 if(WSAGetLastError() != WSA_IO_PENDING) { closesocket( CommSocket ); WSACloseEvent( pThread->m_EventArray[/*pThread->m_dwEventTotal*/1] ); break; } else { //it is normal when returns WSA_IO_PENDING. do nothing except for waiting. } } /* **等待事件(重叠操作——关闭线程,接收数据,发送数据)变为有信号状态,否则阻塞线程。 **注意:在调用WSAWaitForMultipleEvents前就要确定事件的数量和内容。 ** 不能在已经调用了WSAWaitForMultipleEvents阻塞线程后在别的线程再注册事件和增加事件数量。 ** 这样WSAWaitForMultipleEvents将不会响应后面注册的事件。 */ DWORD dwIndex = 0; dwIndex = WSAWaitForMultipleEvents(/*pThread->m_dwEventTotal + 1*/3, pThread->m_EventArray, FALSE, WSA_INFINITE, FALSE); #if 1 //有事件(重叠操作——接收)变为有信号状态,解除了阻塞,线程继续往下执行。 dwIndex = dwIndex - WSA_WAIT_EVENT_0; WSAResetEvent(pThread->m_EventArray[dwIndex]); //复位事件对象句柄 #endif //获取重叠操作(重叠操作——接收)结果,第4个参数为false,不阻塞线程。 DWORD dwBytesTransferred; WSAGetOverlappedResult( CommSocket, &pThread->m_TcpSrvOverlapped, &dwBytesTransferred, FALSE, &Flags); if(dwBytesTransferred == 0) //表示对方关闭了套接字,则退出线程。 { closesocket( CommSocket ); WSACloseEvent( pThread->m_EventArray[/*pThread->m_dwEventTotal*/1] ); // 关闭事件 ::SendMessage(pThread->p_Owner->m_hWnd, WM_COMM_RXCHAR, 0, 1); WSACleanup(); AfxEndThread(100); //结束线程 break; } switch(dwIndex) { case 0: //shutdown event WSACloseEvent(pThread->m_EventArray[0]); //关闭事件对象句柄 WSACloseEvent(pThread->m_EventArray[1]); //关闭事件对象句柄 WSACloseEvent(pThread->m_EventArray[2]); //关闭事件对象句柄 closesocket(TcpSrvSocket); closesocket( CommSocket ); WSACleanup(); AfxEndThread(100); //结束线程 break; case 1: //read event /* **最后,处理数据,线程执行到这里,说明有数据接收成功。 **投递一个消息给父窗口,在父窗口主线程中处理数据。 **注意:这里使用的是sendmessage,而不是postmessage,堵塞线程,直到函数返回才继续线程。 */ WSAResetEvent(pThread->m_EventArray[1]); //复位事件对象句柄 ::SendMessage(pThread->p_Owner->m_hWnd, WM_COMM_RXCHAR, 0, 0); break; case 2: //write event WSAResetEvent(pThread->m_EventArray[2]); //复位事件对象句柄 AfxMessageBox(" tcp server socket send data successfully."); break; default: break; } }
2.4说明:
整个代码注释很完整,非常仔细,可以慢慢看。 需要解释下2.3部分,巧妙的设计了3个事件,接收数据事件——当socket有消息接收到,就产生这个消息,线程解除阻塞,往下执行,postmessage给主窗口,处理数据;发送数据事件——在主线程创建一个发送事件并且使其变为有信号状态,然在这里线程响应发送事件,发送数据;以及关闭线程事件——在主线程创建一个关闭线程事件,并使其变为有信号状态,在这里响应事件,安全退出线程。
当socket客户端有退出动作的时候,捕获到退出消息,然后postmessage给主线程,让主窗口知道连接已经断开,然后安全退出线程。相当nice。
三、其他部分
还有TCP client,UDP server,UDP client部分实现的代码,这里篇幅有限,就不都贴出来了。需要的话可以查看下面的地址:
- JAVA系列课程讲座一:使用Socket通信实现网络通信程序(TCP方式)
- 网络编程之Socket通信实现文件的复制
- DirectX编程:C#中利用Socket实现网络语音通信
- 使用基于Android网络通信的OkHttp库实现Get和Post方式简单操作服务器JSON格式数据
- 通信网络实验——UDP 的socket实现
- Android网络编程之Socket通信实现简单聊天室
- JavaSE入门学习49:Socket网络通信编程(三)四通过Socket实现TCP编程
- 通过套接字(socket)和UDP协议实现网络通信
- 网络通信 Socket 实现TCP
- C# 小规模网络远程调用的基类(基于Socket方式)实现
- Java系列讲座二:Socket网络通信实现聊天软件项目讲解(UDP)
- Java网络编程——使用NIO实现非阻塞Socket通信
- Java网络编程ServerSocket的实现服务器与用户之间的通信的基本步骤
- Android socket通信: 一问一答,一问多答客户端网络请求工具类的实现
- linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)
- 利用socket实现Windows与Linux平台间的网络通信
- 使用SuperSocket实现TLV自定义协议网络通信的Demo
- JAVA系列课程讲座二:使用Socket通信实现简单聊天通信程序(UDP方式)
- Linux Socket 网络编程 基于GTK+ 的多线程实现的局域网通信软件
- Java网络编程——使用NIO实现非阻塞Socket通信