- ·上一篇内容:详解Visual C++事件编程
- ·下一篇内容:利用NetBIOS进行Windows网络编程
Visual C++之WinSock编程介绍
考虑到一个应用程序通常用与"时间"服务对应的端口来和服务器连接,而服务器提供某种机制来通知用户使用另一端口。因此 getservbyname()函数返回的端口号已经是网络顺序了,可以直接用来组成一个地址,而不需要进行转换。然而如果用户输入一个数,而且指定使用这一端口号,应用程序则必须在使用它建立地址以前,把它从主机顺序转换成网络顺序(使用htons()函数)。相应地,如果应用程序希望显示包含于某一地址中的端口号(例如从getpeername()函数中返回的),这一端口号就必须在被显示前从网络顺序转换到主机顺序(使用ntohs()函数)。
由于 Intel处理器和Internet的字节顺序是不同的,上述的转换是无法避免的,应用程序的编写者应该使用作为Windows Sockets API一部分的标准的转换函数,而不要使用自己的转换函数代码。因为将来的Windows Sockets实现有可能在主机字节顺序与网络字节顺序相同的机器上运行。因此只有使用标准的转换函数的应用程序是可移植的。
在MFC中 MS为套接口提供了相应的类CAsyncSocket和CSocket,CAsyncSocket提供基于异步通信的套接口封装功能,CSocket则是由CAsyncSocket派生,提供更加高层次的功能,例如可以将套接口上发送和接收的数据和一个文件对象(CSocketFile)关联起来,通过读写文件来达到发送和接收数据的目的,此外CSocket提供的通信为同步通信,数据未接收到或是未发送完之前调用不会返回。此外通过MFC类开发者可以不考虑网络字节顺序和忽略掉更多的通信细节。
在一次网络通信/连接中有以下几个参数需要被设置:本地IP地址 - 本地端口号 - 对方端口号 - 对方IP地址。左边两部分称为一个半关联,当与右边两部分建立连接后就称为一个全关联。在这个全关联的套接口上可以双向的交换数据。如果是使用无连接的通信则只需要建立一个半关联,在发送和接收时指明另一半的参数就可以了,所以可以说无连接的通信是将数据发送到另一台主机的指定端口。此外不论是有连接还是无连接的通信都不需要双方的端口号相同。
在创建CAsyncSocket对象时通过调用
BOOL CAsyncSocket::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 )通过指明lEvent所包含的标记来确定需要异步处理的事件,对于指明的相关事件的相关函数调用都不需要等待完成后才返回,函数会马上返回然后在完成任务后发送事件通知,并利用重载以下成员函数来处理各种网络事件:
标记 | 事件 | 需要重载的函数 |
FD_READ | 有数据到达时发生 | void OnReceive( int nErrorCode ); |
FD_WRITE | 有数据发送时产生 | void OnSend( int nErrorCode ); |
FD_OOB | 收到外带数据时发生 | void OnOutOfBandData( int nErrorCode ); |
FD_ACCEPT | 作为服务端等待连接成功时发生 | void OnAccept( int nErrorCode ); |
FD_CONNECT | 作为客户端连接成功时发生 | void OnConnect( int nErrorCode ); |
FD_CLOSE | 套接口关闭时发生 | void OnClose( int nErrorCode ); |
我们看到重载的函数中都有一个参数nErrorCode,为零则表示正常完成,非零则表示错误。通过int CAsyncSocket::GetLastError()可以得到错误值。
下面我们看看套接口类所提供的一些功能,通过这些功能我们可以方便的建立网络连接和发送数据。
* BOOL CAsyncSocket::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 );用于创建一个本地套接口,其中nSocketPort为使用的端口号,为零则表示由系统自动选择,通常在客户端都使用这个选择。 nSocketType为使用的协议族,SOCK_STREAM表明使用有连接的服务,SOCK_DGRAM表明使用无连接的数据报服务。 lpszSocketAddress为本地的IP地址,可以使用点分法表示。
* BOOL CAsyncSocket::Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )作为等待连接方时产生一个网络半关联,或者是使用UDP协议时产生一个网络半关联。
* BOOL CAsyncSocket::Listen( int nConnectionBacklog = 5 )作为等待连接方时指明同时可以接受的连接数,请注意不是总共可以接受的连接数。
* BOOL CAsyncSocket::Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )作为等待连接方将等待连接建立,当连接建立后一个新的套接口将被创建,该套接口将会被用于通信。
* BOOL CAsyncSocket::Connect( LPCTSTR lpszHostAddress, UINT nHostPort );作为连接方发起与等待连接方的连接,需要指明对方的IP地址和端口号。
* void CAsyncSocket::Close( );关闭套接口。
* int CAsyncSocket::Send( const void* lpBuf, int nBufLen, int nFlags = 0 ) int CAsyncSocket::Receive( void* lpBuf, int nBufLen, int nFlags = 0 );在建立连接后发送和接收数据,nFlags为标记位,双方需要指明相同的标记。
*int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )
int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString&rSocketAddress, UINT& rSocketPort, int nFlags = 0 );对于无连接通信发送和接收数据,需要指明对方的IP地址和端口号,nFlags为标记位,双方需要指明相同的标记。
我们可以看到大多数的函数都返回一个布尔值表明是否成功。如果发生错误可以通过int CAsyncSocket::GetLastError()得到错误值。
由于CSocket由CAsyncSocket派生所以拥有CAsyncSocket的所有功能,此外你可以通过BOOL CSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL )来创建套接口,这样创建的套接口没有办法异步处理事件,所有的调用都必需完成后才会返回。
在上面的介绍中我们看到MFC提供的套接口类屏蔽了大多数的细节,我们只需要做很少的工作就可以开发出利用网络进行通信的软件。