14_SOCKET类的设计和实现

  • 格式:doc
  • 大小:88.00 KB
  • 文档页数:12

第14章 SOCKET类的设计和实现14.1 WinSock基本知识这里不打算系统地介绍socket或者WinSock的知识。

首先介绍WinSock API函数,讲解阻塞/非阻塞的概念;然后介绍socket的使用。

14.1.1 WinSock APISocket接口是网络编程(通常是TCP/IP协议,也可以是其他协议)的API。

最早的Socket 接口是Berkeley接口,在Unxi操作系统中实现。

WinSock也是一个基于Socket模型的API,在Microsoft Windows操作系统类中使用。

它在Berkeley接口函数的基础之上,还增加了基于消息驱动机制的Windows扩展函数。

Winscok1.1只支持TCP/IP网络,WinSock2.0增加了对更多协议的支持。

这里,讨论TCP/IP网络上的API。

Socket接口包括三类函数:第一类是WinSock API包含的Berkeley socket函数。

这类函数分两部分。

第一部分是用于网络I/O的函数,如accept、Closesocket、connect、recv、recvfrom、Select、Send、Sendto另一部分是不涉及网络I/O、在本地端完成的函数,如bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等第二类是检索有关域名、通信服务和协议等Internet信息的数据库函数,如gethostbyaddr、gethostbyname、gethostname、getprotolbynamegetprotolbynumber、getserverbyname、getservbyport。

第三类是Berkekley socket例程的Windows专用的扩展函数,如gethostbyname对应的WSAAsynGetHostByName(其他数据库函数除了gethostname都有异步版本),select对应的WSAAsynSelect,判断是否阻塞的函数WSAIsBlocking,得到上一次Windsock API错误信息的WSAGetLastError,等等。

从另外一个角度,这些函数又可以分为两类,一是阻塞函数,一是非阻塞函数。

所谓阻塞函数,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。

所谓非阻塞函数,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。

首先,异步函数是非阻塞函数;其次,获取远地信息的数据库函数是阻塞函数(因此,WinSock提供了其异步版本);在Berkeley socket函数部分中,不涉及网络I/O、本地端工作的函数是非阻塞函数;在Berkeley socket函数部分中,网络I/O的函数是可阻塞函数,也就是它们可以阻塞执行,也可以不阻塞执行。

这些函数都使用了一个socket,如果它们使用的socket是阻塞的,则这些函数是阻塞函数;如果它们使用的socket是非阻塞的,则这些函数是非阻塞函数。

创建一个socket时,可以指定它是否阻塞。

在缺省情况下,Berkerley的Socket函数和WinSock 都创建“阻塞”的socket。

阻塞socket通过使用select函数或者WSAAsynSelect函数在指定操作下变成非阻塞的。

WSAAsyncSelect函数原型如下。

int WSAAsyncSelect(SOCKET s,HWND hWnd,u_int wMsg,long lEvent);其中,参数1指定了要操作的socket句柄;参数2指定了一个窗口句柄;参数3指定了一个消息,参数4指定了网络事件,可以是多个事件的组合,如:FD_READ 准备读FD_WRITE 准备写FD_OOB 带外数据到达FD_ACCEPT 收到连接FD_CONNECT 完成连接FD_CLOSE 关闭socket。

用OR操作组合这些事件值,如FD_READ|FD_WRITEWSAAsyncSelect函数表示对socket s监测lEvent指定的网络事件,如果有事件发生,则给窗口hWnd发送消息wMsg。

假定应用程序的一个socket s指定了监测FD_READ事件,则在FD_READ事件上变成非阻塞的。

当read函数被调用时,不管是否读到数据都马上返回,如果返回一个错误信息表示还在等待,则在等待的数据到达后,消息wMsg发送给窗口hWnd,应用程序处理该消息读取网络数据。

对于异步函数的调用,以类似的过程最终得到结果数据。

以gethostbyname的异步版本的使用为例进行说明。

该函数原型如下:HANDLE WSAAsyncGetHostByName(HWND hWnd,u_int wMsg,const char FAR *name,char FAR *buf,int buflen);在调用WSAAsyncGetHostByName启动操作时,不仅指定主机名字name,还指定了一个窗口句柄hWnd,一个消息ID wMsg,一个缓冲区及其长度。

