程序员人生 网站导航

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

栏目:互联网时间:2017-02-22 08:46:16

1、 引言

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

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

2、 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创建1个异步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事件时进行调用它们都定义成了虚函数,需要我们进行继承并进行相应的处理。

3、封装CAsyncSocket的派生类

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

class CMySocket : public CAsyncSocket
{
    ....
}

       在CMySocket中我们定义1个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对象和代表事件类型的宏附加到消息的参数中。

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

       在服务端我们自定义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了1个客户端socket的指针:

CMySocket* pClntSock=new CMySocket();

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

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

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

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⑴,0);
			buf[strLen]=0;
			CString str;
			str.Format("%s",buf);
			m_recv.SetWindowText(str);
		}
		break;
	default:
		break;
	}
	return 0;
}

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

客户端:


客户端:



 Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获得本文源代码:
git checkout NL57

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