Sockets是Windows下
网络编程的规范,Windows Sockets是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、
Informix、
Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
基本介绍
WindowsSockets规范以U.C.Berkeley大学BSDUNIX中流行的Socket接口为范例定义了一套microsoftWindows下
网络编程接口。它不仅包含了人们所熟悉的BerkeleySocket风格的
库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows
消息驱动机制进行编程。WindowsSockets规范本意在于提供给应用程序开发者一套简单的
API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,WindowsSockets也定义了一个
二进制接口(ABI),以此来保证应用WindowsSocketsAPI的应用程序能够在任何网络软件供应商的符合WindowsSockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数调用和相关语义。遵守这套WindowsSockets规范的网络软件,我们称之为WindowsSockets兼容的,而WindowsSockets兼容实现的提供者,我们称之为WindowsSockets提供者。一个网络软件供应商必须百分之百地实现WindowsSockets规范才能做到现WindowsSockets兼容。任何能够与WindowsSockets兼容实现协同工作的应用程序就被认为是具有WindowsSockets接口。我们称这种应用程序为WindowsSockets应用程序。WindowsSockets规范定义并记录了如何使用API与
Internet协议族(IPS,通常我们指的是
TCP/IP)连接,尤其要指出的是所有的WindowsSockets实现都支持流
套接口和
数据报套接口.应用程序调用WindowsSockets的API实现相互之间的通讯。WindowsSockets又利用下层的
网络通讯协议功能和操作系统调用实现实际的通讯工作。
关系
通信的基础是套接口(Socket),一个套接口是通讯的一端。在这一端上你可以找到与其对应的一个名字。一个正在被使用的
套接口都有它的类型和与其相关的进程。套接口存在于通讯域中。通讯域是为了处理一般的线程通过套接口通讯而引进的一种抽象概念。套接口通常和同一个域中的套接口交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种
解释程序)。WindowsSockets规范支持单一的通讯域,即Internet域。各种进程使用这个域互相之间用
Internet协议族来进行通讯(WindowsSockets1.1以上的版本支持其他的域,例如WindowsSockets2)。套接口可以根据通讯性质分类;这种性质对于用户是可见的。应用程序一般仅在同一类的套接口间通讯。不过只要底层的通讯协议允许,不同类型的
套接口间也照样可以通讯。用户可以使用两种套接口,即流套接口和
数据报套接口。流套接口提供了双向的,有序的,无重复并且无记录边界的
数据流服务。数据报套接口支持双向的数据流,但并不保证是可靠,有序,无重复的。也就是说,一个从数据报套接口接收信息的进程有可能发现信息重复了,或者和发出时的顺序不同。数据报套接口的一个重要特点是它保留了记录边界。对于这一特点,数据报套接口采用了与许多
包交换网络(例如
以太网)非常类似的模型。
一个在建立
分布式应用时最常用的范例便是客户机/服务器模型。在这种方案中客户应用程序向服务器程序请求服务。这种方式隐含了在建立客户机/服务器间通讯时的非对称性。客户机/服务器模型工作时要求有一套为客户机和服务器所共识的惯例来保证服务能够被提供(或被接受)。这一套惯例包含了一套协议。它必须在通讯的两头都被实现。根据不同的实际情况,协议可能是对称的或是非对称的。在对称的协议中,每一方都有可能扮演主从角色;在非对称协议中,一方被不可改变地认为是
主机,而另一方则是从机。一个对称协议的例子是Internet中用于
终端仿真的
TELNET休眠状态数据报套接口提供的。
数据报套接口可以用来向许多系统支持的网络发送
广播数据包。要实现这种功能,网络本身必须支持广播功能,因为系统软件并不提供对广播功能的任何模拟。广播信息将会给网络造成极重的负担,因为它们要求网络上的每台主机都为它们服务,所以发送广播数据包的能力被限制于那些用显式标记了允许广播的套接口中。广播通常是为了如下两个原因而使用的:1.一个应用程序希望在本地网络中找到一个资源,而应用程序对该资源的地址又没有任何先验的知识。2.一些重要的功能,例如
路由要求把它们的信息发送给所有可以找到的邻机。被广播信息的目的地址取决于这一信息将在何种网络上广播。Internet域中支持一个速记地址用于广播-INADDR_BROADCAST。由于使用广播以前必须捆绑一个
数据报套接口,所以所有收到的广播消息都带有发送者的地址和端口。
Intel处理器的
字节顺序是和DECVAX处理器的字节顺序一致的。因此它与68000型处理器以及Internet的顺序是不同的,所以用户在使用时要特别小心以保证正确的顺序。任何从WindowsSockets函数对
IP地址和
端口号的引用和传送给WindowsSockets函数的IP地址和端口号均是按照网络顺序组织的,这也包括了
sockaddr_in主机顺序转换成网络顺序(使用htons()函数)。相应地,如果应用程序希望显示包含于某一地址中的
端口号(例如从
getpeername()函数中返回的),这一端口号就必须在被显示前从网络顺序转换到主机顺序(使用ntohs()函数)。由于
Intel处理器和Internet的
字节顺序是不同的,上述的转换是无法避免的,应用程序的编写者应该使用作为WindowsSocketsAPI一部分的标准的
转换函数,而不要使用自己的转换函数代码。因为将来的WindowsSockets实现有可能在主机字节顺序与网络字节顺序相同的机器上运行。因此只有使用标准的转换函数的应用程序是可移植的。
在MFC中MS为
套接口提供了相应的类
CAsyncSocket和
CSocket,CAsyncSocket提供基于
异步通信的套接口封装功能,CSocket则是由CAsyncSocket派生,提供更加高层次的功能,例如可以将套接口上发送和接收的数据和一个文件对象(
CSocketFile)关联起来,通过读写文件来达到发送和
接收数据的目的,此外CSocket提供的通信为
同步通信,数据未接收到或是未发送完之前调用不会返回。此外通过MFC类开发者可以不考虑网络
字节顺序和忽略掉更多的通信细节。
在一次
网络通信/连接中有以下几个参数需要被设置:本地
IP地址-本地
端口号-对方端口号-对方IP地址。左边两部分称为一个半关联,当与右边两部分建立连接后就称为一个全关联。在这个全关联的
套接口上可以双向的交换数据。如果是使用无连接的通信则只需要建立一个半关联,在发送和接收时指明另一半的参数就可以了,所以可以说无连接的通信是将数据发送到另一台主机的指定端口。此外不论是有连接还是无连接的通信都不需要双方的端口号相同。
BOOLCAsyncSocket::Create(UINTnSocketPort=0,intnSocketType=
SOCK_STREAM,longlEvent=FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE,LPCTSTRlpszSocketAddress=NULL)通过指明lEvent所包含的标记来确定需要
异步处理的事件,对于指明的相关事件的相关
函数调用都不需要等待完成后才返回,函数会马上返回然后在完成任务后发送事件通知,并利用
重载以下成员函数来处理各种网络事件:标记事件需要重载的函数
FD_READ有数据到达时发生voidOnReceive(intnErrorCode);
FD_WRITE有数据发送时产生voidOnSend(intnErrorCode);
FD_OOB收到外带数据时发生voidOnOutOfBandData(intnErrorCode);
FD_ACCEPT作为服务端等待连接成功时发生voidOnAccept(intnErrorCode);
FD_CONNECT作为客户端连接成功时发生voidOnConnect(intnErrorCode);
FD_CLOSE
套接口关闭时发生voidOnClose(intnErrorCode);
我们看到
重载的函数中都有一个参数nErrorCode,为零则表示正常完成,非零则表示错误。通过intCAsyncSocket::
GetLastError()可以得到
错误值。
下面我们看看套接口类所提供的一些功能,通过这些功能我们可以方便的建立网络连接和发送数据。
BOOLCAsyncSocket::Create(UINTnSocketPort=0,intnSocketType=
SOCK_STREAM,longlEvent=FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE,LPCTSTRlpszSocketAddress=NULL);用于创建一个本地
套接口,其中nSocketPort为使用的
端口号,为零则表示由系统自动选择,通常在客户端都使用这个选择。nSocketType为使用的协议族,SOCK_STREAM表明使用有连接的服务,
SOCK_DGRAM表明使用无连接的
数据报服务。lpszSocketAddress为本地的
IP地址,可以使用点分法表示如10.1.1.3。
BOOLCAsyncSocket::Bind(UINTnSocketPort,LPCTSTRlpszSocketAddress=NULL)作为等待连接方时产生一个网络半关联,或者是使用UDP协议时产生一个网络半关联。
BOOLCAsyncSocket::Listen(intnConnectionBacklog=5)作为等待连接方时指明同时可以接受的连接数,请注意不是总共可以接受的连接数。
BOOLCAsyncSocket::Accept(CAsyncSocket&rConnectedSocket,SOCKADDR*lpSockAddr=NULL,int*lpSockAddrLen=NULL)作为等待连接方将等待连接建立,当连接建立后一个新的
套接口将被创建,该套接口将会被用于通信。
BOOLCAsyncSocket::Connect(LPCTSTRlpszHostAddress,UINTnHostPort);作为连接方发起与等待连接方的连接,需要指明对方的
IP地址和
端口号。
voidCAsyncSocket::Close();关闭套接口。
intCAsyncSocket::Send(constvoid*lpBuf,intnBufLen,intnFlags=0)
int
CAsyncSocket::Receive(void*lpBuf,intnBufLen,intnFlags=0);在建立连接后发送和接收数据,nFlags为标记位,双方需要指明相同的标记。
intCAsyncSocket::
SendTo(constvoid*lpBuf,intnBufLen,UINTnHostPort,LPCTSTRlpszHostAddress=NULL,intnFlags=0)
intCAsyncSocket::ReceiveFrom(void*lpBuf,intnBufLen,CString&rSocketAddress,UINT&rSocketPort,intnFlags=0);对于无连接通信发送和
接收数据,需要指明对方的
IP地址和
端口号,nFlags为标记位,双方需要指明相同的标记。
我们可以看到大多数的函数都返回一个
布尔值表明是否成功。如果发生错误可以通过intCAsyncSocket::
GetLastError()得到
错误值。
由于
CSocket由
CAsyncSocket派生所以拥有CAsyncSocket的所有功能,此外你可以通过BOOLCSocket::Create(UINTnSocketPort=0,intnSocketType=
SOCK_STREAM,LPCTSTRlpszSocketAddress=NULL)来创建
套接口,这样创建的套接口没有办法
异步处理事件,所有的调用都必需完成后才会返回。
在上面的介绍中我们看到MFC提供的套接口类屏蔽了大多数的细节,我们只需要做很少的工作就可以开发出利用网络进行通信的软件。