如果不能立即得到主机地址,则返回一个错误信息表示还在等待。

当要的数据到达时,WinSock DLL给窗口hWnd发送消息wMsg告知得到了主机地址,窗口过程从指定的缓冲区buf得到主机地址。

使用异步函数或者非阻塞的socket,主要是为了不阻塞本线程的执行。

在多进程或者多线程的情况下,可以使用两个线程通过同步手段来完成异步函数或者非阻塞函数的功能。

14.1.2 Socket的使用WinSock以DLL的形式提供,在调用任何WinSock API之前,必须调用函数WSAStartup 进行初始化,最后,调用函数WSACleanUp作清理工作。

MFC使用函数AfxSocketInit包装了函数WSAStartup,在WinSock应用程序的初始化函数IninInstance中调用AfxSocketInit进行初始化。

程序不必调用WSACleanUp。

Socket是网络通信过程中端点的抽象表示。

Socket在实现中以句柄的形式被创建,包含了进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

要使用socket,首先必须创建一个socket;然后,按要求配置socket;接着,按要求通过socket 接收和发送数据;最后,程序关闭此socket。

●为了创建socket,使用socket函数得到一个socket句柄:socket_handle = socket(protocol_family. Socket_type, protocol);其中:protocol_family指定socket使用的协议,取值PF_INET,表示Internet(TCP/IP)协议族;Socket_type指socket面向连接或者使用数据报;第三个参数表示使用TCP或者UDP协议。

当一个socket被创建时,WinSock将为一个内部结构分配内存,在此结构中保存此socket 的信息,到此,socket连接使用的协议已经确定。

●创建了socket之后,配置socket。

对于面向连接的客户,WinSock自动保存本地IP地址和选择协议端口,但是必须使用connect 函数配置远地IP地址和远地协议端口:result = connect(socket_handle, remote_socket_address, address_length)remote_socket_address是一个指向特定socket结构的指针,该地址结构为socket保存了地址族、协议端口、网络主机地址。

面向连接的服务器则使用bind指定本地信息,使用listen和accept获取远地信息。

使用数据报的客户或者服务器使用bind给socket指定本地信息,在发送或者接收数据时指定远地信息。

bind给socket指定一个本地IP地址和协议端口,如下:result = bind( socket_hndle, local_socket_address, address_length)参数类型同connect。

函数listen监听bind指定的端口,如果有远地客户请求连接,使用accept接收请求,创建一个新的socket,并保存信息。

socket_new = accept(socket_listen, socket_address, address_length)●在socket配置好之后,使用socket发送或者接收数据。

面向连接的socket使用send发送数据,recv接收数据;使用数据报的socket使用sendto发送数据,recvfrom接收数据。

14.2 MFC对WinSockt API的封装MFC提供了两个类CAsyncSocket和CSocket来封装WinSock API,这给程序员提供了一个更简单的网络编程接口。

CAsyncSocket在较低层次上封装了WinSock API,缺省情况下,使用该类创建的socket是非阻塞的socket,所有操作都会立即返回,如果没有得到结果,返回WSAEWOULDBLOCK,表示是一个阻塞操作。

CSocket建立在CAsyncSocket的基础上,是CAsyncSocket的派生类。

也就是缺省情况下使用该类创建的socket是非阻塞的socket,但是CSocket的网络I/O是阻塞的,它在完成任务之后才返回。

CSocket的阻塞不是建立在“阻塞”socket的基础上,而是在“非阻塞”socket 上实现的阻塞操作,在阻塞期间,CSocket实现了本线程的消息循环,因此,虽然是阻塞操作,但是并不影响消息循环,即用户仍然可以和程序交互。

14.2.1 CAsyncSocketCAsyncSocket封装了低层的WinSock API,其成员变量m_hSocket保存其对应的socket句柄。

使用CAsyncSocket的方法如下:首先,在堆或者栈中构造一个CAsyncSocket对象,例如:CAsyncSocket sock;或者CAsyncSocket *pSock = new CAsyncSocket;其次,调用Create创建socket,例如:使用缺省参数创建一个面向连接的socketsock.Create()指定参数参数创建一个使用数据报的socket,本地端口为30pSocket.Create(30, SOCK_DGRM);其三,如果是客户程序,使用Connect连接到远地;如果是服务程序,使用Listen监听远地的连接请求。