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://blog.csdn.net/PiggyXP/article/details/114908),这里直接上代码。

一、界面大概如下:

二、TCP server 服务端实现代码如下:

2.1创建socket部分:

	//初始化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连接部分:

//服务端开始通信
	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正式通信部分

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值