网络编程(57)—— Windows下使用CAsyncSocket搭建回声服务端和客户端

一、 引言

        CAsyncSocketMFC中对WSAAsyncSelect异步非阻塞通知IO的一个封装我们Windows下使用WSAAsyncSelect实现窗口处理socket消息》一文讨论WSAAsyncSelect用法知道它绑定一个窗口一个socket并注册了我们自定义的消息和需要监视的IO事件类型(FD_ACCEPTFD_READ、FD_WRITE等等绑定socket发生注册的IO事件后,操作系统会给上述窗口发送我们自定义的消息而接下来我们就可以针对事件类型而做不同的处理。CAsyncSocket就是将WSAAsyncSelect进行了封装,内部使用了一个不可见的窗口并隐藏了注册绑定过程其内部实现原理WSAAsyncSelect使用相同。

  下面主要讨论下CAsyncSocket的用法,我们要实现的效果是,创建一个都带界面的服务端和客户端。客户端允许用户输入字符串,然后发送给服务端端,服务端接收客户端的字符串后原样返回,在客户端界面上显示。

二、 CAsyncSocket的主要成员介绍

          CAsyncSocket主要包含以下成员:

BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );

        我们Create创建一个异步socket对象,在Create的参数中我们可以指定端口号、协议类型、注册的事件类型IP地址。如果传参Create提供了默认形参;如果你想要修改,可以使用CAsyncSocket提供Bind、AsyncSelect等接口进行修改

BOOL Listen( int nConnectionBacklog = 5 );

        用来开启监听。

virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL );

       用来接收客户端连接

BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );

      用来连接服务

virtual void OnAccept( int nErrorCode );
virtual void OnClose ( int nErrorCode );
virtual void OnConnect ( int nErrorCode );
virtual void OnReceive ( int nErrorCode );
virtual void OnSend ( int nErrorCode );

       上述函数都是系统的回调函数,在socket发生相应的IO事件时进行调用它们都定义成了虚函数,需要我们进行继承并进行相应的处理。

、封装CAsyncSocket的派生类

       在使用CAsyncSocket时我们需要定义自己的CAsyncSocket派生类,在派生类中我们通过继承虚函数的形式可以自由的处理各类socket的io事件,我们定义的名称叫做CMySocket:

class CMySocket : public CAsyncSocket
{
    ....
}

       在CMySocket中我们定义一个CWnd*类型的成员变量用来接收服务端和客户端窗口的指针:

    CWnd* m_pWnd;

     我们重载OnAccept等回调函数,在每个函数中向m_pWnd发送自定义的消息,这样我们在服务端和客户端的窗口处理函数中就可以处理这些消息。

void CMySocket::OnAccept(int nErrorCode)
{
	int param=ACCEPT;
	if(m_pWnd!=NULL)
		m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
	CAsyncSocket::OnAccept(nErrorCode);
}

void CMySocket::OnReceive(int nErrorCode)
{
	int param=RECIEVE;
	if(m_pWnd!=NULL)
		m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
	CAsyncSocket::OnReceive(nErrorCode);
}

void CMySocket::OnClose(int nErrorCode)
{
	int param=CLOSE;
	if(m_pWnd!=NULL)
		m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
	CAsyncSocket::OnClose(nErrorCode);
}

void CMySocket::OnConnect(int nErrorCode)
{
	int param=CONNECT;
	if(m_pWnd!=NULL)
		m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
	CAsyncSocket::OnConnect(nErrorCode);
}

void CMySocket::OnSend(int nErrorCode)
{
	int param=SEND;
	if(m_pWnd!=NULL)
		m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
	CAsyncSocket::OnSend(nErrorCode);
}

       可以看到,在上述每个虚函数中,我们都调用m_pWnd的SendMessage函数向系统的消息队列中发送了自定义的WM_MYSOCKET消息。并通过WPARAM和LPARAM参数把当前CMySocket对象和代表事件类型的宏附加到消息的参数中。

四、在客户端和服务端中添加消息处理函数

       在服务端我们自定义WM_MYSOCKET的消息处理函数如下

afx_msg LRESULT CServDlg::OnMysocket(WPARAM wParam, LPARAM lParam)
{
	CMySocket* pservSock=(CMySocket*)wParam;
	CMySocket* pClntSock=new CMySocket();
	SOCKADDR_IN clntAddr;
	int clntAddrSz=sizeof(clntAddr);
	int param=*((int*)(lParam));
	int recvLen;
	char buf[BUF_SIZE];
	switch(param)
	{
	case ACCEPT:
		{
			pservSock->Accept(*pClntSock,(SOCKADDR*)&clntAddr,&clntAddrSz);
			pClntSock->m_pWnd=this;
			m_pClnts.AddTail(pClntSock);
		}
		break;
	case RECIEVE:
		{
			int strLen =pservSock->Receive(buf,BUF_SIZE,0);
			pservSock->Send(buf,strLen,0);

		}
		break;
	default:
		break;
	}
	return 0;
}

        在上述消息处理函数中我们new了一个客户端socket的指针:

CMySocket* pClntSock=new CMySocket();

       在这里我们不可以将客户端的socket声明为局部变量,因为CAsyncSocket对象离开作用域中会调用析构函数进行析构。如果我们这里在栈中创建一个clntSock而非new一个pClntSock,OnMysocket调用结束后,已经连接的客户端socket会自动断开连接后续将无法进行sendreceive等操作。

       在处理ACCEPT消息时,我们调用了Accept函数,这里普通的accept函数类似,我们获取到了连接到服务端的pClntSock,接下来窗口的this指针赋值给了pClntSockm_pWnd这点很重要,因为我们在调用pservSock->Send(buf,strLen,0)进行数据的发送时,Send函数内部实际上会调用pClntSockOnSend回调函数,我们需要在这个回调函数中向pClntSock的m_pWnd发送自定义消息。

        在客户端中,我们也要添加一个自定义消息的处理函数:

afx_msg LRESULT CClntDlg::OnMysocket(WPARAM wParam, LPARAM lParam)
{
	CMySocket* pSock=(CMySocket*)wParam;
	int param=*((int*)(lParam));
	int recvLen;
	char buf[BUF_SIZE];
	switch(param)
	{
	case RECIEVE:
		{
			int strLen = pSock->Receive(buf,BUF_SIZE-1,0);
			buf[strLen]=0;
			CString str;
			str.Format("%s",buf);
			m_recv.SetWindowText(str);
		}
		break;
	default:
		break;
	}
	return 0;
}

       上述内容,只是对几处关键性的代码进行了解释,需要全部的代码,请自行Github下载,服务端和客户端的运行效果如下:

客户端:

网络编程(57)—— Windows下使用CAsyncSocket搭建回声服务端和客户端_第1张图片

客户端:

网络编程(57)—— Windows下使用CAsyncSocket搭建回声服务端和客户端_第2张图片


 Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone [email protected]:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL57

你可能感兴趣的:(网络通信编程,网络通信编程)