Windows Sockets 规范及应用
-Windows网络编程接口
******************************************************************
版权信息
本书作者保留所有版权。禁止任何商业性的转载或复制。非赢利性质的转载和复制不得修改文章内容,并请保留此段文字。
Copyright (c) 1995-1996 By 施炜,李铮,秦颖
All Right Reserved
******************************************************************
内容提要
本书适应了Windows、Internet及计算机网络普及的潮流,介绍了一套在Windows下网络编程的规范-Windows Sockets。这套规范是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。为使读者能够充分理解和应用这套规范,本书不但对Windows Sockets 1.1及2.0规范作了较为详尽的介绍,还结合了作者的实际工作,给出了具有实际应用价值的程序实例。书中的内容包括:Windows Sockets规范1.1版及2.0.8版介绍;Windows Sockets网络编程指导和具体应用实例;Windows Sockets规范1.1版及2.0.8版库函数参考等。
本书体系完整,文字流畅,可供从事网络应用开发的工程技术人员和大专院校师生参考。
作者声明
由于成书时间紧迫。本书不免有许多错误和不当之处,故此作者衷心希望各位读者能对本书提出宝贵意见(包括补充新的应用实例和内容),以便我们进一步修改完善此书。我们会尊重相应修订者的版权。作者也衷心希望在我们和各位读者的努力下,本书能够成为一本关于Windows Sockets编程的系统而又准确的免费中文参考书,为广大读者在Windows下网络编程提供帮助。
作者联系地址:
施炜:上海交通大学94032班 (200030)
Email: weishi@fudan.ihep.ac.cn
李铮:上海交通大学自动化系 (200030)
Email: blee.bbs@captain.net.tsinghua.edu.cn
秦颖:上海交通大学94033A班 (200030)
Email: fluke.bbs@captain.net.tsinghua.edu.cn
作者希望每一位拿到本书的读者能以任何方式通知我们。以便我们掌握本书的应用情况。并敬请各位读者暂时不要在其他FTP站点散发,谢谢合作。
编著者
1996年5月20日
前言
当今世界正处于信息时代,计算机和通信网络是这一时代所谓“信息基础设施”。网络化是计算机技术九十年代的重要发展趋势之一。目前计算机网络的新发展是:异机种网络和异网互联有较大突破。TCP/IP协议在异网互联中体现出了其强大的生命力,以它为基础组建的Internet是目前国际上规模最大的计算机网间网,到1991年底世界上已有26个国家的五千多个网络连入Internet,其中包含了数千个组织的30万台主机,用户数以百万计。
与计算机网络的普及相呼应的是Windows的广泛应用,现在在全世界各地已有超过四千万用户在使用不同版本的Windows。自1995年8月24日Windows 95正式推出以来,在短短的一个星期内销售量已超过100万份,有的零售商店不得不半夜开门,以迎接滚滚而来的抢购者。这说明以用户友好的图形界面为基础的Windows已得到用户的普遍认可,已经并将继续成为个人机平台上的事实上的操作系统标准。所以研究和开发在Windows下的网络编程技术具有普遍的应用价值。
在Windows下的各种网络编程接口中,Windows Sockets脱颖而出,越来越得到大家的重视,这是因为Windows Sockets规范是一套开放的、支持多种协议的Windows下的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
在作者利用Windows Sockets规范进行应用开发的过程中,发现这方面的资料很少,特别是缺乏一本全面而实用的专著。为了使广大用户能够充分理解和应用这套规范,我们编写了这本书。本书不但对Windows Sockets 1.1及2.0规范作了较为详尽的介绍,还结合了作者的实际工作,给出了具有实际应用价值的程序实例。希望它能对Windows Sockets规范在国内的推广和应用起到抛砖引玉的作用。读者在阅读本书的过程中,如果能对自己的学习工作有所帮助和指导,是作者的最大愿望。由于时间紧迫,作者学识有限,书中错误在所难免,偏颇和不当之处,恳请读者不吝赐教。
本书由施炜、李铮、秦颖合作完成,其中,第一、二、四、六章和5.2节由施炜编写;第七章、5.1节、3.4节由李铮编写;第5.3节、3.1-3.3节由秦颖编写。在本书的编写过程中,得到了上海交通大学的毛向辉先生的大力支持,并提供了一些最新的资料,在此谨表示衷心的谢意。
编著者
1995年9月于上海交通大学
目录
第一章 简介
1.1 什么是WINDOWS SOCKETS规范?
1.2 BEKELEY套接口
1.3 MICROSOFT WINDOWS和针对WINDOWS的扩展
1.4 这份规范的地位
1.5 曾经作过的修改
1.5.1 Windows Sockets 1.0
1.5.2 Windows Sockets 1.1
第二章 使用WINDOWS SOCKETS 1.1编程
2.1 WINDOWS SOCKETS协议栈安装检查
2.2 套接口
2.2.1 基本概念
2.2.2 客户机/服务器模型
2.2.3 带外数据
2.2.4 广播
2.3 字节顺序
2.4 套接口属性选项
2.5 数据库文件
2.6 与BERKELEY套接口的不同
2.6.1 套接口数据类型和错误数值
2.6.2 select()函数和FD_*宏
2.6.3 错误代码-errno,h_errno,WSAGetLastError()
2.6.4 指针
2.6.5 重命名的函数
2.6.5.1 close()和closesocket()
2.6.5.2 ioctl()和iooctlsocket()
2.6.6 阻塞例程和EINPROGRESS宏
2.6.7 Windows Sockets支持的最大套接口数目
2.6.8 头文件
2.6.9 API调用失败时的返回值
2.6.10 原始套接口
2.7 在多线程WINDOWS版本中的WINDOWS SOCKETS
第三章 WINDOWS SOCKETS 1.1应用实例
3.1 套接口网络编程原理
3.2 WINDOWS SOCKETS编程原理
3.3 WINDOWS SOCKETS与UNIX套接口编程实例
3.3.1 SERVER介绍
3.3.2 CLIENT介绍
3.3.3 源程序清单
3.4 另一个精巧的应用程序实例-WSHOUT
3.4.1 源程序目录
3.4.2 程序逻辑结构
3.4.3 源程序清单及注释
3.4.3.1 wshout.c清单
3.4.3.2 wshout.h清单
3.4.3.3 wshout.rc清单
3.4.3.4 ushout.c清单
3.4.3.5 ulisten.c清单
3.4.3.6 tshout.c清单
3.4.3.7 tlisten.c清单
3.4.3.8 errno.c清单
3.4.3.9 resolve.c清单
第四章 WINDOWS SOCKET 1.1库函数概览
4.1 套接口函数
4.1.1 阻塞/非阻塞和数据易失性
4.2 数据库函数
4.3 针对MICROSOFT WINDOWS的扩展函数
4.3.1 异步选择机制
4.3.2 异步支持例程
4.3.3 阻塞钩子函数方法
4.3.4 错误处理
4.3.5 通过中介DLL调用Windows Sockets DLL
4.3.6 Windows Sockets实现内部对消息的使用
4.3.7 私有的API接口
第五章 套接口库函数参考
5.1 WINDOWS SOCKET 1.1库函数参考
5.1.1 accept()
5.1.2 bind()
5.1.3 closesocket()
5.1.4 connect()
5.1.5 getpeername()
5.1.6 getsockname()
5.1.7 getsockopt()
5.1.8 htonl()
5.1.9 htons()
5.1.10 inet_addr()
5.1.11 inet_ntoa()
5.1.12 ioctlsocket()
5.1.13 listen()
5.1.14 ntohl()
5.1.15 ntohs()
5.1.16 recv()
5.1.17 recvfrom()
5.1.18 select()
5.1.19 send()
5.1.20 sendto()
5.1.21 setsockopt()
5.1.22 shutdown()
5.1.23 socket()
5.2 数据库函数
5.2.1 gethostbyaddr()
5.2.2 gethostbyname()
5.2.3 gethostname()
5.2.4 getprotobyname()
5.2.5 getprotobynumber()
5.2.6 getservbyname()
5.2.7 getservbyport()
5.3 WINDOWS扩展函数
5.3.1 WSAAsyncGetHostByAddr()
5.3.2 WSAAsyncGetHostByName()
5.3.3 WSAAsyncGetProtoByName()
5.3.4 WSAAsyncGetProtoByNumber()
5.3.5 WSAAsyncGetServByName()
5.3.6 WSAAsyncGetServByPort()
5.3.7 WSAAsyncSelect()
5.3.8 WSACancelAsyncRequest()
5.3.9 WSACancelBlockingCall()
5.3.10 WSACleanup()
5.3.11 WSAGetLastError()
5.3.12 WSAIsBlocking()
5.3.13 WSASetBlockingHook()
5.3.14 WSASetLastError()
5.3.15 WSAStartup()
5.3.16 WSAUnhookBlockingHook()
第六章 WINDOWS SOCKET 2的扩展特性
6.1 同时使用多个传输协议
6.2 与WINDOWS SOCKET 1.1应用程序的向后兼容性
6.2.1 源码的兼容性
6.2.2 二进制兼容性
6.3 在WINDOWS SOCKETS中注册传输协议
6.3.1 使用多个协议
6.3.2 select()函数应用中关于多个服务提供者的限制
6.4 协议无关的名字解析
6.5 重叠I/O和事件对象
6.5.1 事件对象
6.5.2 接收操作完成指示
6.5.2.1 阻塞并且等待完成指示。
6.5.2.2 检查完成指示
6.5.2.3 使用套接口I/O操作完成例程
6.5.3 WSAOVERLAPPED的细节
6.6 使用事件对象异步通知
6.7 服务的质量(QOS)
6.8 套接口组
6.9 共享套接口
6.10 连接建立和拆除的高级函数
6.11 扩展的字节顺序转换例程
6.12 分散/聚集方式I/O
6.13 协议无关的多点通讯
6.14 新增套接口选项一览
6.15 新增套接口IOCTL操作代码
6.16 新增函数一览
第七章 WINDOWS SOCKETS 2扩展库函数简要参考
7.1 WSAACCEPT()
7.2 WSACLOSEEVENT()
7.3 WSACONNECT()
7.4 WSACREATEEVENT()
7.5 WSADUPLICATESOCKET()
7.6 WSAENUMNETWORKEVENTS()
7.7 WSAENUMPROTOCOLS()
7.8 WSAEVENTSELECT()
7.9 WSAGETOVERLAPPEDRESULT()
7.10 WSAGETQOSBYNAME()
7.11 WSAHTONL()
7.12 WSAHTONS()
7.13 WSAIOCTL()
7.14 WSAJOINLEAF()
7.15 WSANTOHL()
7.16 WSANTOHS()
7.17 WSARECV()
7.18 WSARECVDISCONNECT()
7.19 WSARECVFROM()
7.20 WSARESETEVENT()
7.21 WSASEND()
7.22 WSASENDDISCONNECT()
7.23 WSASENDTO()
7.24 WSASETEVENT()
7.25 WSASOCKET()
7.26 WSAWAITFORMULTIPLEEVENTS()
附录A 错误代码
附录B WINDOWS SOCKETS头文件
附录B.1 WINDOWS SOCKETS 1.1头文件
附录B.2 WINDOWS SOCKETS 2头文件
附录B.3 WINSOCK.DEF文件
附录C 参考文献
第一章 简介
1.1 什么是Windows Sockets规范?
Windows Sockets规范以U.C. Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Micosoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。
Windows Sockets规范本意在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,Windows Sockets也定义了一个二进制接口(ABI),以此来保证应用Windows Sockets API的应用程序能够在任何网络软件供应商的符合Windows Sockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数调用和相关语义。
遵守这套Windows Sockets规范的网络软件,我们称之为Windows Sockets兼容的,而Windows Sockets兼容实现的提供者,我们称之为Windows Sockets提供者。一个网络软件供应商必须百分之百地实现Windows Sockets规范才能做到现Windows Sockets兼容。
任何能够与Windows Sockets兼容实现协同工作的应用程序就被认为是具有Windows Sockets接口。我们称这种应用程序为Windows Sockets应用程序。
Windows Sockets规范定义并记录了如何使用API与Internet协议族(IPS,通常我们指的是TCP/IP)连接,尤其要指出的是所有的Windows Sockets实现都支持流套接口和数据报套接口.
应用程序调用Windows Sockets的API实现相互之间的通讯。Windows Sockets又利用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。它们之间的关系如图1-1。
虽然我们并不反对使用这一套API来实现另一通讯协议栈(而且我们期望在将来规范的修改中能够讨论这个问题),但这种用法已经超出了我们这一份规范所规定的范围,我们在此将不作讨论。
1.2 Bekeley套接口
Windows Sockets规范是建立在Bekeley套接口模型上的。这个模型现在已是TCP/IP网络的标准。它提供了习惯于UNIX套接口编程的程序员极为熟悉的环境,并且简化了移植现有的基于套接口的应用程序源代码的工作。Windows Sockets API也是和4.3BSD的要求一致的。
1.3 Microsoft Windows和针对Windows的扩展
这一套Windows Sockets API能够在所有3.0以上版本的Windows和所有Windows Scokets实现上使用,所以它不仅为Windwos Sockets实现和Windows Sockets应用程序提供了16位操作环境,而且也提供了32位操作环境。
Windows Sockets也支持多线程的Windows进程。一个进程包含了一个或多个同时执行的线程。在Windows 3.1非多线程版本中,一个任务对应了一个仅具有单个线程的进程。而我们在本书中所提到的线程均是指在多线程Windows环境中的真正意义的线程。在非多线程环境中(例如Windows 3.0)这个术语是指Windows Sockets进程.
Windows Sockets规范中的针对Windows的扩展部分为应用程序开发者提供了开发具有Windows应用软件的功能。它有利于使程序员写出更加稳定并且更加高效的程序,也有助于在非占先Windows版本中使多个应用程序在多任务情况下更好地运作。除了WSAStartup()和WSACleanup()两个函数除外,其他的Windows扩展函数的使用不是强制性的。
1.4 这份规范的地位
Windows Sockets是一份独立的规范。它的产生和存在是为了造益于应用程序开发者,网络软件供应商和广大计算机用户。这份规范的每一份正式出版的版本(非草稿)实际上代表了为网络软件供应商实现所需和应用程序开发者所用的一整套API。关于这套规范的讨论和改进还正在进行之中。这样的讨论主要是通过Internet上的一个电子邮件论坛-winsock@microdyne.com进行的。同时也有不定期的会议举行。会议的具体内容会在电子邮件论坛上发表。
1.5 曾经作过的修改
1.5.1 Windows Sockets 1.0
Windows Sockets 1.0代表了网络软件供应商和用户协会细致周到的工作的结晶。Windows Sockets 1.0规范的发布是为了让网络软件供应商和应用程序开发者能够开始建立各自的符合Windows Sockets标准的实现和应用程序。
1.5.2 Windows Sockets 1.1
Windows Sockets 1.1继承了Windows Sockets 1.0的准则和结构,并且仅在一些绝对必要的地方作了改动。这些改动都是基于不少公司在创作Windows Sockets 1.0实现时的经验和教训的。Windows Scokets 1.1包含了一些更加清晰的说明和对Windows Sockets 1.0的小改动。此外1.1还包含了如下重大的变更:
* 加入了gethostname()这个常规调用,以便更加简单地得到主机名字和地址。
* 定义DLL中小于1000的序数为Windows Sockets保留,而对大于1000的序数则没有限制。这使Windows Sockets供应商可以在DLL中加入自己的界面,而不用担心所选择的序数会和Windows Scokets将来的版本冲突。
* 增加了WSAStartup()函数和WASClearup()函数之间的关联,要求两个函数互相对应。这使得应用程序开发者和第三方DLL在使用Windows Sockets实现时不需要考虑其他程序对这套API的调用。
* 把函数intr_addr()的返回类型,从结构in_addr改为了无符号长整型。这个改变是为了适应Microsoft C编译器和Borland C编译器对返回类型为四字节结构的函数的不同处理方法。
* 把WSAAsyncSelect()函数语义从“边缘触发”改为“电平触发”。这种方式大大地简化了一个应用程序对这个函数的调用。
* 改变了ioctlsocket()函数中FIONBIO的语义。如果套接口还有未完成的WSAAsyncSelect()调用,该函数将失败返回。
* 为了符合RFC 1122,在套接口选项中加入了TCP_NODELAY这一条。
所有Windows Sockets 1.1对于Windows Sockets 1.0的改动在以下都作了记号。
第二章 使用Windows Sockets 1.1编程
在这一章,我们将介绍如何使用Windows Sockets 1.1编程,并讨论了使用Windows Sockets 1.1编程的一些细节问题。本章的讨论均是基于Windows Sockets 1.1规范的,在某些方面可能会和第六、七章对Windows Sockets 2的讨论不一致,请读者注意这一区别。
2.1 Windows Sockets协议栈安装检查
任何一个与Windows Sockets Import Library联接的应用程序只需简单地调用WSAStartup()函数便可检测系统中有没有一个或多个Windows Sockets实现。而对于一个稍微聪明一些的应用程序来说,它会检查PATH环境变量来寻找有没有Windows Sockets实现的实例(Windows Sockets.DLL)。对于每一个实例,应用程序可以发出一个LoadLibrary()调用并且用WSAStartup()函数来得到这个实现的具体数据。
这一版本的Windows Sockets规范并没有试图明确地讨论多个并发的Windows Sockets实现共同工作的情况。但这个规范中没有任何规定可以被解释成是限制多个Windows Sockets DLL同时存在并且被一个或者多个应用程序同时调用的。
2.2 套接口
2.2.1 基本概念
通讯的基石是套接口,一个套接口是通讯的一端。在这一端上你可以找到与其对应的一个名字。一个正在被使用的套接口都有它的类型和与其相关的进程。套接口存在于通讯域中。通讯域是为了处理一般的线程通过套接口通讯而引进的一种抽象概念。套接口通常和同一个域中的套接口交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。Windows Sockets规范支持单一的通讯域,即Internet域。各种进程使用这个域互相之间用Internet协议族来进行通讯(Windows Sockets 1.1以上的版本支持其他的域,例如Windows Sockets 2)。
套接口可以根据通讯性质分类;这种性质对于用户是可见的。应用程序一般仅在同一类的套接口间通讯。不过只要底层的通讯协议允许,不同类型的套接口间也照样可以通讯。
用户目前可以使用两种套接口,即流套接口和数据报套接口。流套接口提供了双向的,有序的,无重复并且无记录边界的数据流服务。数据报套接口支持双向的数据流,但并不保证是可靠,有序,无重复的。也就是说,一个从数据报套接口接收信息的进程有可能发现信息重复了,或者和发出p[;'//-0同。数据报套接口的一个重要特点是它保留了记录边界。对于这一特点,数据报套接口采用了与现在许多包交换网络(例如以太网)非常类似的模型。
2.2.2 客户机/服务器模型
一个在建立分布式应用时最常用的范例便是客户机/服务器模型。在这种方案中客户应用程序向服务器程序请求服务。这种方式隐含了在建立客户机/服务器间通讯时的非对称性。客户机/服务器模型工作时要求有一套为客户机和服务器所共识的惯例来保证服务能够被提供(或被接受)。这一套惯例包含了一套协议。它必须在通讯的两头都被实现。根据不同的实际情况,协议可能是对称的或是非对称的。在对称的协议中,每一方都有可能扮演主从角色;在非对称协议中,一方被不可改变地认为是主机,而另一方则是从机。一个对称协议的例子是Internet中用于终端仿真的TELNET。而非对称协议的例子是Internet中的FTP。无论具体的协议是对称的或是非对称的,当服务被提供时必然存在“客户进程”和“服务进程”。
一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务-对客户的请求作出适当的反应。这一请求/相应的过程可以简单的用图2-1表示。虽然基于连接的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过数据报套接口提供的。
2.2.3 带外数据
注意:以下对于带外数据(也称为TCP紧急数据)的讨论,都是基于BSD模型而言的。用户和实现者必须注意,目前有两种互相矛盾的关于RFC 793的解释,也就是在这基础上,带外数据这一概念才被引入的。而且BSD对于带外数据的实现并没有符合RFC 1122定下的主机的要求,为了避免互操作时的问题,应用程序开发者最好不要使用带外数据,除非是与某一既成事实的服务互操作时所必须的。Windows Sockets提供者也必须提供他们的产品对于带外数据实现的语义的文挡(采用BSD方式或者是RFC 1122方式)。规定一个特殊的带外数据语义集已经超出了Windows Sockets规范的讨论范围。
流套接口的抽象中包括了带外数据这一概念,带外数据是相连的每一对流套接口间一个逻辑上独立的传输通道。带外数据是独立于普通数据传送给用户的,这一抽象要求带外数据设备必须支持每一时刻至少一个带外数据消息被可靠地传送。这一消息可能包含至少一个字节;并且在任何时刻仅有一个带外数据信息等候发送。对于仅支持带内数据的通讯协议来说(例如紧急数据是与普通数据在同一序列中发送的),系统通常把紧急数据从普通数据中分离出来单独存放。这就允许用户可以在顺序接收紧急数据和非顺序接收紧急数据之间作出选择(非顺序接收时可以省去缓存重叠数据的麻烦)。在这种情况下,用户也可以“偷看一眼”紧急数据。
某一个应用程序也可能喜欢线内处理紧急数据,即把其作为普通数据流的一部分。这可以靠设置套接口选项中的SO_OOBINLINE来实现(参见5.1.21节,setsockopt())。在这种情况下,应用程序可能希望确定未读数据中的哪一些是“紧急”的(“紧急”这一术语通常应用于线内带外数据)。为了达到这个目的,在Windows Sockets的实现中就要在数据流保留一个逻辑记号来指出带外数据从哪一点开始发送,一个应用程序可以使用SIOCATMARK ioctlsocket()命令(参见5.1.12节)来确定在记号之前是否还有未读入的数据。应用程序可以使用这一记号与其对方进行重新同步。
WSAAsyncSelect()函数可以用于处理对带外数据到来的通知。
2.2.4 广播
数据报套接口可以用来向许多系统支持的网络发送广播数据包。要实现这种功能,网络本身必须支持广播功能,因为系统软件并不提供对广播功能的任何模拟。广播信息将会给网络造成极重的负担,因为它们要求网络上的每台主机都为它们服务,所以发送广播数据包的能力被限制于那些用显式标记了允许广播的套接口中。广播通常是为了如下两个原因而使用的:1. 一个应用程序希望在本地网络中找到一个资源,而应用程序对该资源的地址又没有任何先验的知识。2. 一些重要的功能,例如路由要求把它们的信息发送给所有可以找到的邻机。
被广播信息的目的地址取决于这一信息将在何种网络上广播。Internet域中支持一个速记地址用于广播-INADDR_BROADCAST。由于使用广播以前必须捆绑一个数据报套接口,所以所有收到的广播消息都带有发送者的地址和端口。
某些类型的网络支持多种广播的概念。例如IEEE802.5令牌环结构便支持链接层广播指示,它用来控制广播数据是否通过桥接器发送。Windows Sockets规范没有提供任何机制用来判断某个应用程序是基于何种网络之上的,而且也没有任何办法来控制广播的语义。
2.3 字节顺序
Intel处理器的字节顺序是和DEC VAX处理器的字节顺序一致的。因此它与68000型处理器以及Internet的顺序是不同的,所以用户在使用时要特别小心以保证正确的顺序。
任何从Windows Sockets函数对IP地址和端口号的引用和传送给Windows Sockets函数的IP地址和端口号均是按照网络顺序组织的,这也包括了sockaddr_in结构这一数据类型中的IP地址域和端口域(但不包括sin_family域)。
考虑到一个应用程序通常用与“时间”服务对应的端口来和服务器连接,而服务器提供某种机制来通知用户使用另一端口。因此getservbyname()函数返回的端口号已经是网络顺序了,可以直接用来组成一个地址,而不需要进行转换。然而如果用户输入一个数,而且指定使用这一端口号,应用程序则必须在使用它建立地址以前,把它从主机顺序转换成网络顺序(使用htons()函数)。相应地,如果应用程序希望显示包含于某一地址中的端口号(例如从getpeername()函数中返回的),这一端口号就必须在被显示前从网络顺序转换到主机顺序(使用ntohs()函数)。
由于Intel处理器和Internet的字节顺序是不同的,上述的转换是无法避免的,应用程序的编写者应该使用作为Windows Sockets API一部分的标准的转换函数,而不要使用自己的转换函数代码。因为将来的Windows Sockets实现有可能在主机字节顺序与网络字节顺序相同的机器上运行。因此只有使用标准的转换函数的应用程序是可移植的。
2.4 套接口属性选项
Windows Sockets规范支持的套接口属性选项都列在对setsockopt()函数和getsockopt()函数的叙述中。任何一个Windows Sockets实现必须能够识别所有这些属性选项,并且对每一个属性选项都返回合理的数值。每一个属性选项的缺省值列在下表中:
选项 类型 含义 缺省值 注意事项
SO_ACCEPTCON BOOL 套接口正在监听。 FALSE
SO_BROADCAST BOOL 套接口被设置为可以 FALSE
发送广播数据。
SO_DEBUG BOOL 允许Debug。 FALSE (*)
S0_DONTLINGER BOOL 如果为真,SO_LINGER TRUE
选项被禁止。
SO_DONTROUTE BOOL 路由被禁止。 FALSE (*)
SO_ERROR int 得到并且清除错误状态。 0
SO_KEEPALIVE BOOL 活跃信息正在被发送。 FALSE
SO_LINGER struct 返回目前的linger信息。 l_onoff
linger 为0
FAR *
SO_OOBINLINE BOOL 带外数据正在普通数据流 FALSE
中被接收。
SO_RCVBUF int 接收缓冲区大小。 决定于实现 (*)
SO_REUSEADDR BOOL 该套接口捆绑的地址 FALSE
是否可被其他人使用。
SO_SNDBUF int 发送缓冲区大小。 决定于实现 (*)
SO_TYPE int 套接口类型(如 和套接口被
SOCK_STREAM)。 创建时一致
TCP_NODELAY BOOL 禁止采用Nagle 决定于实现
进行合并传送。
(*) Windows Sockets实现有可能在用户调用setsockopt()函数时忽略这些属性,并且在用户调用getsockopt()函数时返回一个没有变化的值。或者它可能在setsockopt()时接受某个值,并且在getsockopt()时返回相应的数值,但事实上并没有在任何地方使用它。
2.5 数据库文件
getXbyY()和WSAAyncGetXByY()这一类的例程是用来得到某种特殊的网络信息的。getXbyY()例程最初(在第一版的BERKELY UNIX中)是被设计成一种在文本数据库中查询信息的机制。虽然Windows Sockets实现可能用不同的方式来得到这些信息,但Windows Sockets应用程序要求通过getXbyY()或WSAAyncGetXByY()这一类例程得到的信息是一致。
2.6 与Berkeley套接口的不同
有一些很有限的地方,Windows Sockets API必须与从严格地坚持Berkeley传统风格中解放出来。通常这么做是因为在Windows环境中实现的难度。
2.6.1 套接口数据类型和错误数值
Windows Sockets规范中定义了一个新的数据类型SOCKET,这一类型的定义对于将来Windows Sockets规范的升级是必要的。例如在Windows NT中把套接口作为文件句柄来使用。这一类型的定义也保证了应用程序向Win/32环境的可移植性。因为这一类型会自动地从16位升级到32位。
在UNIX中所有句柄包括套接口句柄,都是非负的短整数,而且一些应用程序把这一假设视为真理。Windows Sockets句柄则没有这一限制,除了INVALID_SOCKET不是一个有效的套接口外,套接口可以取从0到INVALID_SOCKET-1之间的任意值。
因为SOCKET类型是unsigned,所以编译已经存在于UNIX环境中的应用程序的源代码可能会导致signed/unsigned数据类型不匹配的警告。
这还意味着,在socket()例程和accept()例程返回时,检查是否有错误发生就不应该再使用把返回值和-1比较的方法,或判断返回值是否为负(这两种方法在BSD中都是很普通,很合法的途径)。取而代之的是,一个应用程序应该使用常量INVALID_SOCKET,该常量已在WINSOCK.H中定义。
例如:
典型的BSD风格:
s = socket(...);
if (s == -1) /* of s<0 */
{...}
更优良的风格:
s = socket(...);
if (s == INVALID_SOCKET)
{...}
{**************************Read this ******************}
2.6.2 select()函数和FD_*宏
由于一个套接口不再表示了UNIX风格的小的非负的整数,select()函数在Windows Sockets API中的实现有一些变化:每一组套接口仍然用fd_set类型来代表,但是它并不是一个位掩码。整个组的套接口是用了一个套接口的数组来实现的。为了避免潜在的危险,应用程序应该坚持用FD_XXX宏来设置,初始化,清除和检查fd_set结构。
2.6.3 错误代码-errno,h_errno,WSAGetLastError()
Windows Sockets实现所设置的错误代码是无法通过errno变量得到的。另外对于getXbyY()这一类的函数,错误代码无法从h_errno变量得到。错误代码可以使用WSAGetLastError()调用得到。这一函数在5.3.11中讨论。这个函数在Windows Sockets实现中是作为WIN/32函数GetLastError()的先导函数(最终是一个别名)。这样做是为了在多线程的进程中为每一线程得到自己的错误信息提供可靠的保障。
为了保持与BSD的兼容性,应用程序可以加入以下一行代码:
#define errno WSAGetLastError()
这就保证了用全程的errno变量所写的网络程序代码在单线程环境中可以正确使用。当然,这样做有许多明显的缺点:如果一个原程序包含了一段代码对套接口和非套接口函数都用errno变量来检查错误,那么这种机制将无法工作。此外,一个应用程序不可能为errno赋一个新的值(在Windows Sockets中,WSASetLastError()函数可以做到这一点)。
例如:
典型的BSD风格:
r = recv(...);
if (r == -1 /* 但请见下文 */
&& errno == EWOULDBLOCK)
{...}
更优良的风格:
r = recv(...);
if (r == -1 /* 但请见下文 */
&& WSAGetLastError() == EWOULDBLOCK)
{...}
虽然为了兼容性原因,错误常量与4.3BSD所提供的一致;应用程序应该尽可能地使用“WSA”系列错误代码定义。例如,一个更准确的上面程序片断的版本应该是:
r = recv(...);
if (r == -1 /* 但请见下文 */
&& WSAGetLastError() == WSAEWOULDBLOCK)
{...}
2.6.4 指针
所有应用程序与Windows Sockets使用的指针都必须是FAR指针,为了方便应用程序开发者使用,Windows Sockets规范定义了数据类型LPHOSTENT。
2.6.5 重命名的函数
有两种原因Berkeley套接口中的函数必须重命名以避免与其他的API冲突:
2.6.5.1 close()和closesocket()
在Berkeley套接口中,套接口出现的形式与标准文件描述字相同,所以close()函数可以用来和关闭正规文件一样来关闭套接口。虽然在Windows Sockets API中,没有任何规定阻碍Windows Sockets实现用文件句柄来标识套接口,但是也没有任何规定要求这么做。套接口描述字并不认为是和正常文件句柄对应的,而且并不能认为文件操作,例如read(),write()和close()在应用于套接口后不能保证正确工作。套接口必须使用closesocket()例程来关闭,用close()例程来关闭套接口是不正确的,这样做的效果对于Windows Sockets规范说来也是未知的。
2.6.5.2 ioctl()和iooctlsocket()
许多C语言的运行时系统出于与Windows Sockets无关的目的使用ioctl()例程,所以Windows Sockets定义ioctlsocket()例程。它被用于实现BSD中用ioctl()和fcntl()实现的功能。
2.6.6 阻塞例程和EINPROGRESS宏
虽然Windows Sockets支持关于套接口的阻塞操作,但是这种应用是被强烈反对的.如果程序员被迫使用阻塞模式(例如一个准备移植的已有的程序),那么他应该清楚地知道Windows Sockets中阻塞操作的语义。有关细节请参见4.1.1
2.6.7 Windows Sockets支持的最大套接口数目
一个特定的Windows Sockets提供者所支持的套接口的最大数目是由实现确定的。任何一个应用程序都不应假设某个待定数目的套接口可用。这一点在4.3.15 WSAStartup()中会被重申。而且一个应用程序可以真正使用的套接口的数目和某一特定的实现所支持的数目是完全无关的。
一个Windows Sockets应用程序可以使用的套接口的最大数目是在编译时由常量FD_SETSIZE决定的。这个常量在select()函数(参见4.1.18)中被用来组建fd_set结构。在WINSOCK.H中缺省值是64。如果一个应用程序希望能够使用超过64个套接口,则编程人员必须在每一个源文件包含WINSOCK.H前定义确切的FD_SET值。有一种方法可以完成这项工作,就是在工程项目文件中的编译器选项上加入这一定义。例如在使用Microsoft C时加入-D FD_SETSIZE=128作为编译命令的一个命令行参数.要强调的是:FD_SET定义的值对Windows Sockets实现所支持的套接口的数目并无任何影响。
2.6.8 头文件
为了方便基于Berkeley套接口的已有的源代码的移植,Windows Sockets支持许多Berkeley头文件。这些Berkeley头文件被包含在WINSOCK.H中。所以一个Windows Sockets应用程序只需简单的包含WINSOCK.H就足够了(这也是一种被推荐使用的方法)。
2.6.9 API调用失败时的返回值
常量SOCKET_ERROR是被用来检查API调用失败的。虽然对这一常量的使用并不是强制性的,但这是推荐的。如下的例子表明了如何使用SOCKET_ERROR常量
典型的BSD风格:
r = recv(...);
if (r == -1 /* or r < 0 */
&& errno == EWOULDBLOCK)
{...}
更优良的风格:
r = recv(...);
if (r == SOCKET_ERROR
&& WSAGetLastError == WSAEWOULDBLOCK)
{...}
2.6.10 原始套接口
Windows Sockets规范并没有规定Windows Sockets DLL必须支持原始套接口-用SOCK_RAW打开的套接口。然而Windows Sockets规范鼓励Windows Sockets DLL提供原始套接口支持。一个Windows Sockets兼容的应用程序在希望使用原始套接口时应该试图用socket()调用(参见5.1.23节)来打开套接口。如果这么做失败了,应用程序则应该使用其他类型的套接口或向用户报告错误。
2.7 在多线程Windows版本中的Windows Sockets
Windows Sockets接口被设计成既能够在单线程的Windows版本(例如Windows 3.1)又能够在占先的多线程Windows版本(例如Windows NT)中使用,在多线程环境中,套接口接口基本上是不变的。但多线程应用程序的作者必须知道,在线程之间同步对套接口的使用是应用程序的责任,而不是Windows Sockets实现的责任。这一点在其他形式的I/O中管理,例如文件I/O中是一样的。没有对套接口调用进行同步将导致不可预测的结果。例如,如果有两个线程同时调用同一套接口进行send(),那么数据发送的先后顺序就无法保证了。
在一个线程中关闭一个未完成的阻塞的套接口将会导致另一个线程使用同一套接口的阻塞调用出错(WSAEINTER)返回,就象操作被取消一样。这也同样适用于某一个select()调用未完成时,应用程序关闭了其中的一个被选择的套接口。
在占先的多线程Windows版本中,并没有缺省的阻塞钩子函数。这是因为如果一个单一的应用程序在等待某一操作结束时并不会调用PeekMessage()或GetMessage()这些会使应用程序产生一个非占先窗口的函数。因此机器在这种情况下不会被阻塞。然而,为了向后的兼容性,在多线程Windows版本中,WSASetBlockingHook()函数也被实现了。任何使用缺省阻塞钩子的应用程序可以安装它们自己的阻塞钩子函数来覆盖缺省的阻塞钩子函数。
第三章 Windows Sockets 1.1应用实例
在本章中,作者的实际工作为背景,给出了一个使用Windows Sockets 1.1编程的具体例子。并对这个例子作了详细的分析。这个例子在Windows 3.1、Windows Sockets 1.1和BSD OS for PC 2.0(BSD UNIX微机版)环境下调试通过。
3.1 套接口网络编程原理
套接口有三种类型:流式套接口,数据报套接口及原始套接口.
流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输.数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错.原始套接口允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等.
无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。若使用无连接的套接口编程,程序的流程可以用图3-1表示。
面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。使用面向连接的套接口编程,可以通过图3-1来表示:其时序。
套接口工作过程如下:服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接.客户在建立套接口后就可调用connect()和服务器建立连接.连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据.最后,待数据传送结束后,双方调用close()关闭套接口.
3.2 Windows Sockets编程原理
由于Windows的基于消息的特点,WINSOCK和BSD套接口相比,有如下一些新的扩充:
1.异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,如FD_READ,FD_WRITE,FD_CONNECT,FD_ACCEPT等等代表的网络事件.当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息.这样就可以实现事件驱动了.
2.异步请求函数
异步请求函数允许应用程序用异步方式获得请求的信息,如WSAAsyncGetXByY()类函数. 这些函数是对BSD标准函数的扩充.函数WSACancelAsyncRequest()允许用户中止一个正在执行的异步请求.
3.阻塞处理方法
WINSOCK提供了"钩子函数"负责处理Windows消息,使Windows的消息循环能够继续.WINSOCK提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让应用程序设置或取消自己的"钩子函数".函数WSAIsBlocking()可以检测是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用.
4.错误处理
WINSOCK提供了两个WSAGetLastError()和WSASetLastError()来获取和设置最近错误号.
5.启动和终止
由于Windows Sockets的服务是以动态连接库WINSOCK.DLL形式实现的,所以必须要先调用WSAStartup()函数对Windows Sockets DLL进行初始化,协商WINSOCK的版本支持,并分配必要的资源.在应用程序关闭套接口后,还应调用WSACleanup()终止对Windows Sockets DLL的使用,并释放资源,以备下一次使用.
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用. 用法及详细说明参见第5.3.7.
3.3 Windows Sockets与UNIX套接口编程实例
下面是一个简单的基于连接的点对点实时通信程序.它由两部分组成,服务器在主机UNIX下直接运行, 客户机在Windows下运行.
3.3.1 SERVER介绍
由于SERVER是在UNIX下运行的,它对套接口的使用都是BSD的标准函数,程序也比较简单, 只有一段程序,下面简要解释一下.
首先,建立自己的套接口.在互连网的进程通信中,全局标识一个进程需要一个被称为"半相关"的三元组(协议,本地主机地址,本地端口号)来描述,而一个完整的进程通信实例则需要一个被称为"相关"的五元组(协议, 本地主机地址,本地端口号,远端主机地址,远端端口号)来描述.
s=socket(AF_INET, SOCK_STREAM, 0)
该函数建立指定地址格式,数据类型和协议下的套接口,地址格式为AF_INET(唯一支持的格式),数据类型SOCK_STREAM表示建立流式套接口,参数三为0,即协议缺省.
bind(s, (struct sockaddr *)&server, sizeof(server))
该函数将建立服务器本地的半相关,其中,server是sockaddr_in结构,其成员描述了本地端口号和本地主机地址,经过bind()将服务器进程在网上标识出来.
然后,建立连接.先是调用listen()函数表示开始侦听.再通过accept()调用等待接收连接.
listen(s,1)表示连接请求队列长度为1,即只允许有一个请求,若有多个请求,则出现错误,给出错误代码WSAECONNREFUSED.
ns = accept(s, (struct sockaddr *)&client, &namelen))
accept()阻塞(缺省)等待请求队列中的请求,一旦有连接请求来,该函数就建立一个和s有相同属性的新的套接口.client也是一个sockaddr_in结构,连接建立时填入请求连接的套接口的半相关信息.
接下来,就可以接收和发送数据了.
recv(ns,buf,1024,0)
send(ns,buf,pktlen,0)
上面两个函数分别负责接收和发送数据,recv从ns(建立连接的套接口)接收数据放入buf中,send则将buf中数据发送给ns.至于第四个参数,表示该函数调用方式,可选择MSG_DONTROUTE和MSG_OOB, 0表示缺省.
最后,关闭套接口.
close(ns);
close(s);
3.3.2 CLIENT介绍
客户端是在Windows上运行的,使用了一些Windows Sockets的扩展函数,稍微复杂一些.包括了.RC和.C两个文件,其中的主窗口函数ClientProc()是程序的主要部分,下面简单解释一下.
首先,是在WinMain()中建立好窗口后,即向主窗口函数发一条自定义的WM_USER消息, 做相关的准备工作.在主窗口函数中,一接收到WM_USER消息,首先调用WSAStartup()函数初始化Windows Sockets DLL,并检查版本号.如下:
Status = WSAStartup(VersionReqd, lpmyWSAData);
其中,VersionReqd描述了WINSOCK的版本(这里为1.1版),lpmyWSAData指向一个WSADATA结构,该结构描述了Windows Sockets的实现细节.
WSAStartup()之后,进程通过主机名(运行时命令行参数传入)获取主机地址,如下:
hostaddr = gethostbyname(server_address);
hostaddr指向hostent结构,内容参见5.2.1.
然后,进程就不断地消息循环,等待用户通过菜单选择"启动".这时,通过调用Client()来启动套接口.在Client()中,首先也是调用socket()来建立套接口.如下:
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
紧接着,调用WSAAsyncSelect()函数提名FD_CONNECT网络事件,如下:
if (!SetSelect(hWnd, FD_CONNECT))
return (FALSE);
SetSelect()主要就是调用WSAASyncSelect(),让Windows Sockets DLL在侦测到连接建立时,就发送一条UM_SOCK的自定义消息,使消息循环继续下去.如下:
BOOL SetSelect(HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR)
{
AlertUser(hWnd, "WSAAsyncSelect Failure.");
return (FALSE);
}
return (TRUE);
}
为建立连接,必须马上调用connect()如下,由于先调用了WSAASyncSelect(),connect()便是非阻塞调用.进程发出连接请求后就不管了,当连接建立好后,WINSOCK DLL自动发一条消息给主窗口函数,以使程序运行下去.
connect(s, (struct sockaddr FAR *)&dst_addr, sizeof(dst_addr));
窗口函数在收到UM_SOCK消息后,判断是由哪个网络事件引起的,第一次,必然是由连接事件引起的,这样,就会执行相应的程序段,同样调用SetSelect()来提名FD_WRITE事件.希望在套接口可发送数据时接到消息.在收到FD_WRITE消息时,先调用send()发送数据,再调用SetSelect()来提名FD_READ事件, 希望在套接口可接收数据是接到消息.在收到FD_READ消息时,先调用recv()来接收数据再提名FD_WRITE事件,如此循环下去.直到发生连接关闭的事件FD_CLOSE,这时就调用WSAAsyncSelect(s,hWnd,0,0)来停止异步选择.在窗口函数接到WM_DESTROY消息时(即关闭窗口之前),先调用closesocket()(作用同UNIX 中的close())来关闭套接口,再调用WSACleanup()终止Windows Sockets DLL,并释放资源.
3.3.3 源程序清单
程序1:CLIENT.RC
ClientMenu MENU
BEGIN
POPUP "&Server"
BEGIN
MENUITEM "&Start...", 101
MENUITEM "&Exit", 102
END
END
程序2:CLIENT.C
#define USERPORT 10001
#define IDM_START 101
#define IDM_EXIT 102
#define UM_SOCK WM_USER + 0X100
#include <alloc.h>
#include <mem.h>
#include <windows.h>
#include <winsock.h>
#define MAJOR_VERSION 1
#define MINOR_VERSION 2
#define WSA_MAKEWORD(x,y) ((y)*256+(x))
HANDLE hInst;
char server_address[256] = {0};
char buffer[1024];
char FAR * lpBuffer = &buffer[0];
SOCKET s = 0;
struct sockaddr_in dst_addr;
struct hostent far *hostaddr;
struct hostent hostnm;
struct servent far *sp;
int count = 0;
BOOL InitApplication(HINSTANCE hInstance);
long FAR PASCAL ClientProc(HWND hWnd, unsigned message, UINT wParam, LONG lParam);
void AlertUser(HWND hWnd, char *message);
BOOL Client(HWND hWnd);
BOOL ReceivePacket(HWND hWnd);
BOOL SetSelect(HWND hWnd, long lEvent);
BOOL SendPacket(HWND hWnd, int len);
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
MSG msg;
lstrcpy((LPSTR)server_address, lpCmdLine);
if (!hPrevInstance)
if (!InitApplication(hInstance))
return (FALSE);
hInst = hInstance;
hWnd = CreateWindow("ClientClass", "Windows ECHO Client", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (!hWnd)
return (FALSE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
PostMessage(hWnd, WM_USER, (WPARAM)0, (LPARAM)0);
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL InitApplication(HINSTANCE hInstance)
{
WNDCLASS WndClass;
char *szAppName = "ClientClass";
// fill in window class information
WndClass.lpszClassName = (LPSTR)szAppName;
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc = ClientProc;
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(hInstance, NULL);
WndClass.lpszMenuName = "ClientMenu";
WndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
// register the class
if (!RegisterClass(&WndClass))
return(FALSE);
return(TRUE);
}
long FAR PASCAL ClientProc(HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
int length, i;
WSADATA wsaData;
int Status;
switch (message)
{
case WM_USER:
{
WORD wMajorVersion, wMinorVersion;
LPWSADATA lpmyWSAData;
WORD VersionReqd;
int ret;
wMajorVersion = MAJOR_VERSION;
wMinorVersion = MINOR_VERSION;
VersionReqd = WSA_MAKEWORD(wMajorVersion,wMinorVersion);
lpmyWSAData = (LPWSADATA)malloc(sizeof(WSADATA));
Status = WSAStartup(VersionReqd, lpmyWSAData);
if (Status != 0)
{
AlertUser(hWnd, "WSAStartup() failed/n");
PostQuitMessage(0);
}
hostaddr = gethostbyname(server_address);
if (hostaddr == NULL)
{
AlertUser(hWnd, "gethostbyname ERROR!/n");
WSACleanup();
PostQuitMessage(0);
}
_fmemcpy(&hostnm, hostaddr, sizeof(struct hostent));
}
break;
case WM_COMMAND:
switch (wParam)
{
case IDM_START:
if (!Client(hWnd))
{
closesocket(s);
AlertUser(hWnd, "Start Failed");
}
break;
case IDM_EXIT:
// WSACleanup();
PostQuitMessage(0);
break;
}
break;
case UM_SOCK:
switch (lParam)
{
case FD_CONNECT:
if (!SetSelect(hWnd, FD_WRITE))
closesocket(s);
break;
case FD_READ:
if (!ReceivePacket(hWnd))
{
AlertUser(hWnd, "Receive Packet Failed./n");
closesocket(s);
break;
}
if (!SetSelect(hWnd, FD_WRITE))
closesocket(s);
break;
case FD_WRITE:
for (i = 0; i < 1024; i ++)
buffer[i] = (char)'A' + i % 26;
length = 1024;
if (!SendPacket(hWnd, length))
{
AlertUser(hWnd, "Packet Send Failed!/n");
closesocket(s);
break;
}
if (!SetSelect(hWnd, FD_READ))
closesocket(s);
break;
case FD_CLOSE:
if (WSAAsyncSelect(s, hWnd, 0, 0) == SOCKET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect Failed./n");
break;
default:
if (WSAGETSELECTERROR(lParam) != 0)
{
AlertUser(hWnd, "Socket Report Failure.");
closesocket(s);
break;
}
break;
}
break;
case WM_DESTROY:
closesocket(s);
WSACleanup();
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return(NULL);
}
void AlertUser(HWND hWnd, char *message)
{
MessageBox(hWnd, (LPSTR)message, "Warning", MB_ICONEXCLAMATION);
return;
}
BOOL Client(HWND hWnd)
{
memset(&dst_addr,'/0', sizeof (struct sockaddr_in));
_fmemcpy((char FAR *)&dst_addr.sin_addr,(char FAR *)hostnm.h_addr,hostnm.h_length);
dst_addr.sin_family = hostnm.h_addrtype;
dst_addr.sin_port = htons(USERPORT);
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
if (!SetSelect(hWnd, FD_CONNECT))
return (FALSE);
connect(s, (struct sockaddr FAR *)&dst_addr, sizeof(dst_addr));
return (TRUE);
}
BOOL ReceivePacket(HWND hWnd)
{
HDC hDc;
int length;
int i1,i2,i3;
char line1[255], line2[255], line3[255];
count ++;
if ((length = recv(s, lpBuffer, 1024, 0)) == SOCKET_ERROR)
return (FALSE);
if (length == 0)
return (FALSE);
if (hDc = GetDC(hWnd))
{
i1 = wsprintf((LPSTR)line1, "TCP Echo Client No.%d", count);
i2 = wsprintf((LPSTR)line2, "Receive %d bytes",length);
i3 = wsprintf((LPSTR)line3, "Those are:%c, %c, %c, %c, %c, %c",buffer[0],buffer[1],buffer[2],buffer[100],buffer[1000],buffer[1023]);
TextOut(hDc, 10, 2, (LPSTR)line1, i1);
TextOut(hDc, 10, 22, (LPSTR)line2, i2);
TextOut(hDc, 10, 42, (LPSTR)line3, i3);
ReleaseDC(hWnd, hDc);
}
return (TRUE);
}
BOOL SetSelect(HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR)
{
AlertUser(hWnd, "WSAAsyncSelect Failure.");
return (FALSE);
}
return (TRUE);
}
BOOL SendPacket(HWND hWnd, int len)
{
int length;
if ((length = send(s, lpBuffer, len, 0)) == SOCKET_ERROR)
return (FALSE);
else
if (length != len)
{
AlertUser(hWnd, "Send Length NOT Match!");
return (FALSE);
}
return (TRUE);
}
程序3:SERVER.C
#include <sys/types.h>
#include <sys/mntent.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define USERPORT 10001
#define HOST_IP_ADDR "192.1.1.2"
main(int argc, char **argv)
{
char buf[1024];
struct sockaddr_in client;
struct sockaddr_in server;
int s;
int ns;
int namelen;
int pktlen;
if ((s=socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("Socket()");
return;
}
bzero((char *)&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(USERPORT);
server.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (struct sockaddr *)&server, sizeof(server))<0)
{
perror("Bind()");
return;
}
if (listen(s,1)!=0)
{
perror("Listen()");
return;
}
namelen = sizeof(client);
if ((ns = accept(s, (struct sockaddr *)&client, &namelen)) ==-1)
{
perror("Accept()");
return;
}
for (;;)
{
if ((pktlen = recv(ns,buf,1024,0))<0)
{
perror("Recv()");
break;
}
else
if (pktlen == 0)
{
printf("Recv():return FAILED,connection is shut down!/n");
break;
}
else
printf("Recv():return SUCCESS,packet length = %d/n",pktlen);
sleep(1);
if (send(ns,buf,pktlen,0)<0)
{
perror("Send()");
break;
}
else
printf("Send():return SUCCESS,packet length = %d/n",pktlen);
}
close(ns);
close(s);
printf("Server ended successfully/n");
}
3.4 另一个精巧的应用程序实例-wshout
在本节中,我们通过一个经过精心选择的例子,进一步讨论一下Windows Sockets编程技术。例如如何编制客户机或服务器程序,如何应用TCP有连接服务(流式套接口)或UDP无连接服务(数据报套接口),如何进行阻塞或非阻塞方式的套接口操作等等,这些都是经常碰到的问题。接下来要介绍的wshout程序,可以通过灵活地设置不同选项来达到上述应用情况的任意组合,从而基本覆盖了应用Windows Sockets编程所可能碰到的问题,具有很好的研究参考价值。
由于该程序思路清晰,结构精良,所以我们不打算很详细地剖析每一个语句,而只是简要介绍一下整个程序的逻辑结构,并在源程序中加入适当的注释。我们相信,任何具有基本C语言和Windows编程经验的读者,都能很轻松地读懂绝大部分内容。经过仔细咀嚼和推敲后,更能得到一些编写优质程序的灵感。
该程序在FTP公司的PCTCP支撑环境下调试通过,不过只要读者拥有任何符合Windows Sockets 1.1规范的实现,也能顺利执行该程序。
3.4.1 源程序目录
1. wshout.c wshout主程序
2. wshout.h wshout头文件
3. wshout.rc wshout资源文件
4. ushout.c UDP客户机程序
5. ulisten.c UDP服务器程序
6. tshout.c TCP客户机程序
7. tlisten.c TCP服务器程序
8. errno.c 获取WSAE*错误描述字符串程序
9. resolve.c 客户机/服务器启动程序
在编译本程序时,笔者用的是BC3.1,只需做一个PRJ工程文件,将上述.c文件及winsock.lib包括进来就行了。请注意winsock.h应在include目录或当前目录中,winsock.lib可利用winsock.dll通过implib工具来建立。如果读者使用其他的编译器,可自行作相应的调整,在此不再赘述。
3.4.2 程序逻辑结构
3.4.3 源程序清单及注释
3.4.3.1 wshout.c清单
/*
* 文件名: WSHOUT.C
*/
/* MSC Include files: */
#include <stdio.h>
#include <io.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "wshout.h"
#define MAJOR_VERSION 1
#define MINOR_VERSION 2
#define WSA_MAKEWORD(x,y) ((y) * 256 + (x)) /* HI:Minor, LO:Major */
HANDLE hInst; /* 进程实例句柄 */
HWND hOurWnd; /* 主窗口句柄 */
HWND hMainDlg; /* 主对话框句柄 */
int ret; /* 工作变量 */
char prbuf[PRBUF_LEN]; /* 用于显示文本的工作缓冲区 */
SOCKET sd; /* 用于监听连接的套接口描述字 */
long temporary_option = 0L; /* 缺省为阻塞模式 */
long blocking_option = 0L; /* 阻塞模式的全局标识 */
int run_cancelled = 0; /* 指示何时按下了取消按钮 */
int len = 1024; /* 一次写的字节数 */
BOOL running = FALSE; /* 程序的运行状态 */
const int iTCP = 1; /* 指定为TCP Shout */
const int iUDP = 2; /* 指定为UDP Shout */
int iProto = 1; /* 缺省为TCP Shout */
int iPortNo = SOCK_SHOUT;
int temporary_protocol = 1; /* 在Settings()中使用 */
int iShout = 1;
int iListen = 2;
int iClientOrServer = 1; /* 缺省为Shout(客户机) */
int tClientOrServer = 1; /* 在Settings()中使用 */
char HostModeBuf[20];/* 保存模式字符串 */
WORD VersionReqd;
LPWSADATA lpmyWSAData;
int PASCAL
WinMain (HANDLE hInstance,HANDLE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
HWND hWnd;
MSG msg;
BOOL InitApp(HANDLE);
if (!hPrevInstance)
if (!InitApp(hInstance))
return (NULL);
hInst = hInstance;
hWnd = CreateWindow("MainMenu",
"Windows Shout",
WS_OVERLAPPEDWINDOW | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
if (!hWnd)
return (NULL);
hOurWnd = hWnd;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg); /* 翻译虚拟键码 */
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL InitApp(HANDLE hInstance)
{
HANDLE hMemory;
PWNDCLASS pWndClass;
BOOL bSuccess;
hMemory = LocalAlloc(LPTR, sizeof(WNDCLASS));
pWndClass = (PWNDCLASS) LocalLock(hMemory);
pWndClass->hCursor = LoadCursor(NULL, IDC_ARROW);
pWndClass->hIcon = LoadIcon(hInstance, (LPSTR) "SHOUT");
pWndClass->lpszMenuName = (LPSTR) "MainMenu";
pWndClass->lpszClassName = (LPSTR) "MainMenu";
pWndClass->hbrBackground = GetStockObject(WHITE_BRUSH);
pWndClass->hInstance = hInstance;
pWndClass->style = NULL;
pWndClass->lpfnWndProc = ShoutWndProc;
bSuccess = RegisterClass(pWndClass);
LocalUnlock(hMemory);
LocalFree(hMemory);
return (bSuccess);
}
long FAR PASCAL ShoutWndProc(HWND hWnd, WORD message,WORD wParam, LONG lParam)
{
FARPROC lpDialogBoxProc;
switch (message){
case WM_CREATE:
/* Put up the dialog box */
lpDialogBoxProc = MakeProcInstance(DialogProc, hInst);
DialogBox (hInst, (LPSTR) "MainDialog", hWnd, lpDialogBoxProc) ;
FreeProcInstance(lpDialogBoxProc);
PostMessage(hWnd, WM_DESTROY, 0, 0L);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd, message, wParam, lParam));
}
return NULL;
}
BOOL FAR PASCAL DialogProc(HWND hOurDlg, WORD message, WORD wParam, LONG lParam)
{
FARPROC lpProcAbout;
FARPROC lpProcSettings;
long lret;
WORD wMajorVersion, wMinorVersion;
char hostnm[64]; /* 包含主机名的工作缓冲区 */
switch (message) {
case WM_INITDIALOG:
/* 选择缺省主机 */
SetDlgItemText(hOurDlg, IDD_HNAME, "");
SendDlgItemMessage(hOurDlg, /* 对话框句柄 */
IDD_HNAME, /* 向何处发送msg */
EM_SETSEL, /* 选择字符 */
NULL, /* 附加信息 */
MAKELONG(0, 0x7fff)); /* 全部内容 */
SetFocus(GetDlgItem(hOurDlg, IDD_HNAME));
/* 初始化 */
hMainDlg = hOurDlg; /* 保存自己的窗口句柄 */
SetDlgItemText(hOurDlg, IDD_COHOST,"Shout to:"
SOCKETS规范及应用-WINDOWS网络编程接口
最新推荐文章于 2023-09-12 18:22:08 发布
本书详细介绍了Windows Sockets规范,包括1.1版和2.0.8版,旨在帮助读者理解和应用这套开放的、支持多种协议的网络编程接口。内容涵盖基本概念、套接口编程实例、Windows扩展函数,以及详细的库函数参考,适合网络应用开发的工程技术人员和学生参考学习。
摘要由CSDN通过智能技术生成