2.4.1 澄清一些概念
Socket 在英文屮是“插座”的意思,它的设计者实际上是暗指电话插座。因为在 Socket 环境下编程很像是模拟打电话,Internet 的 IP 就是电话号码,要打电话,需要电话插座,在 程序中就是向系统申请一个 Socket,以后两台机器上的程序“交谈”都是通过这个 Socket 来 进行的。对程序员来说,也可以把 Socket 看成一个文件指针,只要向指针所指的文件读写数 据,就可以实现双向通信。利用 Socket 进行通信,有两种主要的方式。第一种是面向连接的 流方式。顾名思义,在这种方式下,两个通信的应用程序之间先要建立一种连接链路,其过 程好像在打电话。一台计算机(电话)要想和另一台计算机(电话)进行数据传输(通 话),须首先获得一条链路,只有确定了这条通路之后,数据(通话)才能被正确接收和发 送,这种方式对应的是 TCP(Transport Control Protocol)。第二种叫做无连接的数据报文方 式,这时两台计算机像是把数据放在一个信封里,通过网络寄给对方,信在传送的过程中有 可能会残缺不全,而且后发出的信也有可能会先收到,它对应的是 UDP(User Datagram Protocol)。
流方式的特点是通信可靠,对数据有校验和重发的机制,通常用来做数据文件的传输,
如 FTP、Telnet 等,数据报文方式由于取消了重发校验机制,能够达到较高的通信速率,可 用于对数据可靠性要求不高的通信,如实时的语音、图像转送和广播消息等。
在 ISO 的 OSI 网络七层协议中,WinSock 主要负责控制数据的输入和输出,也就是传输 层和网络层。WinSock 屏蔽了数据链路层和物理层,它的出现给 Windows 下的网络编程带来 了巨大的变化。
套接字有同步阻塞方式和异步非阻塞方式两种使用方法。同步和异步往往都是针对一个 函数来说的,“同步”就是函数直到其要执行的功能全部完成时才返回,而“异步”则是函数 仅仅做一些简单的工作,然后马上返回,所要实现的功能留给别的线程或者消息循环去完成。
阻塞方式的套接字简单、易用,但效率低。相比之下,异步套接字使用复杂,但效率很 高。本章实例使用的是阻塞方式。
2.4.2 WinSock 编程原理
Windows Sockets API 是 Windows 下的网络应用程序接口,用于网络通信。API 函数有 1.1 版和 2.0 版,称为 WinSockl 和 WinSock2,通过前缀 WSA 区分。2.0 版有良好的向后兼 容性,任何使用 1.1 版的源代码、二进制文件和应用程序都可以不加修改地在 2.0 规范下使 用。现在基本上都使用 2.0 版本进行开发,它主要是为了适应近年来网络技术的迅猛发展,
特别是多媒体网络技术迅速发展的需要,通过制定 Windows Sockets2 规范来提供一个与协议 无关的网络传输接口。
Windows Sockets 使用套接字进行编程,套接字编程是面向客户端/服务器模型而设计的,
因此系统中需要客户端和服务器两个不同类型的进程,根据连接类型的不同,对于面向连接的 TCP 服务和无连接的 UDP 服务,服务器分别采取不同的处理操作来对客户提供服务。
第 2 章 扫描可控计算机 39
对于面向连接的 TCP 服务,服务器需要等待客户端向其提出的建立连接的申请,一旦 接收到客户端的连接请求,服务器返回一个新的套接字描述符,通过该描述符调用数据传输 函数即可与客户端进行数据的收发。
对于无连接的 UDP 服务,服务器通常是面向事务处理的,服务器和客户端在传输数据 之前不需要进行连接的申请和建立,数据传输结束后,直接关闭套接字即可完成一次通信。
WinSock 中的主要函数如下:
WinSock 的打开——WSAStartup()
服务器建立套接字——socket()
服务器绑定端口——bind()
客户端提出连接申请——connect()
服务器端接受客户端的连接请求——accept()
数据的传送——send()和 recv()
关闭套接字——closesocket()
关闭 WinSock——WSACleanup()
这些函数的具体定义信息前面已经详细介绍了,这里就不再赘述。
2.4.3 MFC 网络编程
MFC 中有两个主要的网络编程类:CAsyncSocket 类和 CSocket 类。CAsyncSocket 类用 面向对象的方法封装了 WinSock,CSocket 类是 CAsyncSocket 类的子类。这两个类是 Visual C++环境中网络编程经常使用的两个类。
1.CAsyncSocket 类介绍
CAsyncSocket 类对 Windows Sockets API 函数进行了封装,它是从 CObject 类派生出来 的。该类在非常低的级别上封装 Windows Sockets API。CAsyncSocket 适合那些对网络通信 细节很了解,但希望利用回调的便利通知网络事件的程序员使用。如果想利用 Windows Sockets 方便地处理 MFC 应用程序中的多个网络协议,而又不想放弃灵活性,可以使用 CAsyncSocket,但是程序员必须自己处理阻塞,字节序的差异和 Unicode 多字节字符集
(MBCS)的转换。CAsyncSocket 类的重要函数如下:
构造套接字对象——Create()
接受套接字的连接请求——Accept()
将本机地址绑定到套接字——Bind()
建立连接——Connect()
监听连接请求——Listen()
发送数据——Send()和 SendTo()
接受数据——Receive()和 ReceiveFrom()
禁止发送接收数据——ShutDown()
关闭套接字——Close()
这些函数中大部分在前面程序代码详解里已经详细介绍过,这里不再赘述。没有介绍的 函数如下:
将本机地址绑定到套接字——Bind()
40 Visual C++网络编程技术
该函数将本机地址绑定到套接宇,在 Connect 或者 Listen 之前可以被调用。在能接受连 接请求前,监听的服务器套接字必须选择一个端口号并调用 Bind 将本地名分配给未命名的 Windows 套接字,使其知道自己的地址和端口号,如果函数调用成功则返回非 0 值,否则返 0 值。其函数原型如下:
BOOL Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL);
参数 nSocketPort:要绑定的套接字端口号。
参数 lpszSocketAddress:要绑定的套接字网络地址。
BOOL Bind (const SOCKADDR* IpSockAddr, int nSockAddrLen);
参数 lpSockAddr:指向 SOCKADDR 结构的指针。
参数 nSockAddrLen:IpSockAddr 指针中地址的字节长度。
发送数据——SendTo()
该函数与 Send()类似,也是通过数据报或者数据流套接字发送数据,如果函数成功调 用,则返回发送的字节总数,否则返回 SOCKET_ERROR。其函数原型如下:
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);
参数 lpBuf:要发送的数据缓冲区地址。
参数 nBufLen:lpBuf 缓冲区的字节长度。
参数 nHostPort:套接字应用程序的端口号。
参数 lpszHostAddress:被连接的套接字的网络地址。
参数 lpSockAddr:SOCKADDR 结构的指针。
参数 nSockAddrLen:指针所指向结构中的地址的字节长度的指针。
参数 nFlags:函数的调用标志,其取值为 MSG_DONTROUTE 和 MSG_OOB 的组合,
默认值为 0。
接收数据——ReceiveFrom()
该 函 数 从 面 向 连 接 流 或 者 无 连 接 的 数 据 报 套 接 字 接 收 数 据 , 并 将 源 地 址 存 放 在 SOCKADDR 结构或者 rSockeLAddress 中。如果函数调用成功,则返回所读入的字节数,如果 连接被关闭,则返回 0,如果函数调用失败,则返回 SOCKET_ERROR。其函数原型如下:
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);
参数 lpBuf:接收数据的缓冲区。
参数 nBufLen:缓冲区的字节节长度。
参数 rSocketAddress:一个点分隔的字符串 IP 地址的 CString 对象的引用。
参数 rSocketPort:端口号的 UINT 类型的引用。
参数 lpSockAddr:返回源地址的 SOCKADDR 结构指针。
参数 lpSockAddrLen:lpSockAddr 的字节长度。
参数 nFlags:用来表示函数的实现,值为 MSG_OOB 和 MSG_PEEK 项的组合,通常默
第 2 章 扫描可控计算机 41
认为 0。
禁止发送接收数据——ShutDown()
该函数用于禁止发送或者接收数据,如果调用成功返回非 0 值,否则返回 0 值。其函数 原型如下:
BOOL ShutDown (int nHow=sends) ;
参数 nHow:不允许的操作,可以取值为 SD_RECENE、SD_SEND 或 SD_BOTH,这三 个值的含义按照字面理解即可。
2.CSocket 类介绍
CSocket 类是 CAsyncSocket 类的派生类,它继承了 Windows Sockets API 封装函数。实 现了比 CAsyncSocket 类对 Windows Sockets 更高层的抽象。它与类 CSocketFile 和 CArchive 共同合作完成对发送数据、接收数据的管理,CSocket 类提供了对于同步操作 CArchive 对象 非常重要的阻塞功能,使程序员在管理数据的发送和接收的工作变得简单。
CSocket 类提供了阻塞的访问方式,这对于 CArchive 类的同步操作是必需的,其成员函 数如 Receive()、Send()、ReceiveFrom()、SendToO 和 Accept()不会像 WinSock 中的函数一样 返回错误,这些函数会等待直到操作完成。
CSocket 类的几个重要成员函数:
创建套接字并将其同一个对象绑定起来——Create()
确定一个阻塞是否正在执行——IsBlocking() 其函数原型如下:
BOOL IsBlocking();
如果套接字是阻塞状态返回非 0 值,否则返回 0 值。
返回一个指向 CSocket 对象的指针——FromHandle()
给定一个 Socket 句柄,返回一个指向 CSocket 的指针,如果没有 CSocket 对象绑定到这 个句柄上,则该函数返回 NULL,并且不创建临时的对象。其函数原型如下:
static CSocket* PASCAL FromHandle( SOCKET hSocket );
参数 hSocket 为套接字句柄。
把一个 Socket 句柄绑定到一个 CSocket 对象上——Attach()
该函数把一个 Socket 句柄绑定到一个 CSocket 对象上,SOCKET 句柄被保存到 CSocket 对象的成员变量 m_hSocket 中。如果调用成功返回非 0 值,否则返回 0 值。其函数原型如下:
BOOL Attach(SOCKET hSocket) ; 参数 hSocket 为一个套接字的句柄。
取消一个正在进行中的阻塞调用——CancelBlockingCall()
调用该函数,原始的阻塞调用会立即终止并返回 WSAEINTR 错误。如果是阻塞的 Connect()操作,Windows 套接字可以实现立即终止这个阻塞的调用,但是不可能立即释放这 个套接字的资源,必须等到连接完成或者超时后资源才会被释放。其函数原型如下:
void CancelBlockingCall();
过滤和响应 Windows 特定的消息——OnMessagePending()
该函数是一个重载函数,用来过滤和响应 Windows 特定的消息,是一个高级的可重载 函数。如果 Windows 消息成功处理了则返回非 0 值,否则返回 0 值。其函数原型如下:
virtual BOOL OnMessagePending() ;
42 Visual C++网络编程技术
2.4.4 WinInet 编程技术
一个 Internet 客户端程序的目的是通过 Internet 协议如 HTTP、FTP 等来存取网络数据源
(服务器)的信息。客户端程序可以访问服务器获得如天气预报、股票价格、重要新闻数 据,甚至是与服务器交换信息。Internet 客户端程序可以通过外部网络(Internet)或内部网 络(一般为 Intranet)访问服务器。
为了开发 Internet 客户端程序。MFC 类库提供了专门的 Win32 Internet 扩展接口,也就
为了开发 Internet 客户端程序。MFC 类库提供了专门的 Win32 Internet 扩展接口,也就