18.4 | WinSock类
在Visual C++的MFC类库中,提供了两个与WinSock相关的类,分别为CAsyncSocket类和CSocket类。这两个类对WinSock API进行了封装,使得开发Windows Socket应用程序变得简单了。本节将针对CAsyncSocket和CSocket介绍Windows Socket应用程序的开发。
18.4.1 CAsyncSocket类介绍
CAsyncSocket类对WinSock API进行了低级封装,它提供的许多方法直接对应于低层的API函数。在使用CAsyncSocket时,首先需要调用构造函数创建CAsyncSocket对象,然后调用Create方法创建套接字句柄,对于服务器端的套接字,需要调用Listen方法使其处于监听模式,对于客户端套接字,需要调用Connect方法连接服务器。下面介绍CAsyncSocket的主要方法和事件。
(1)Create方法
Create方法用于创建一个Windows 套接字。
语法:
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方法参数说明如表18.9所示。
表18.9 Create方法参数说明
参 数 名 称 | 参 数 描 述 |
nSocketPort | 标识套接字端口 |
nSocketType | 标识套接字类型,默认为流式套接字 |
lEvent | 表示套接字能够接受的事件 |
lpszSocketAddress | 表示套接字的网络地址 |
(2)GetLastError方法
GetLastError方法用于获得最后一次操作失败的状态信息。
语法:
static int GetLastError( );
(3)GetPeerName方法
GetPeerName方法用于获得套接字连接的IP地址。
语法:
BOOL GetPeerName( CString& rPeerAddress, UINT& rPeerPort );
BOOL GetPeerName( SOCKADDR* lpSockAddr, int* lpSockAddrLen );
GetPeerName方法参数说明如表18.10所示。
表18.10 GetPeerName方法参数说明
参 数 名 称 | 参 数 描 述 |
rPeerAddress | 用于接收函数返回的IP地址 |
rPeerPort | 用于记录端口号 |
lpSockAddr | 是一个sockaddr结构指针,用于记录套接字名称 |
lpSockAddrLen | 用于确定lpSockAddr的大小 |
(4)Accept方法
Accept方法用于接受一个套接字的连接。
语法:
virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int*
lpSockAddrLen = NULL );
rConnectedSocket:当前连接的套接字引用。
lpSockAddr:一个SOCKADDR结构指针,用于记录套接字地址。
lpSockAddrLen:确定lpSockAddr的大小。
(5)Bind方法
Bind方法将本机地址关联到套接字上。
语法:
BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL );
BOOL Bind ( const SOCKADDR* lpSockAddr, int nSockAddrLen );
Bind方法参数说明如表18.11所示。
表18.11 Bind方法参数说明
参 数 名 称 | 参 数 描 述 |
nSocketPort | 标识套接字端口 |
lpszSocketAddress | 标识IP地址 |
lpSockAddr | 是一个SOCKADDR结构指针,该结构记录了套接字的地址信息 |
nSockAddrLen | 确定lpSockAddr的大小 |
(6)Close方法
Close方法用于关闭套接字。
语法:
virtual void Close( );
说明:
Close方法释放套接字描述符,因此,在调用该方法后,再对其进行访问,会导致错误。当CAsyncSocket对象被释放时,会自动调用Close方法。
(7)Connect方法
Connect方法用于建立一个套接字连接。
语法:
BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
BOOL Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen );
Connect方法参数说明如表18.12所示。
表18.12 Connect方法参数说明
参 数 名 称 | 参 数 描 述 |
lpszHostAddress | 是主机的IP地址或网址 |
nHostPort | 标识当前应用程序的端口 |
lpSockAddr | 是一个SOCKADDR结构指针,该结构标识套接字地址信息 |
nSockAddrLen | 确定lpSockAddr的大小 |
(8)Listen方法
Listen方法用于监听套接字的连接请求。
语法:
BOOL Listen( int nConnectionBacklog = 5 );
nConnectionBacklog:表示等待连接的最大队列长度。
(9)Receive方法
Receive方法用于从一个套接字上接收数据。
语法:
virtual int Receive( void* lpBuf, int nBufLen, int nFlags = 0 );
lpBuf:是接收数据的缓冲区。
nBufLen:确定缓冲区的长度。
nFlags:确定函数的调用模式,可选值如下。
l MSG_PEEK用来查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走。
l MSG_OOB用来处理Out-Of-Band数据。
(10)ReceiveFrom方法
ReceiveFrom方法用于接收面向无连接传递的数据(数据报)。
语法:
int ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort,
int nFlags = 0 );
int ReceiveFrom( void* lpBuf, int nBufLen, SOCKADDR* lpSockAddr, int* lpSockAddrLen,
int nFlags = 0 );
ReceiveFrom方法参数说明如表18.13所示。
表18.13 ReceiveFrom方法参数说明
参 数 名 称 | 参 数 描 述 |
lpBuf | 是接收数据的缓冲区 |
nBufLen | 是缓冲区的大小 |
rSocketAddress | 用于接收数据报的目的地(IP地址) |
rSocketPort | 用于记录端口号 |
lpSockAddr | 是一个SOCKADDR结构指针,用于记录套接字地址信息 |
lpSockAddrLen | 确定lpSockAddr的大小 |
nFlags | 标识函数调用方式 |
(11)Send方法
Send方法用于发送数据到连接的套接字上。
语法:
virtual int Send( const void* lpBuf, int nBufLen, int nFlags = 0 );
lpBuf:标识要发送数据的缓冲区。
nBufLen:确定缓冲区的大小。
nFlags:标识函数调用方法。
(12)SendTo方法
SendTo方法既可以在面向连接的套接字上发送数据,也可以在面向无连接的套接字上发送数据。
语法:
int SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress =
NULL, int nFlags = 0 );
int SendTo( const void* lpBuf, int nBufLen, const SOCKADDR* lpSockAddr, int nSockAddrLen,
int nFlags = 0 );
SendTo方法参数说明如表18.14所示。
表18.14 SendTo方法参数说明
参 数 名 称 | 参 数 描 述 |
lpBuf | 标识要发送数据的缓冲区 |
nBufLen | 确定缓冲区大小 |
nHostPort | 确定主机端口号 |
lpszHostAddress | 确定主机地址(可以是域名或IP地址) |
lpSockAddr | 是一个SOCKADDR结构指针,用于确定主机套接字地址信息 |
nSockAddrLen | 确定lpSockAddr的大小 |
nFlags | 标识函数调用方式 |
(13)ShutDown方法
ShutDown方法用于在套接字上断开数据的发送或接收。
语法:
BOOL ShutDown( int nHow = sends );
nHow:确定ShutDown函数的行为,0表示不允许接收,1表示不允许发送,2表示不允许接收和发送。
(14)OnAccept事件
OnAccept事件在套接字接受连接请求时触发。
语法:
virtual void OnAccept( int nErrorCode );
nErrorCode:标识错误代码。
(15)OnClose事件
OnClose事件在连接的套接字被关闭时触发。
语法:
virtual void OnClose( int nErrorCode );
nErrorCode:标识错误代码。
(16)OnConnect事件
OnConnect事件在套接字被连接后触发。
语法:
virtual void OnConnect( int nErrorCode );
nErrorCode:标识错误代码。
(17)OnOutOfBandData事件
OnOutOfBandData事件在接收数据的套接字收到Out-Of-Band数据时触发。
语法:
virtual void OnOutOfBandData( int nErrorCode );
nErrorCode:标识错误代码。
(18)OnReceive事件
OnReceive事件在套接字有数据被接收时触发。
语法:
virtual void OnReceive( int nErrorCode );
nErrorCode:标识错误代码。
(19)OnSend事件
OnSend事件在套接字发送数据时触发。
语法:
virtual void OnSend( int nErrorCode );
nErrorCode:标识错误代码。
18.4.2 CSocket类介绍
CSocket类派生于CAsyncSocket,对Windows Socket API进行更高层次的封装。它支持同步操作,可以单独使用,但通常情况下与CSocketFile、 CArchive类一起实现数据的发送和接收。下面介绍CSocket的主要方法。
(1)Create方法
Create方法用于创建一个套接字。
语法:
BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR
lpszSocketAddress = NULL );
nSockPort:确定套接字端口号。
nSocketType:确定套接字类型。
lpszSocketAddress:确定套接字IP地址。
(2)Attach方法
Attach方法将套接字句柄关联到CSocket对象上。
语法:
BOOL Attach( SOCKET hSocket );
hSocket:标识套接字句柄。
(3)FromHandle方法
FromHandle方法根据套接字句柄获得CSocket对象指针。
语法:
static CSocket* PASCAL FromHandle( SOCKET hSocket );
hSocket:标识套接字句柄。
(4)IsBlocking方法
IsBlocking方法用于判断套接字是否处于阻塞状态。如果返回值为零,表示处于非阻塞状态;非零,表示处于阻塞状态。
语法:
BOOL IsBlocking( );
(5)CancelBlockingCall方法
CancelBlockingCall方法用于取消阻塞模式。
语法:
void CancelBlockingCall( );
说明:
当套接字发送或接收数据时,它将处于阻塞模式,直到其操作完成,才将控制权返回给程序。调用CancelBlockingCall方法将取消套接字的阻塞模式,将控制权立即返回给程序。
18.4.3 使用WinSock类设计网络聊天室
在18.3.3节中介绍了如何使用套接字函数设计网络聊天室,下面将使用WinSock类来设计一个网络聊天程序。程序依然由两个实例组成,即服务器端和客户端。服务器端接受连接并转发信息,客户端负责连接服务器,并发送信息。
服务器端程序设计步骤如下。
ch1808实例位置:mr/18/sl/08
(1)创建一个基于对话框的工程,设计对话框资源如图18.16所示。
图18.16 服务器端资源设计窗口
(2)从CSocket类派生一个新类CClientSocket,在头文件中引用对话框的头文件和afxsock.h头文件,并对对话框类进行前导声明。
(3)处理“监听”按钮的单击事件,开始监听客户端。
void CServerDlg::OnOK()
{
this->UpdateData();
m_pSocket = new CServerSocket(this);
if (!m_pSocket->Create(70))
{
MessageBox("套接字创建失败");
delete m_pSocket;
m_pSocket = NULL;
return;
}
if (!m_pSocket->Listen())
MessageBox("监听失败");
}
(4)在对话框中添加AcceptConnect方法,用于接受客户端的连接。
void CServerDlg::AcceptConnect()
{
CClientSocket* socket = new CClientSocket(this);
//接受客户端的连接
if (m_pSocket->Accept(*socket))
m_socketlist.AddTail(socket);
else
delete socket;
}
(5)在对话框中添加ReceiveData方法,用于接收客户端传来的数据。
void CServerDlg::ReceiveData(CClientSocket* socket)
{
char bufferdata[BUFFERSIZE];
//接收客户端传来的数据
int result = socket->Receive(bufferdata,BUFFERSIZE);
bufferdata[result] = 0;
POSITION pos = m_socketlist.GetHeadPosition();
//将数据发送给每个客户端
while (pos!=NULL)
{
CClientSocket* socket = (CClientSocket*)m_socketlist.GetNext(pos);
if (socket != NULL)
socket->Send(bufferdata,result);
}
}
客户端程序设计步骤如下。
ch1809实例位置:mr/18/sl/09
(1)创建一个基于对话框的工程,设置对话框资源如图18.17所示。
(2)在应用程序的InitInstance方法中初始化套接字。
//初始化套接字
WSADATA wsd;
AfxSocketInit(&wsd);
(3)从CSocket类中派生一个子类CMysocket。在头文件中引用Afxsock.h头文件,目的是使用CSocket类;引用主对话框的头文件,并对主对话框进行前导声明,因为在CMysocket类中需要定义主对话框类指针。
(4)处理“发送”按钮的单击事件,发送数据到服务器。
图18.17 客户端窗口设计
void CClientDlg::OnButtonsend()
{
// TODO: Add your control notification handler code here
CString str,temp;
m_info.GetWindowText(str);
if (str.IsEmpty()|m_name.IsEmpty())
return;
temp.Format("%s说: %s",m_name,str);
int num = pMysocket->Send(temp.GetBuffer(temp.GetLength()),temp.GetLength());
m_info.SetWindowText("");
m_info.SetFocus();
}
(5)在主对话框中定义一个CMysocket对象指针。添加ReceiveData成员方法,用于接收服务器传来的数据。
void CClientDlg::ReceiveData()
{
char buffer[200];
//接收传来的数据
int factdata = pMysocket->Receive(buffer,200);
buffer[factdata] = '/0';
CString str;
str.Format("%s",buffer);
int i = m_list.GetCount();
//将数据添加到列表框中
m_list.InsertString(m_list.GetCount(),str);
}
(6)处理“连接”按钮的单击事件,连接服务器。
void CClientDlg::OnButtonjoin()
{
// TODO: Add your control notification handler code here
UpdateData(true);
CString servername = m_servername; //读取服务器名称
int port;
port = 70; //获取端口
if (! pMysocket->Connect(servername,port)) //连接服务器
{
MessageBox("连接服务器失败!");
return;
}
CString str;
str.Format("%s----->%s",m_name,"进入聊天室");
int num = pMysocket->Send(str.GetBuffer(0),str.GetLength());
}
运行程序,效果如图18.18、图18.19、图18.20所示。
图18.18 服务器端窗口
图18.19 客户端窗口1 图18.20 客户端窗口2