您的位置:首页 > 理论基础 > 计算机网络

socket网络通信(重叠I/O方式实现)包含4种方式

2019-05-07 11:21 399 查看

  采用重叠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部分实现的代码,这里篇幅有限,就不都贴出来了。需要的话可以查看下面的地址:

https://download.csdn.net/download/hill_guo/11163639

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: