1、1第6章 TCP/IP网络编程6.1 TCP/IP协议概述协议概述6.3 TCP编程编程6.4 UDP编程编程6.5 组播编程组播编程6.6 WinSock I/O模型模型小结小结2基于TCP/IP协议的网络程序是当前网络通信的主要方式,TCP/IP协议目前也处于鼎盛时期。随着TCP/IP由IPv4向IPv6过渡,可以预见,这种通信协议还会使用很长时间。因此,基于TCP/IP协议的编程方法也是本书所介绍的重点内容之一。本章首先简要地介绍TCP/IP协议的基本内容,然后介绍TCP/IP在Windows操作系统下网络编程的重要编程接口WinSock,基于WinSock进行TCP/IP传输层两种通信
2、方式(即TCP和UDP)的编程,接着介绍基于WinSock的TCP/IP组播编程。为了使网络编程更加具有灵活性,本章介绍了三种I/O控制方法。36.1.1 6.1.1 基本概念基本概念TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是一系列协议,或者说是一个协议族,它定义了数据传输如何通过因特网进行交换。TCP/IP起源于20世纪60年代末美国政府资助的一个分组交换网络研究项目,到20世纪90年代已发展成为计算机之间最常用的组网协议。6.1 TCP/IP协议概述协议概述4TCP/IP允许分布在各地安装着完全不
3、同系统的计算机互相通信,是一个真正的开放系统。TCP/IP是根据它最主要的两个协议命名的,已经实际应用了许多年,并在世界范围内证明了它的有效性。51 1协议栈结构协议栈结构TCP/IP模型进一步提炼与合并了OSI模型,它取消了OSI模型中的表示层和会话层,并合并了数据链路层和物理层(由于主要与连接有关并依赖于阐述介质,因此TCP/IP参考模型实际上对物理层并没有定义),使得逻辑更加简洁明晰,在此基础上逐步实现了各种子协议。TCP/IP模型与OSI模型的比较如图6-1所示。6TCP/IP模型每一层所负责的功能如下:链路层:有时被称做数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机
4、中对应的网络接口卡,它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。网络层:有时也被称为互连网层,负责分组在网络中的活动,包括IP(网际协议)、ICMP(Internet互联网控制报文协议)以及IGMP(Internet组管理协议)。这一层TCP/IP有两个基本组件:一个是IP协议,另一个是路由协议。7图6-1 TCP/IP模型与OSI模型的比较8传输层:该层主要为两台主机上的应用程序提供端到端的数据通信,它分为两个不同的协议,即TCP(传输控制协议)和UDP(用户数据报协议)。TCP提供端到端的保证质量的数据传输,该层负责数据的分组、质量控制和超时重发等,对于应用层来说,就可以忽略这
5、些工作。UDP则只简单地把数据报从一端发送到另一端,至于数据是否到达或按时到达、数据是否损坏,这都必须由应用层来做。这两种协议各有用途,前者可用于面向连接的应用,而后者则在及时性服务中有着重要的用途,如网络多媒体通信等。9应用层:该层负责处理实际的应用程序细节,包括Telnet、HTTP、SMTP、FTP、DNS和SNMP等协议和应用。层与层之间的联系与逻辑分离是利用封装与分用过程分别实现的。102 2协议封装协议封装当应用程序传送数据时,数据按自上而下的方向被送入协议栈中,然后逐个通过每一层直到被当做一串比特流送入网络。其中,每一层对收到的上一层数据都要增加一些首部信息(有时还要增加尾部信息
6、),通过层层包裹完成数据的封装过程,使之适合网络传输,这相当于完成了1.1.2节提及的数字通信中的信道编码。以应用程序通过TCP协议传输数据为例,该过程如图6-2所示。11图6-2 应用程序通过TCP协议传输数据封装过程12用户数据首先被添加应用首部,传给TCP层;在TCP层,数据被再安装TCP首部后,传给IP层(TCP传给IP的数据单元称作TCP报文段或简称为TCP段);在IP层,数据被再次安装IP首部后,传给链路层(IP传给网络接口层的数据单元称作IP数据报);在链路层,数据再被安装以太网首部,并添加以太网尾部,形成适合网络(光、电信号)传输的数据比特流,该比特流称做帧(Frame)。读者
7、可能对首部和尾部的概念还不太了解。实际上,首部和尾部是一种协议指定的数据结构,按照一定的顺序规则填写数据。关于各种首部中的字段含义、用途将在后面章节进行详细介绍。133 3协议分用协议分用网络接口分别发送和接收IP、ARP和RARP数据。IP层接口负责发送ICMP、IGMP、TCP、UDP数据包,因此必须在以太网的帧首部、IP特定字段中加入某种形式的标识,以指明生成数据的网络协议。例如:以太网的帧首部有一个16bit的帧类型域,当目的主机收到一个以太网数据帧时,数据就开始从协议栈中由底向上升,同时去掉各层协议上加的报文首部。每层协议都要检查报文协议标识,以确定接收数据的上层协议。这个过程称做分
8、用(Demultiplexing),它解决了数据包接收时协议解析的问题,从而保证了各种不同的TCP/IP子协议能够被组合成一个整体(图6-3显示了该过程)。14图6-3 TCP/IP协议分用过程156.1.2 6.1.2 常用协议常用协议如前所述,TCP/IP协议是一个协议族,下面对常用的协议分别进行介绍。1 1MACMAC协议协议MAC(Media Access Control,媒体访问控制)协议最重要的功能是确定谁占有信道,即信道分配问题,其主要作用是保证信道的公平性和有效的资源共享。MAC的机制分为两类,即基于竞争的信道协议和无竞争的信道协议。基于竞争的信道协议是假定网络中没有中心实体来
9、分配信道资源,16每个节点必须通过竞争媒体资源来进行传送,当超过一个节点同时尝试发送时,碰撞就会发生(见7.1.1节所介绍的CSMA/CD协议)。相反,无竞争的信道协议为每个需要通信的节点分配专用的信道资源。无竞争的信道协议能够有效地减少冲突,其代价是突发数据业务的信道利用率可能会比较低。不同的传输介质决定了所使用的MAC标准,如:以太网遵循IEEE 802.3标准,令牌总线遵循IEEE 802.4标准,令牌环网遵循IEEE 802.5标准,等等。本书重点关心的IEEE 802.3标准定义了一种具有七个字段的MAC帧,包括:17前导符P、帧起始分界符SFD、目的地址DA、源地址SA、表示数据字
10、段字节数长度的字段LEN、要发送的数据字段、填充字段PAD和帧校验序列FCS等8个字段,这8个字段中除了数据字段和填充字段外,其余的长度都是固定的。图6-4就是以太网帧的结构,其首部由5个字段组成,包括前导符、起始帧分界符、目标地址、源地址、长度/类型构成;尾部由一个字段构成(CRC);上层协议的首部及数据夹在首部与尾部之间的数据字段里。18图6-4 以太网帧结构19这里使用的地址无论是DA还是SA,都是硬件地址,或称为MAC地址。MAC地址由网卡的生产厂商唯一设定给每一块不同的网卡。一块网卡依据数据帧的包头信息中是否写有它的MAC地址来决定是否接受并上传该帧。查阅本机MAC地址的方法很多,如
11、Windows的ipcongfig命令、NetBIOS的Astatus命令等。需要注意的是,IEEE 802.3标准的MAC帧不提供任何对收到的帧进行确认的机制,其通信确认在高层完成,这表明它是一种不可靠的介质。以太网MAC协议承载了其他TCP/IP上层子协议。202 2IPIP协议协议IP协议负责在TCP/IP主机之间提供数据报服务,进行数据封装,产生协议头。由于在以太网中帧的大小受限制,并且不同的帧可能由不同的网络路径传送,因此IP协议需要将较大的数据报文分割,并在目的主机处按正确顺序组合。另外,IP协议不负责包的校验,它是一种无连接、不可靠的传输。如果发生任何错误,IP协议则丢弃该数据报
12、,然后发送ICMP消息报给信源端。数据报的检测校验是由上层协议如TCP等提供的。无连接数据报并不维护任何关于后续数据报的状态,每个数据报的处理是相互独立的,即IP数据报可以不按发送顺序接收。21IP协议还需要负责寻找路由,因此它需要配一个确定的IP地址。在IP报文的包头中包含了源与目的的IP地址。一般来说,不会有应用程序直接访问IP协议。IP数据报是Internet上数据通信的基本单元,这些数据报不超过1000字节长,当人们打开Web页、下载文件或者发送E-mail时,这些数据报就在世界各地来回传输。IP协议包裹的协议有:ARP、RAPRP、ICMP、IGMP、路由协议。22网络互联的目的是提
13、供一个无缝的通信系统,为此,互联网协议必须屏蔽物理网络的具体细节,并提供一个虚拟网络的功能,使设计者可以在不考虑物理硬件细节的情况下自由地选择地址。在TCP/IP栈中,编址由IP协议规定,IP标准分配给每台主机一个32位的二进制数作为该主机的IP地址。在2011年6月即将正式投入运行的IPv6中,IP地址升至128位,这样IP资源变得更加丰富。IP协议将每个IP地址分割成前缀和后缀两部分。前缀用于确定计算机从属的物理网络,后缀则用于确定网络上一台单独的计算机。互联网中的每一个物理网络都有一个唯一的值作为网络号(Network Number)。23IP地址的层次性设计保证了以下两个重要性质:每台
14、计算机分配一个唯一的地址;网络号分配全球统一,但后缀可本地分配,无需全球统一。IP地址共分五类:A类、B类、C类、D类和E类。其中,A类、B类和C类为基本类;D类用于多播传送;E类属于保留类,现在不用。这种地址分配方法的优点是,通过判断从左到右第一个0出现的位置就可以区分地址类型。它们的格式如表6-1所示(其中,*代表网络号位数,X代表主机号位数)。24表6-1 IP地址分类25IP地址一般采用点分十进制的方法表示,例如10000001 00110100 00000110 00000000129.52.6.0。此外,需要特别注意以下几个特殊的IP地址:(1)网络地址:IP中主机地址为0,表示网
15、络地址,如128.211.0.0。(2)广播地址:网络号后跟一个所有位全是1的后缀,即直接广播地址。(3)回送地址:127.0.0.1用于测试。26(4)内网地址:B类地址中的10.0.0.010.255.255.255、172.16.0.0172.31.255.255,C类地址中的192.168.0.0192.168.255.255等三个地址段内的IP习惯上经常作为内部网络地址使用。除了给每个主机分配一个IP地址外,IP协议也规定给每个路由器分配IP地址。事实上,每个路由器会被分配了两个或更多个IP地址。一个路由器连接到多个物理网络,每一个IP地址包含一个特定物理网络的网络号。这个IP地址并
16、不标识一台特定的计算机,而是标识一台计算机和一个网络间的一个连接。27现在所有的主机都要求支持子网编址(RFC950,J.Mogul and J.Postel,1985),该功能要求,不仅要把IP地址看成由单纯的一个网络号和一个主机号组成,还要把主机号再分成一个子网号和主机号。这样做是因为A类和B类地址为主机号分配了太多空间,但事实上在一个网络中并不会有这么多主机,因此在NIC(Network Information Center)获得某个IP网络号后,就由系统管理员来决定是否建立子网,以及分配多少位给子网号和主机号。例如,这里有一个B类地址(140.252.0.0),在剩下的16位中,8位用
17、于子网号,8位用于主机号,其格式如图6-5所示。这样就允许有254个子网,每个子网可有254台主机。28图6-5 B类地址的子网编址举例29除了地址类型以外,主机还需要知道地址中分别有多少位用于子网号与主机号。这是在引导过程中由子网掩码所确定的。这个掩码是一个32位的值,其中值为1的位留给网络号和子网号,为0的位留给主机号。在上面的例子中,子网掩码就是255.255.255.0。通常规定,具有相同网络号的主机属于网内关系,不同网络号的主机属于网间关系。IP协议包裹或承载了ARP/RARP、ICMP、IGMP、OSPF等TCP/IP子协议。303 3ARP/RARPARP/RARP协议协议ARP
18、(Address Resolution Protocol,地址解析协议)和RARP(Reverse Address Resolution Protocol,逆向地址解析协议)是某些网络接口(如以太网和令牌环网)使用的特殊协议,用来转换IP层和MAC层使用的地址。由于IP地址只对TCP/IP有效,MAC地址只对网络访问层有意义,因此分配给主机使用的IP地址和它固有的MAC地址是互不相干的。31在物理网络上的数据帧交换依赖于MAC地址,而在网络层层面的IP地址赋予用户设定逻辑地址的权利,要使二者配合工作必须进行正确的转换。ARP实现了从IP地址到MAC地址的映射,而RARP负责根据NIC硬件地址去
19、查询对应的IP地址。ARP要求网络接口有一个硬件地址。在硬件上进行的数据帧交换必须要有正确的接口地址。TCP/IP的地址是32位的IP地址。仅知道主机的IP地址并不能让内核(如以太网驱动程序)发送数据帧给主机,内核必须知道目的端的硬件地址才能发送数据。32假设在一个以太网中,客户端要将一个IP报文发送到服务器端,那么客户端就必须把32位的IP地址转换成48位的以太网地址。ARP获取IP的过程可分为以下三个步骤:(1)ARP以广播的方式发送ARP Request数据帧给以太网的每个主机。ARP请求数据帧中包含目的主机的IP地址,意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址”。(2)目
20、的主机的ARP层收到这份广播报文后,识别出这是发送端在询问它的IP地址,于是发送一个ARP应答。这个ARP应答包含IP地址及对应的硬件地址。33(3)发送端收到ARP应答后,主机间通过使用ARP协议获得的硬件地址进行通信。ARP中规定了两种信息的基本类型:请求(Request)和应答(Response)。在以太网上解析IP地址时,ARP请求和应答分组的格式如附录4中的附表4-6所示(ARP亦可用于解析其他类型网络的IP地址以外的地址,紧跟着帧类型字段的前四个字段决定了最后四个字段的类型和长度)。344 4ICMPICMP协议协议ICMP(Internet Control Message Pro
21、tocol)是Internet控制报文协议。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着至关重要的作用。ICMP协议是一种面向非连接的协议,用于传输出错报告控制信息。它是一个非常重要的协议,对网络安全具有极其重要的意义。35它是TCP/IP协议族的一个子协议,属于网络层协议,主要用于在主机与路由器之间传递控制信息,包括报告错误、交换受限控制和状态信息等。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速度转发数据包等情况时,IP路
22、由器会自动发送ICMP消息。ICMP提供一致易懂的出错报告信息。发送的出错报文返回到发送原数据的设备,因为只有发送设备才是出错报文的逻辑接受者。发送设备随后可根据ICMP报文确定发生错误的类型,并确定如何才能更好地重发失败的数据报。但是ICMP唯一的功能是报告问题而不是纠正错误,纠正错误的任务由发送方完成。365 5IGMPIGMP协议协议Internet 组管理协议(IGMP)是因特网协议家族中的一个组播协议,用于 IP 主机向任一个直接相邻的路由器报告它们的组成员情况。IGMP信息封装在IP报文中,其IP的协议号为2,用来在IP主机和与其直接相邻的组播路由器之间建立、维护组播组成员关系。I
23、GMP不包括组播路由器之间的组成员关系信息的传播与维护,这部分工作由各组播路由协议完成。所有参与组播的主机必须实现IGMP。参与IP组播的主机可以在任意位置、任意时间、成员总数不受限制地加入或退出组播组。37组播路由器不需要也不可能保存所有主机的成员关系,它只是通过IGMP协议了解每个接口连接的网段上是否存在某个组播组的接收者,即组成员。而主机方只需要保存自己加入了哪些组播组。IGMP在主机与路由器之间是不对称的:主机需要响应组播路由器的IGMP查询报文,即以IGMP membership report报文响应;路由器周期性发送成员资格查询报文,然后根据收到的响应报文确定某个特定组在自己所在子
24、网上是否有主机加入,并且当收到主机的退出组的报告时,发出特定组的查询报文(IGMP版本2),以确定某个特定组是否已无成员存在。386 6路由协议路由协议当数据跨网传输时需要进行路由,通常路由包括两个基本的动作:确定最佳路径和让信息群(或称为分组)通过网络传输。通过网络传输分组相对较简单,而路径的确定却相对复杂,一般采用的方法就是查询路由表。路由表保存了通过路由器可能到达的目标网络以及如何到达该目标网络的信息,基本项包括目标网络地址、子网掩码以及到达目标网络的下一站路由器的地址,简介记录了网络间的位置关系。路由表由路由协议维护和更新,此外,路由协议还完成发送路由更新信息且基于路由算法决定路由的功
25、能,常见的路由协议有RIP、OSPF、BGP等。39RIP协议使用V-D算法在局域网上实现,它将参加者分为主动机和被动机两种。主动机主动地向外广播路径刷新报文,被动机被动地接受路径刷新报文。一般情况下,网关作主动机,主机作被动机。RIP规定一条路径的距离为该路径(从信源机到信宿机)上的网关数。为防止寻径回路的长期存在,RIP规定,长度为16的路径为无限长路径,即不存在路径。所以一条有限的路径长度不得超过15。正是这一规定限制了RIP的使用范围,使RIP局限于小型的局域网中。40OSPF(Open Shortest Path First)是一个内部网关协议(Interior Gateway Pr
26、otocol,IGP),用于在单一自治系统(Autonomous System,AS)内决策路由,一个自治系统的经典定义是在一个管理机构控制之下的一组路由器。与RIP相对,OSPF是链路状态路由协议,而RIP是距离向量路由协议。链路是路由器接口的另一种说法,因此OSPF也称为接口状态路由协议。OSPF通过路由器之间通告网络接口的状态来建立链路状态数据库,生成最短路径树,每个OSPF路由器使用这些最短路径构造路由表。41BGP(Border Gateway Protocol)是一种在自治系统之间动态交换路由信息的路由协议,它使用IGP(内部网关协议)和普通度量值向其他自治系统转发报文。BGP中使
27、用自治系统这个术语是为了强调这样一个事实:一个自治系统的管理对于其他自治系统而言是提供一个统一的内部选路计划,它为那些通过它可以到达的网络提供了一个一致的描述。三种协议分别采用不同的下层协议承载自己,即RIP使用UDP、OSPF使用IP、BGP使用TCP。427 7TCPTCP协议协议TCP(Transmission Control Protocol,传输控制协议)使用IP作为网络层协议。在网络通信传输机制中,它属于面向连接、可靠传输的类型。面向连接的传输意味着在进行通信以前,需要在两个系统之间建立逻辑连接,在每个数据传输的过程中都需要进行应答以保证数据包的完整。这种方法需要的网络开销较大,可
28、是数据传输的可靠性可以保证。虽然TCP使用不可靠的IP服务,但它却提供了一种可靠的传输层服务。TCP承载的高级应用与路由协议有HTTP、FTP、SMTP、BGP等。438 8UDPUDP协议协议UDP(User Datagram Protocol,用户数据报协议)属于面向无连接、不可靠传输的类型。该协议只负责接收和传送由上层协议传递的消息,它本身不做任何检测、修改与应答,上层协议需要自己处理这些事务。UDP的报头格式较简单,主要是地址信息、包的长度和校验信息。与此对应,TCP包的头信息有十多个域。因此UDP的网络开销一般要小于TCP。由于UDP在传送数据过程中没有建立连接,亦不进行检查,因此在
29、良好的网络环境中,其工作效率较TCP要高。44由于UDP的这种特点,因此亦是进行网络广播的首选协议。UDP承载的高级应用与路由协议有DNS、SNMP及RIP等。459 9应用协议应用协议应用协议是平时使用最广泛的协议,这层的每个协议都由两部分组成:客户程序和服务程序。程序通过服务器与客户机的交互来工作,如SNMP、FTP、SMTP、POP3等。应用协议被下层协议所承载,提供更加具体的应用服务,详见第12章。更多关于TCP/IP协议的介绍请参考相关书籍。466.1.3 TCP/IP6.1.3 TCP/IP地址函数地址函数TCP/IP协议下的WinSock函数与5.2.3节介绍的函数基本一致(实现
30、产生的区别见8.1节),特殊之处在于其地址编码。TCP/IP协议的地址编码遵循IP协议(参见6.1.2节),采用结构sockaddr_in实现。同时,为了解决不同情况下的地址格式转换与查询问题,还分别提供了一组地址转换函数和地址信息查询函数,下面分别进行介绍。471 1地址结构地址结构TCP/IP协议采用一种不同于IPX/SPX和NetBIOS的逻辑地址编码方法,使用IP地址结构体sockaddr。TCP/IP协议WinSock的bind、connect、recvfrom、sendto等都用这个地址结构来指明地址信息,该结构的定义如下:struct sockaddr unsigned shor
31、t sa_family;/地址协议族char sa_data14;/地址数据字符数组然而,一般在编程中,并不直接使用sockaddr结构,而是使用其等价结构sockaddr_in,定义如下:48struct sockaddr_inshort sin_family;/地址协议族unsigned short sin_port;/IP端口struct in_addr sin_addr;/IP地址结构char sin_zero8;/填充,使总长度与sockaddr一致其中,in_addr结构体存放的IP地址可以用标准点分式字符串、无符号短整型、无符号长整型等三种不同的方式描述。该结构是一个联合体(un
32、ion),定义如下:struct in_addr union49 struct unsigned char s_b1,s_b2,s_b3,s_b4;/标准点分式字符串 S_un_b;struct unsigned short s_w1,s_w2;/两个无符号短整型 S_un_w;unsigned long S_addr;/一个无符号长整型50 S_un;512 2地址转换地址转换在TCP/IP地址的使用中,不仅由于具有不同的字节顺序(网络字节顺序和主机字节顺序)需要转换,再加上地址本身的三种数据类型有时需要进行适当的转换,因此WinSock提供了以下一组转换函数。(1)ntohl()函数:将一
33、个u_long类型数(32位无符号整数)从TCP/IP网络字节顺序转换成主机字节顺序,原型如下:u_long WSAAPI ntohl(u_long netlong);/netlong是TCP/IP网络字节顺序表示的32位52/无符号整数(2)ntohs()函数:将一个u_short类型数(16位无符号整数)从TCP/IP网络字节顺序转换成主机字节顺序,原型如下:u_short WSAAPI ntohs(u_short netshort);/netshort是TCP/IP网络字节顺序表示的16位/无符号整数53(3)htonl()函数:将一个u_long类型数(32位无符号整数)从主机字节顺序
34、转换成TCP/IP网络字节顺序,原型如下:u_long WSAAPI htonl(u_long hostlong);/hostlong是指主机字节顺序表示的32位无符号整数(4)htons()函数:将一个u_short类型数(16位无符号整数)从主机字节顺序转换成TCP/IP网络字节顺序,原型如下:u_short WSAAPI htons(u_short hostshort);/hostshort是按主机字节顺序表示的16位无符号整数54上述函数很容易记忆,h代表主机字节顺序,n代表网络字节顺序,s代表无符号短整型,l代表无符号长整型,to代表转换目标。另外,还有两个重要的函数,即inet_a
35、ddr()和inet_ntoa(),用于IP地址点分表示法的转换。(1)inet_addr()函数将一个用点分表示法表示的地址的字符串地址转换成网际地址in_addr形式,所有网际地址都以网络字节顺序(字节顺序从左到右)返回,原型如下:unsigned long WSAAPI inet_addr(const char FAR*cp);/cp是含有用点分表示法表示的55/地址字符串(2)inet_ntoa()函数将一个网际地址转换成点分十进制表示法表示的字符串。它接收由参数in指定的网际地址结构,返回以点分表示法表示的地址的ASCII字符串,原型如下:char FAR*WSAAPI inet_n
36、toa(struct in_addr in);/in表示主机网际地址结构563 3地址查询地址查询TCP/IP还提供地址查询函数,可以在编程过程中根据需要获取主机信息。主机信息被放入一个hostent的结构体里。函数如下:struct hostent char FAR*h_name;/PC的官方名 char FAR*FAR*h_aliases;/PC的别名 short h_addrtype;/地址类型57 short h_length;/地址长度 char FAR*FAR*h_addr_list;/主机地址列表可以依据主机的地址得到该主机信息,函数如下:struct HOSTENT FAR*g
37、ethostbyaddr(const char FAR*addr,/主机地址 int len,/地址长度 int type);/地址类型58可以依据主机名得到该主机信息,函数如下:struct hostent FAR*gethostbyname(const char FAR*name);/指向主机名的指针 可以依据主机名得到扩展gethostbyname,函数如下:HANDLE WSAAsyncGetHostByName(HWND hWnd,/接收异步请求的窗口句柄unsigned int wMsg,/接收该异步请求的消息const char FAR*name,/指向主机名的指针char FA
38、R*buf,/接收信息的缓冲区指针int buflen);/缓冲区长度59获得本地主机名的函数如下:int gethostname(char FAR*name,/接收主机名的缓存区int namelen);/缓冲区长度上述部分函数需要PSDK(参见2.8.2节)的支持。60TCP/IP协议中的面向连接服务是TCP协议提供的,本节对TCP编程进行介绍。在WinSock编程中,TCP协议编程是用流套接字实现的。流套接字的服务进程和客户进程在通信前必须创建各自的套接字并建立连接,然后才能对相应的套接字进行读/写操作,实现数据的传输。6.3 TCP编程编程616.3.1 TCP6.3.1 TCP程序结
39、构程序结构在TCP通信中主要有连接的建立、数据的传输、连接的关闭等三个主要过程(如图6-6所示)。每个过程完成不同的工作,而且TCP包的序列号和确认号在每个过程中的变化都是不同的。TCP建立连接也就是我们常说的三次握手,它需要三步完成。在 TCP 建立连接后,就可以开始传输数据了。TCP工作在全双工模式,它可以同时进行双向数据传输。以服务器向客户端发送数据为例,服务器向客户端发送一个数据包,客户端收到这个数据包后,会向服务器发送一个确认数据包。TCP连接的关闭经历四次挥手的过程。62图6-6 流套接字程序时序图63下面是从套接字编程角度进行的服务端与客户端的函数过程,TCP通信的三个过程蕴含于
40、其中。建立套接字。用socket()函数完成。将指定协议的套接字绑定到它已知的名字上,这个名字就是本地的IP地址端口号。这个过程通过bind()函数完成。服务进程要处于监听状态,等待任意数量的客户端连接,以便为它们的请求提供服务。此服务进程必须在所绑定的名字上进行监听,所以要把套接字置为监听模式。通过listen()函数来实现。64 服务进程调用函数accept()或WSAAccept()准备接收来自客户端的连接,如果一个客户端用connect()函数试图建立连接,服务进程就可以接受连接。建立连接后,服务器和客户端之间就可以使用send()和recv()函数进行通信。注意,默认情况recv()
41、函数处于阻塞模式,在接收到数据前,程序不向下执行。通信结束后,调用closescoket()函数关闭套接字。注意:在此编程过程中,服务器必须首先启动,直到执行完accept()调用,进入等待状态后,方能接收客户请求。如客户在此之前启动,则connect()将返回出错代码,表示连接不成功。656.3.2 TCP6.3.2 TCP服务器端服务器端下列程序是一个面向连接的服务器端程序,能够侦听客户端的连接请求。当使客户端发送字符串“list0”到“list5”,服务器端每接收到一个字符串就回应一个echo信号,例如:接收到“list4”,回应“echo list4”。#include stdafx.
42、h#include winsock.h#include windows.h#include stdio.h66#pragma comment(lib,wsock32.lib)#define RECV_PORT 3000SOCKET sock,sock1;sockaddr_in ServerAddr;sockaddr_in ClientAddr;int Addrlen;DWORD StartSock()WSADATA WSAData;67 if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)/初始化套接字printf(sock init fail!n);return
43、(-1);return(1);DWORD CreateSocket()68sock=socket(AF_INET,SOCK_STREAM,0);/创建套接字if(sock=SOCKET_ERROR)printf(sock create fail!n);WSACleanup();return(-1);ServerAddr.sin_family=AF_INET;69 /填充服务器地址ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);ServerAddr.sin_port=htons(RECV_PORT);if(bind(sock,(struct sockad
44、dr FAR*)&ServerAddr,sizeof(ServerAddr)=SOCKET_ERROR)/绑定套接字为创建的sock指定通信对象 printf(bind is the error);return(-1);return(1);70DWORD ConnectProcess()char buff80;char buffecho80=echo;int i,length;Addrlen=sizeof(sockaddr_in);if(listen(sock,5)0)71 printf(Listen error);return(-1);else printf(Listening.n);soc
45、k1=accept(sock,(struct sockaddr FAR*)&ClientAddr,&Addrlen);printf(connect ok n);printf(wait for receive.n);for(i=0;i6;i+)72 memset(buff,0,80);if(recv(sock1,buff,80,0)=0)return-1;else printf(recive%sn,buff);strncpy(buffecho+5,buff,5);/依次生成ehco list0、ehco list1、ehco list2、ehco list3、/ehco list4、ehco l
46、ist5字符串并发送 length=send(sock1,buffecho,strlen(buffecho),0);if(length=0)73 printf(send data error!n);closesocket(sock);WSACleanup();return(-1);return(1);74int main(int argc,char*argv)if(StartSock()=-1)return(-1);if(CreateSocket()=-1)return(-1);if(ConnectProcess()=-1)return(-1);closesocket(sock);WSACle
47、anup();return 0;756.3.3 TCP6.3.3 TCP客户端客户端下面程序是6.3.2节程序对应的客户端程序,首先向服务端发出连接请求。连接建立后向服务端发送字符串“list0”到“list5”,之后接收来自服务端的响应。#include stdafx.h#include winsock.h#include windows.h#include stdio.h#include#pragma comment(lib,wsock32.lib)76#define SEND_PORT 3000SOCKET sock;sockaddr_in ServerAddr;DWORD Create
48、Socket()sock=socket(AF_INET,SOCK_STREAM,0);/创建套接字if(sock=SOCKET_ERROR)printf(sock create fail!n);77WSACleanup();return(-1);return(1);DWORD CallServer()CreateSocket();if(connect(sock,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr)=SOCKET_ERROR)78 printf(Connect fail n);closesocket(sock);return(-1);r
49、eturn(1);DWORD TCPSend(char data)int length;length=send(sock,data,strlen(data),0);79 if(length=0)printf(send data error!n);closesocket(sock);WSACleanup();return(-1);return(1);80DWORD TCPRecv(char data)int length;length=recv(sock,data,80,0);if(length=0)printf(send data error!n);closesocket(sock);WSAC
50、leanup();81return(-1);return(1);DWORD StartSock()WSADATA WSAData;if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)/初始化套接字82printf(sock init fail!n);return(-1);ServerAddr.sin_family=AF_INET;/填充服务器地址ServerAddr.sin_addr.s_addr=inet_addr(127.0.0.1);ServerAddr.sin_port=htons(SEND_PORT);return(1);83int main()char