《网络程序设计复习》由会员分享,可在线阅读,更多相关《网络程序设计复习(292页珍藏版)》请在金锄头文库上搜索。
1、1第第1 1章章 网络编程基础网络编程基础网络编程相关的基本概念网络编程相关的基本概念1.1三类网络编程三类网络编程1.2客户机客户机/服务器交互模式服务器交互模式1.3P2PP2P模式模式 1.421.1.1 1.1.1 网络编程与进程通信网络编程与进程通信1 1进程与线程的基本概念进程与线程的基本概念n进程是处于运行过程中的程序实例,是操作系统调度和分配资源的基本单位。n 一个进程实体由三部分构成。l程序代码:规定进程所做的计算。l数据:计算的对象。l进程控制块:是操作系统为了控制进程建立的数据结构,用来管理进程的内核对象,系统用来存放关于进程的统计信息。1.1网络编程相关的基本概念网络编
2、程相关的基本概念3n操作系统给进程分配内存空间:l静态分配空间:用来装入进程所有的可执行模块或动态链接库模块的代码及数据。l动态分配空间:栈区空间和堆区空间。n各种计算机应用程序在运行时,都以进程的形式存在,网络应用程序也不例外。nWindows系统不但支持多进程,还支持多线程。l进程是分配资源的单位;l线程是执行和调度的单位n由线程负责执行包含在进程的地址空间中的代码.1 1进程与线程的基本概念进程与线程的基本概念4n一个进程可以包含若干个线程,同时执行进程地一个进程可以包含若干个线程,同时执行进程地址空间中的代码。址空间中的代码。l当创建一个进程时,系统会自动创建它的第一当创建一个进程时,
3、系统会自动创建它的第一个线程,称为主线程。个线程,称为主线程。l然后,该线程可以创建其他的线程,而这些线然后,该线程可以创建其他的线程,而这些线程又能创建更多的线程。程又能创建更多的线程。l每个线程拥有自己的一组每个线程拥有自己的一组CPU寄存器和堆栈。寄存器和堆栈。l进程至少拥有一个线程,否则将被撤销。进程至少拥有一个线程,否则将被撤销。nWindows2000能在有多个能在有多个CPU的计算机上运行,的计算机上运行,每个每个CPU上运行不同的线程,达到多线程同时运上运行不同的线程,达到多线程同时运行。行。1 1进程与线程的基本概念进程与线程的基本概念51 1进程与线程的基本概念进程与线程的
4、基本概念图图1.1 1.1 单单CPUCPU分时地运行进程中的各个线程分时地运行进程中的各个线程6n从从计计算算机机网网络络体体系系结结构构的的角角度度来来看看,网网络络应应用用进程处于网络层次结构的最上层。进程处于网络层次结构的最上层。n从功能上,可以将网络应用程序分为两部分:从功能上,可以将网络应用程序分为两部分:l一一部部分分是是专专门门负负责责网网络络通通信信的的模模块块,它它们们与与网网络络协协议议栈栈相相连连接接,借借助助网网络络协协议议栈栈提提供供的的服服务完成网络上数据信息的交换。务完成网络上数据信息的交换。l另另一一部部分分是是面面向向用用户户或或者者作作其其他他处处理理的的
5、模模块块,它它们们接接收收用用户户的的命命令令,或或者者对对借借助助网网络络传传输输过过来的数据进行加工。来的数据进行加工。n这这两两部部分分模模块块相相互互配配合合,来来实实现现网网络络应应用用程程序序的功能。的功能。 2网络应用进程在网络体系结构中的位置网络应用进程在网络体系结构中的位置7图图1.2 网络应用程序在网络体系结构中的位置网络应用程序在网络体系结构中的位置2网络应用进程在网络体系结构中的位置网络应用进程在网络体系结构中的位置8n网络应用程序这两部分的关系:l通信模块,是网络分布式应用的基础;l其他模块,对网络交换的数据进行加工处理。l网网络络应应用用程程序序要要实实现现网网络络
6、资资源源的的共共享享,共共享享的的基基础就是必须能够通过网络轻松地传递各种信息。础就是必须能够通过网络轻松地传递各种信息。l网络编程首先要解决网间进程通信的问题,然后才能在通信的基础上开发各种应用功能。2网络应用进程在网络体系结构中的位置网络应用进程在网络体系结构中的位置93实现网间进程通信必须解决的问题实现网间进程通信必须解决的问题 网网间间进进程程通通信信是是指指网网络络中中不不同同主主机机中中的的应应用用进进程程之间的相互通信问题,必须解决以下问题:之间的相互通信问题,必须解决以下问题:l网间进程的标识问题网间进程的标识问题(不能只用进程号标识);l如如何何与与网网络络协协议议栈栈连连接
7、接的的问问题题(通过定义套接字网络编程接口来解决);l多重协议的识别问题多重协议的识别问题(不同协议工作方式不同);l不不同同通通信信服服务务的的问问题题(要求不同,如文件传输要求可靠、无差错、无乱序、无丢失,网络聊天要求不高,可选TCP和UDP服务)。101.1.2 1.1.2 因特网中网间进程的因特网中网间进程的标识标识1传输层在网络通信中的地位传输层在网络通信中的地位nTCP/IP协议栈的特点是协议栈的特点是“两头大、中间小两头大、中间小”l应用层有多个应用进程,使用不同应用层协议;l网络接口层,有多种数据链路层协议,支持不同的物理网络连接;l网络层有IP协议,传输层有TCP和UDP协议
8、。n按按照照OSI七七层层协协议议的的描描述述,传传输输层层与与网网络络层层在在功功能上的最大区别,是传输层提供进程通信的能力。能上的最大区别,是传输层提供进程通信的能力。nTCP/IP协协议议提提出出了了传传输输层层协协议议端端口口(简简称称端端口口)的概念,成功地解决了通信进程的标识问题。的概念,成功地解决了通信进程的标识问题。111.1.2 1.1.2 因特网中网间进程的因特网中网间进程的标识标识1传输层在网络通信中的地位传输层在网络通信中的地位 传输层是计算机网络中,通信主机内部进传输层是计算机网络中,通信主机内部进行独立操作的第一层,是支持端到端的进程通信的关行独立操作的第一层,是支
9、持端到端的进程通信的关键的一层。键的一层。图图1.3基于基于TCP/IP协议栈的进程间的通信协议栈的进程间的通信122 2端口的概念端口的概念n端口是TCP/IP协议族中,应用层进程与传输层协议实体间的通信接口n在OSI七层协议描述中,将其称为应用层进程与传输层协议实体间的服务访问点(SAP)。l应用层进程通过系统调用与某个传输层端口进行绑定,然后通过该端口接收或发送数据。l类似于文件描述符,每个端口都拥有一个叫作端口号(port number)的16位整数型标识符。l可以用端口标识通信的网络应用程序。132 2端口的概念端口的概念图1.4UDP与TCP的报文格式n传输层TCP和UDP两个协议
10、是完全独立的软件模块,因此各自的端口号也独立。l使用时必须说明是UDP端口还是TCP端口,两种协议的端口间没有任何联系。lTCP 和UDP都可以提供65536个端口。l端口是操作系统可分配的一种资源。14n 从从实实现现的的角角度度讲讲,端端口口是是一一种种抽抽象象的的软软件件机机 制,包括一些数据结构和制,包括一些数据结构和I/OI/O缓冲区。缓冲区。l进程通过系统调用与某端口建立绑定关系后,传输层传给该端口的数据都被相应进程接收,相应进程发给传输层的数据都通过该端口输出。n在在TCP/IP实现中端口操作类似于一般的实现中端口操作类似于一般的I/O操作。操作。l进程获取一个端口,相当于获取本
11、地唯一的I/O文件,可以用一般的读写原语访问。 2 2端口的概念端口的概念153端口号的分配机制端口号的分配机制n网络进程通信前必须获知对方的进程地址。网络进程通信前必须获知对方的进程地址。l由于网络应用程序大多采用C/S模式开发,通信总是由客户机发起,因此事先只需让客户机知道服务器进程的端口号即可。lInternet中众所周知的服务是有限的。nTCP/IPTCP/IP协协议议采采用用了了全全局局分分配配(静静态态分分配配)和和本本地分配(动态分配)地分配(动态分配)相结合的分配方法。相结合的分配方法。n对对于于TCPTCP,或或者者UDPUDP,将将它它们们的的全全部部6553665536个
12、个端端口口号分为号分为保留端口号保留端口号和和自由端口号自由端口号两部分。两部分。 16n保留端口号保留端口号:范围是:范围是0-10230-1023,又称为,又称为众所周知的众所周知的端口端口或或熟知端口熟知端口(well-known portwell-known port),只占少),只占少数,采用全局分配或集中控制的方式,由一个公数,采用全局分配或集中控制的方式,由一个公认的中央机构根据需要进行统一分配,静态地分认的中央机构根据需要进行统一分配,静态地分配给因特网上著名的众所周知的服务器进程,并配给因特网上著名的众所周知的服务器进程,并将结果公布于众。将结果公布于众。 3端口号的分配机制
13、端口号的分配机制TCP的保留端口UDP的保留端口FTP21DNS53HTTP80TFTP69SMTP25SNMP161POP3110表1.1 一些典型的应用层协议分配到的保留端口 17n自由端口号自由端口号: :范围是范围是1024-655351024-65535,采用本地分配,又,采用本地分配,又称为动态分配的方法。称为动态分配的方法。 nTCPTCP或或UDPUDP端口的分配规则是:端口的分配规则是:l端口端口0 0:不使用,或者作为特殊的用途;:不使用,或者作为特殊的用途;l端端口口1-2551-255:保保留留给给特特定定的的服服务务,TCPTCP和和UDPUDP均均规规定定,小于小于
14、256256的端口号才能分配给网上著名的服务;的端口号才能分配给网上著名的服务;l端口端口256-1023256-1023:保留给其他的服务,如路由;:保留给其他的服务,如路由;l端口端口1024-49991024-4999:可以用作任意客户机的端口;:可以用作任意客户机的端口;l端口端口5000-655355000-65535:可以用作用户的服务器端口。:可以用作用户的服务器端口。3端口号的分配机制端口号的分配机制183端口号的分配机制端口号的分配机制我们可以描述一下,在这样的端口分配机制下,客户机进程C与服务器进程S第一次通信的情景。图1.5 客户机与服务器的第一次通信19n为为确确保保服
15、服务务器器进进程程为为多多个个客客户户机机进进程程服服务务,服服务务器器的的保保留留端端口口是是专专门门用用来来监监听听客客户户端端的的连连接接请求的。请求的。n当当服服务务器器从从保保留留端端口口接接收收到到一一个个客客户户机机的的请请求求后后,立立即即创创建建另另外外一一个个线线程程,并并为为这这个个线线程程分分配配一一个个自自由由端端口口(在在500050006553565535选选择择分分配配),然后继续接收新的客户机请求。然后继续接收新的客户机请求。3端口号的分配机制端口号的分配机制204 4进程的网络地址的概念进程的网络地址的概念n 在在因因特特网网中中,用用一一个个三三元元组组可
16、可以以在在全全局局中中唯唯一一地地标标识一个应用层进程:识一个应用层进程: 应应用用层层进进程程地地址址= =(传传输输层层协协议议,主主机机的的IPIP地地址,址, 传传输输层层的端口号)的端口号)n这样一个三元组,叫做一个这样一个三元组,叫做一个半相关半相关(half-half-associationassociation),它标识了因特网中进程间通信的一),它标识了因特网中进程间通信的一个端点,也把它称为个端点,也把它称为进程的网络地址进程的网络地址。 215 5网络中进程通信的标识网络中进程通信的标识n 一一个个完完整整的的网网间间通通信信需需要要一一个个五五元元组组在在全全局局中中唯
17、唯一地来标识:一地来标识:( (传传输输层层协协议议,本本地地机机IPIP地地址址,本本地地机机传传输输层层端端口口, 远地机远地机IPIP地址,远地机传输层端口地址,远地机传输层端口) )n这个五元组称为一个这个五元组称为一个全相关全相关(associationassociation),即),即两个协议相同的半相关才能组合成一个合适的全两个协议相同的半相关才能组合成一个合适的全相关,或完全指定一对网间通信的进程。相关,或完全指定一对网间通信的进程。 221.1.3 1.1.3 网络协议的网络协议的特征特征1面向连接的服务和无连接的服务面向连接的服务和无连接的服务n协协议议可可以以提提供供面面
18、向向连连接接的的服服务务,或或者者提提供供无无连连接的服务。接的服务。n面面向向连连接接服服务务是是电电话话系系统统服服务务模模式式的的抽抽象象,即即每每一一次次完完整整的的数数据据传传输输都都要要经经过过建建立立连连接接,使用连接,终止连接的过程。使用连接,终止连接的过程。l传输过程中数据分组不携带目的地址;lTCP提供面向连接的虚电路服务,建立连接时确定通信路径,并经过协商做好通信准备。l连接需要很多开销,如差错控制和流量控制。23n无无连连接接服服务务,是是邮邮政政系系统统服服务务的的抽抽象象,每每个个分分组组都都携携带带完完整整的的目目的的地地址址,各各分分组组在在系系统统中中独独立立
19、传传送。送。l不能保证分组按序到达,不能进行分组出错的恢复与重传,不能保证传输的可靠性。l通信前不需建立连接,不管接收端是否做好准备接收数据。lUDP是无连接协议。1面向连接的服务和无连接的服务面向连接的服务和无连接的服务242 2面向消息的协议与基于流的协议面向消息的协议与基于流的协议(1 1)面向消息的协议)面向消息的协议n 面向消息的协议以消息为单位在网上传送数据,在面向消息的协议以消息为单位在网上传送数据,在发送端,消息一条一条地发送,在接收端,也只能发送端,消息一条一条地发送,在接收端,也只能一条一条地接收,每一条消息是独立的,消息之间一条一条地接收,每一条消息是独立的,消息之间存在
20、着边界。存在着边界。 n保护消息边界保护消息边界:是指传输协议把数据当作一条独立:是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息,的消息在网上传输,接收端只能接收独立的消息,即接收端一次只能接收发送端发出的一个数据包。即接收端一次只能接收发送端发出的一个数据包。25图1.6 保护消息边界的数据报传输服务 nUDP就是面向消息的,适合于交换结构化数据。就是面向消息的,适合于交换结构化数据。(1 1)面向消息的协议)面向消息的协议26(2 2)基于流的协议)基于流的协议n基基于于流流的的协协议议不不保保护护消消息息边边界界,将将数数据据当当作作字字节节流连续地传输,不管实
21、际消息边界是否存在。流连续地传输,不管实际消息边界是否存在。l发送端允许系统将原始消息分解成几条小消息分别发送,或把几条消息积累在一起形成大数据包发送,多次发送的数据统一编号。l如果发送端连续发送数据,接收端有可能在一次接收动作中接收两个或更多的数据包。l只要数据一到达,网络堆栈就开始读取并将其缓存,等待进程处理。27(2 2)基于流的协议)基于流的协议 图1.7 无消息边界的流传输服务nTCP是基于流的协议。是基于流的协议。n流传输,把数据当作一串数据流,不认为数据是一个一个的消息,编程时不要忽略这一点。28l1.1.4 1.1.4 高效的用户数据报协议高效的用户数据报协议UDPUDPn用户
22、数据报协议用户数据报协议(UserDatagramProtocol,UDP),是一种尽力传送的、无连接的、不保障),是一种尽力传送的、无连接的、不保障可靠的传输服务,是一种保护消息边界的数据传可靠的传输服务,是一种保护消息边界的数据传输。输。l基于UDP的应用程序在高可靠性、低延迟的网络中运行良好;l在网络层的基础上只增加了端口号的支持;l传输效率高,适用于交易型的应用程序,如TFTP、SNMP、DNS等应用进程。291.1.5 可靠的传输控制协议TCP1可靠性是很多应用的基础 2TCP为应用层提供的服务 传输控制协议传输控制协议TCPTCP (Transmission Control (Tr
23、ansmission Control ProtocolProtocol,TCP)TCP)为应用层进程提供一个面向连接为应用层进程提供一个面向连接的、端到端的、完全可靠的(无差错、无丢失、的、端到端的、完全可靠的(无差错、无丢失、无重复或失序)全双工的流传输服务。无重复或失序)全双工的流传输服务。 nIP为TCP提供的是无连接的、尽力传送的、不可靠的传输服务,TCP为了向应用层进程提供可靠的传输服务,采取了一系列保障机制。nTCP提供流传输服务,对传输数据的内部结构一无所知,只负责将字节流原封不动的传送到对方的应用进程。303 3TCPTCP利用利用IPIP数据报实现了端对端的传输服务数据报实现
24、了端对端的传输服务nTCPTCP被称作一种端对端(被称作一种端对端(end to endend to end)协议,因为它提)协议,因为它提供一个直接从一台计算机上的应用进程到另一远程计算供一个直接从一台计算机上的应用进程到另一远程计算机上的应用进程的连接。机上的应用进程的连接。l应用进程能请求应用进程能请求TCPTCP构造一个连接,通过这个连构造一个连接,通过这个连 接发接发送和接收数据,以及关闭连接。送和接收数据,以及关闭连接。l由由TCPTCP提供的连接叫做虚连接,虚连接是由软件实现提供的连接叫做虚连接,虚连接是由软件实现的。事实上,底层的因特网系统并不对连接提供硬件或的。事实上,底层的
25、因特网系统并不对连接提供硬件或软件支持,只是两台机器上的软件支持,只是两台机器上的TCPTCP软件模块通过交换消软件模块通过交换消息来实现连接的虚拟。息来实现连接的虚拟。 31图1.8 TCP是一个端到端的传输协议 3 3TCPTCP利用利用IPIP数据报实现了端对端的传输服务数据报实现了端对端的传输服务n从从TCP角度来看,整个角度来看,整个Internet是一个通信系统,是一个通信系统,能够接收和传递消息,而不会改变和干预消息的能够接收和传递消息,而不会改变和干预消息的内容。内容。324 4三次握手三次握手n为确保连接的建立和终止都是可靠的,为确保连接的建立和终止都是可靠的,TCPTCP使
26、用三使用三次握手(次握手(3-way handshake3-way handshake)的方式来建立连接)的方式来建立连接. .图图1.9 TCP1.9 TCP的三次握手过程的三次握手过程 33n已证明:三次握手是在包丢失、重复和延迟的情已证明:三次握手是在包丢失、重复和延迟的情况下确保非模糊协定的充要条件。况下确保非模糊协定的充要条件。l如图所示,前两个被称为SYN段。lTCP会重发丢失的SYN段。l三次握手确保TCP不会打开或关闭一个连接,直到两端达成一致。l创建一个连接的三次握手中,要求每一端产生一个随机32位序列号。l在计算机重启后,尝试建立一个新的TCP连接时,要选择一个新的随机数,
27、可保证不受老连接的重复或延迟包的影响。4 4三次握手三次握手341.2.1 1.2.1 基于基于TCP/IPTCP/IP协议栈的网络编程协议栈的网络编程三类网络编程三类网络编程1.2n最最基基本本的的网网络络编编程程方方式式,主主要要是是使使用用各各种种编编程程语语言言,利利用用操操作作系系统统提提供供的的套套接接字字网网络络编编程程接接口口,直接开发各种网络应用程序。直接开发各种网络应用程序。n本门课程主要讲解这种网络编程的相关技术。本门课程主要讲解这种网络编程的相关技术。l直接利用网络协议栈提供的服务来实现网络应用,层次比较低,编程者自由度比较大,在利用套接字实现了网络进程通信以后,可以编
28、写各种网络应用程序。l需掌握套接字网络编程接口及应用层协议。351.2.2 1.2.2 基于基于WWWWWW应用的网络应用的网络编程编程nWWW称为万维网或Web,是因特网上最广泛的应用。n基于WWW应用的网络编程技术,包括:l所见即所得的静态网页制作;uHTML、JavaScript等。l动态服务器页面的制作。uASP、PHP、JSP、J2EE、Hibernate、Spring、Struts等技术。36客户机客户机/服务器交互模式服务器交互模式1.3n本本节节着着重重于于因因特特网网上上的的高高级级服服务务,以以及及提提供供这些服务的应用软件。这些服务的应用软件。n讨讨论论网网络络应应用用软
29、软件件的的客客户户机机服服务务器器交交互互模模式式,并并说说明明网网络络协协议议操操作作的的方方式式为为什什么么需需要要这种模式。这种模式。n这是构筑所有网络应用的基础。这是构筑所有网络应用的基础。371.3.2 1.3.2 客户机客户机服务器服务器模式模式n网网络络应应用用进进程程通通信信时时,普普遍遍采采用用客客户户机机服服务务器器交交 互互 模模 式式 ( client-server client-server paradigm paradigm of of interactioninteraction),简称),简称C/SC/S模式模式. .n这是因特网上应用程序最常用的通信模式。这是
30、因特网上应用程序最常用的通信模式。nC/S模式的建立基于以下两点:l客户机与服务器之间的关系是非对等的,服务器提供资源,客户机请求共享这些资源;l网间进程通信是完全异步的,互相通信的进程间既不存在父子关系,又没有共享内存缓冲区,需要一种机制为二者间的数据交换提供同步. 381、服务器的工作过程nC/S模式过程中服务器处于被动服务的地位。n服务器要先启动,并根据客户机请求提供相应服务:l打开一通信通道,并告知服务器所在的主机,并愿意在某一公认的地址上(熟知端口,如FTP为21)接收客户机请求。l等待客户机的请求到达该端口。l服务器接收到服务请求,处理该请求并发送应答信号。为了能并发地接收多个客户
31、机的服务请求,要激活一个新进程或新线程来处理这个客户机请求(如UNIX系统中用fork、exec)。服务完成后,关闭此新进程与客户机的通信链路并终止.l返回第二步,等待并处理另一客户请求。l在特定的情况下,关闭服务器。 392、客户机的工作过程n客户机采取的是主动请求方式:l打开一通信通道,并连接到服务器所在主机的特定监听端口。l向服务器发送请求报文,等待并接收应答;继续提出请求,与服务器的会话按照应用协议进行。l请求结束后,关闭通信通道并终止。401.3.2 1.3.2 客户机客户机服务器服务器模式模式表表1.2 一些常见的网络应用一些常见的网络应用网络应用客户端软件服务器软件应用层协议电子
32、邮件foxmail电子邮件服务器SMTP、Pop3文件传输CuteFTP文件传输服务器FTPWWW浏览IE浏览器IIS服务器HTTP411.3.3 1.3.3 客户机与服务器的特性客户机与服务器的特性n客户机软件的特点客户机软件的特点n服务器软件的特点服务器软件的特点n基于因特网的基于因特网的C/S模式的应用程序的特点模式的应用程序的特点421.3.3 1.3.3 客户机与服务器的特性客户机与服务器的特性1 1客户机软件特点客户机软件特点在在进进行行网网络络通通信信时时临临时时成成为为客客户户机机,但但它它也也可可在在本本地进行其他的计算。地进行其他的计算。被被用用户户调调用用,只只为为一一个
33、个会会话话运运行行。在在打打算算通通信信时时主主动向远地服务器发起通信。动向远地服务器发起通信。能能访访问问所所需需的的多多种种服服务务,但但在在某某一一时时刻刻只只能能与与一一个个远程服务器进行主动通信。远程服务器进行主动通信。主动地启动与服务器的通信。主动地启动与服务器的通信。在在用用户户的的计计算算机机上上运运行行,不不需需要要特特殊殊的的硬硬件件和和很很复复杂的操作系统。杂的操作系统。432 2服务器软件的特点服务器软件的特点是是一一种种专专门门用用来来提提供供某某种种服服务务的的程程序序,可可同同时时处处理多个远端客户机的请求。理多个远端客户机的请求。当当系系统统启启动动时时即即自自
34、动动调调用用,并并且且连连续续运运行行着着,不不断地为多个会话服务。断地为多个会话服务。接接受受来来自自任任何何客客户户机机的的通通信信请请求求,但但只只提提供供一一种种服务。服务。被被动动地地等等待待并并接接受受来来自自多多个个远远端端客客户户机机的的通通信信请请求。求。在在共共享享计计算算机机上上运运行行,一一般般需需要要强强大大的的硬硬件件和和高高级的操作系统支持。级的操作系统支持。1.3.3 1.3.3 客户机与服务器的特性客户机与服务器的特性443 3基于因特网的基于因特网的C/SC/S模式的应用程序的特点模式的应用程序的特点客客户户机机和和服服务务器器都都是是软软件件进进程程,C/
35、S模式是网络上通过进程通信建立分布式应用的常用模型。非非对对称称性性:服务器通过网络提供服务,客户机通过网络使用服务,这种不对称性体现在软件结构和工作过程上。对等性:对等性:客户机和服务器必有一套共识的约定,必与某种应用层协议相联,并且协议必须在通信的两端实现。比如浏览器和3W服务器就都基于HTTP超文本传输协议。 1.3.3 1.3.3 客户机与服务器的特性客户机与服务器的特性45服服务务器器的的被被动动性性:服务器必须先行启动,时刻监听,日夜值守,及时服务,只要有客户机请求,就立即处理并响应,回传信息,但决不主动提供服务。客客户户机机的的主主动动性性:客户机可以随时提出请求,通过网络得到服
36、务,也可以关机走人,一次请求与服务的过程是由客户机首先激发的。一一对对多多:一个服务器可以为多个客户机服务,客户机也可以打开多个窗口,连接多个服务器。分分布布性性与与共共享享性性:资源在服务器端组织与存储,通过网络为分散的多个客户机使用。1.3.3 1.3.3 客户机与服务器的特性客户机与服务器的特性3 3基于因特网的基于因特网的C/SC/S模式的应用程序的特点模式的应用程序的特点461.3.4 1.3.4 容易混淆的术语容易混淆的术语1服务器程序与服务器类计算机服务器程序与服务器类计算机n服服务务器器(serverserver)这这个个术术语语来来指指那那些些运运行行着着的的服服务务器器程序
37、。程序。n服服务务器器类类计计算算机机(server-class server-class computercomputer)这这一一术术语语来称呼那些运行服务器软件的强大的计算机。来称呼那些运行服务器软件的强大的计算机。47图1.10 用户和客户机、服务器和服务器类计算机 2客户机与用户n“客户机”(client)和服务器都指的是应用进程,即计算机软件。n“用户”(user)指的是使用计算机的人。 1.3.4 1.3.4 容易混淆的术语容易混淆的术语481.3.5 客户机与服务器的通信过程客户机与服务器的通信过程n客户机与服务器的通信过程一般是这样的:客户机与服务器的通信过程一般是这样的:通
38、信之前,服务器应先行启动,并通知它的下层协议栈做好接收客户机请求的准备,然后被动地等待客户机的通信请求,称服务器处于监听状态。一般是先由客户机向服务器发送请求,服务器向客户机返回应答。客户机随时可以主动启动通信,向服务器发出连接请求,服务器接收这个请求,建立了二者的通信关系。客户机与服务器的通信关系一旦建立,客户机和服务器都可发送和接收信息。信息在客户机与服务器之间可以沿任一方向或两个方向传递。在某些情况下,客户机向服务器发送一系列请求,服务器相应地返回一系列应答。491.3.6网络协议与网络协议与C/S模式的关系模式的关系n客户机与服务器作为两个软件实体,它们之间的通信是虚拟的,是概念上的,
39、实际的通信要借助下层的网络协议栈来进行。n网络应用进程与应用层协议间的关系:l为了解决具体应用问题而彼此通信的进程,称为网络应用进程;l应用层协议并不解决任何具体问题,而是规定了网络应用进程通信时必须遵守的约定。l应用层协议在网络应用进程之下,并为网络应用进程服务,帮助应用进程组织数据。501.3.7错综复杂的错综复杂的C/S交互交互n在C/S模式中,存在着三种一个与多个的关系:一个服务器同时为多个客户机服务;一个用户的计算机上同时运行多个连接不同服务器的客户机;一个服务器类的计算机同时运行多个服务器。51图1.11 一台计算机中的多个服务器被多个计算机的客户机访问 1.3.7 1.3.7 1
40、.3.7 1.3.7 错综复杂的错综复杂的错综复杂的错综复杂的C/SC/S交互交互交互交互521.3.8服服务器如何同器如何同时为多个客多个客户机服机服务n并发性是客户机服务器交互模式的基础,并发允许多个客户机获得同一种服务,而不必等待服务器完成对上一个请求的处理。这样才能很好地同时为多个客户机提供服务。n在设计并发服务器时,可以让主服务器线程为每个客户机请求创建一个新的子服务线程。n一般服务器程序代码由两部分组成,第一部分代码负责监听并接收客户请求,还为客户机请求创建新的服务线程;另一部分代码负责处理单个客户机请求,如与客户机交换数据,并提供具体的服务。531.3.8 1.3.8 服务器如何
41、同时为多个服务器如何同时为多个客户机服务客户机服务图1.12 服务器创建多个线程来为多个客户机服务 541.3.9标识一个特定服务标识一个特定服务 在在一一台台服服务务器器类类的的计计算算机机中中可可以以并并发发地地运运行行多多个个服服务务器器进进程程。它它们们都都要要借借助助协协议议栈栈来来交交换换信信息息,协议栈就是多个服务器进程传输数据的公用通道,协议栈就是多个服务器进程传输数据的公用通道, 这有了一个问题,既然在一个服务器类计算机这有了一个问题,既然在一个服务器类计算机中运行着多个服务器,如何能让客户机无二义性地中运行着多个服务器,如何能让客户机无二义性地指明所希望的服务?指明所希望的
42、服务?55图1.13 沙漏计时器形状的TCP/IP协议族 1.3.9 1.3.9 标识一个特定服务标识一个特定服务56n这个问题由传输协议栈提供的一套机制来解决。这个问题由传输协议栈提供的一套机制来解决。n这种机制必须赋给每个服务一个唯一的标识,并这种机制必须赋给每个服务一个唯一的标识,并要求服务器和客户机都使用这个标识。要求服务器和客户机都使用这个标识。l当服务器开始执行时,它在本地的协议栈软件中登记,指明它所提供的服务的标识。l当客户机与远程服务器通信时,客户机在提出请求时,通过这个标识来指定所希望的服务。l客户机端机器的传输协议栈软件将该标识传给服务器端机器。l服务器端机器的传输协议栈则
43、根据该标识来决定由哪个服务器程序来处理这个请求。 1.3.9 1.3.9 标识一个特定服务标识一个特定服务57P2PP2P模式模式 1.4n随着应用规模的不断扩大,软件复杂度不断提高,面对巨大的用户群,单服务器成了性能的瓶颈 ,为了解决这些问题,就出现了P2P技术。 1.4.1 1.4.1 1.4.1 1.4.1 P2PP2PP2PP2P的定义和特征的定义和特征的定义和特征的定义和特征nP2P技术就是一种在计算机之间直接进行资源和服务的共享,不需要服务器介入的网络技术。在P2P网络中,每台计算机同时充当着Server和Client的角色,当需要其他电脑的文件和服务时,两台电脑直接建立连接,本机
44、是Client;而当响应其他电脑的资源要求时,本机又成为提供资源与服务的Server。 58第第2 2章章 UNIXUNIX中的套接字网络编程接口中的套接字网络编程接口套接字网络编程接口的产生与发展套接字网络编程接口的产生与发展2.1套接字编程的基本概念套接字编程的基本概念2.2面向连接的套接字编程面向连接的套接字编程2.3无连接的套接字编程无连接的套接字编程 2.4592.1.3套接字编程接口的两种实现方式n采用两种实现套接字编程接口的方式:采用两种实现套接字编程接口的方式:l在操作系统的内核中增加相应的软件来实现;l通过开发操作系统之外的函数库来实现。 n在在BSDUNIX及起源于它的操作
45、系统中,套接字函及起源于它的操作系统中,套接字函数是操作系统本身的功能调用,是操作系统内核数是操作系统本身的功能调用,是操作系统内核的一部分。的一部分。l其他操作系统供应商为了不修改基本操作系统,其他操作系统供应商为了不修改基本操作系统,开发了套接字库(开发了套接字库(SocketLibrary)来提供套接字)来提供套接字编程接口。编程接口。l套接字库中的每个过程具有与UNIX套接字函数相同的名字与参数,向没有本机套接字的操作系统上的应用程序提供套接字编程接口。60套接字编程的基本概念套接字编程的基本概念2.22.2.1 2.2.1 什么是套接字(什么是套接字(SOCKETSOCKET) 图2
46、.1 电插座与电话插座的作用n套接口是对网络中不同主机上应用进程之间进行套接口是对网络中不同主机上应用进程之间进行双向通信的端点的抽象。双向通信的端点的抽象。n一个套接口就是网络上进程通信的一端,提供了一个套接口就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制。应用层进程利用网络协议栈交换数据的机制。61图图2.2 2.2 应用进程、套接口、网络协议栈及操作系统的关系应用进程、套接口、网络协议栈及操作系统的关系 2.2.1 2.2.1 什么是套接字(什么是套接字(SOCKETSOCKET) l两个应用进程只要分别连接到自己的套接字,就可以通过网络进行通信了,不用去管复杂的
47、网络结构及数据传输过程。62n从多个层面来理解套接字这个概念的内涵从多个层面来理解套接字这个概念的内涵l从所处的地位来讲,套接字上连应用进程,下连套接字上连应用进程,下连网络协议栈,是应用程序通过网络协议栈进行通网络协议栈,是应用程序通过网络协议栈进行通信的接口,是应用程序与网络协议栈交互的接口信的接口,是应用程序与网络协议栈交互的接口.l从实现的角度来讲,非常复杂。套接字是一个复,非常复杂。套接字是一个复杂的软件机构,包含了一定的数据结构,包含许杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。多选项,由操作系统内核管理。l从使用的角度来讲,非常简单。对套接字的操作,非常
48、简单。对套接字的操作形成了一种网络应用程序的编程接口(形成了一种网络应用程序的编程接口(API),提提供了一组系统调用或库函数,可以用于供了一组系统调用或库函数,可以用于构造套接字、安装绑定套接字、连接套接字、通过套接字交换数据、关闭套接字,实现各种分布式应用。n套接字编程接口是一套操作套接字的编程接口函数,是一套操作套接字的编程接口函数,套接字是它的操作对象。套接字是它的操作对象。632.2.2套接字的特点1通信域n通信域:是一个计算机网络的范围,在这个范围中,所有的计算机使用同一种网络体系结构及协议栈。n套接字存在于通信域中。套接字存在于通信域中。l套接字通常只和同一域中的套接字交换数据。
49、套接字通常只和同一域中的套接字交换数据。l如果数据交换要穿越域的边界,就一定要执行如果数据交换要穿越域的边界,就一定要执行某种解释程序。某种解释程序。n这里,仅仅针对这里,仅仅针对Internet域,并且使用域,并且使用Internet协议族(即协议族(即TCP/IP协议族)来通信。协议族)来通信。642 2套接字的三种类型套接字的三种类型n每一个正被使用的套接字都有它确定的类型。只有相同类型的套接字才能相互通信。(1)数据报套接字(数据报套接字(DatagramSOCKET)n数据报套接字提供无连接的、不保证可靠的、独立的数据报传输服务。n在Internet通信域中,数据报套接字使用UDP数
50、据报协议形成的进程间通路,具有UDP协议为上层所提供的服务的所有特点。n具有多播通信的能力。65图图2.3 2.3 在在InternetInternet通信域中,数据报套接字基于通信域中,数据报套接字基于UDPUDP协议协议 (1 1)数据报套接字)数据报套接字66n流式套接字提供双向的、有序的、无重复的、无记录边界的可靠的数据流传输服务。n在Internet通信域中,流式套接字使用TCP协议形成的进程间通路,具有TCP协议为上层所提供的服务的所有特点。n在使用流式套接字传输数据之前,必须在数据的发送端和接收端之间建立连接。n一般用于交换大批量的数据,或者要求数据按照发送的顺序无重复的到达目的
51、地。(2) (2) 流式套接字(流式套接字(Stream SOCKETStream SOCKET)67图2.4 在Internet通信域中,流式套接字基于TCP协议(2) (2) 流式套接字(流式套接字(Stream SOCKETStream SOCKET)68(3) (3) 原始式套接字(原始式套接字(RAW SOCKETRAW SOCKET)n原始式套接字允许对较低层次的协议(如IP、ICMP)直接访问,用于检验新协议的实现。l原始套接字保存了数据包中的完整IP头;l前面两种套接字只能收到用户数据;l因此可以通过原始套接字对数据进行分析。 693套接字的创建套接字由应用层的通信进程创建,并
52、为其服务。每一个套接字都有一个相关的应用进程,操作该套接字的代码是该进程的组成部分。 4使用确定的IP地址和传输层端口号n在生成套接字的描述符后,要将套接字与计算机上的特定的IP地址和传输层端口号相关联,这个过程称为绑定。n一个套接口要使用一个确定的三元组网络地址信息,才能使它在网络中唯一地被标识。701表示套接字地址的三种结构n在套接字编程接口中,定义了三种结构型的数据类型,用来存储协议相关的网络地址,在套接字编程接口的函数调用中要用到它们。(1)通用套接字地址结构sockaddr针对各种通信域的套接字,用来表示地址的一般格式,然后要求每个协议族说明其协议地址如何具体使用这个一般格式的,在头
53、文件中定义。structsockaddruint8_t sa_len;/地址总长sa_familiy_tsa_family;/地址家族charsa_data14;/特定的协议地址2.2.4 2.2.4 套接字使用的数据类型及相关问题套接字使用的数据类型及相关问题71 套接字函数被定义为以指向某个通用套接字地址结构的一个指针作为参数,如bind函数的ANSI C函数原型所示: int bind(int, struct sockaddr *, socklen_t)(1)通用套接字地址结构sockaddr 要求对套接字函数的调用都必须将指向特定协议的套接字地址结构的指针进行类型强制转换,变成指向某个
54、通用套接字地址结构的指针,例如: struct sockaddr_in serv; bind(sockfd, (struct sockaddr *)&serv, sizeof(serv); 通用套接字地址结构的唯一用途就是对指向特定协议的套接字地址结构的指针执行类型强制转换。72(2 2)in_addrin_addr结构,专门用来存储结构,专门用来存储IPIP地址。地址。structin_addrin_addr_ts_addr;s_addr 是网络字节顺序的是网络字节顺序的32位位IPv4地址,地址,in_addr_t一般为一般为uint32_t,即,即32位无符号整数位无符号整数类型。类型。
55、73(3)IPV4套接字地址结构sockaddr_in 也称为“网际套接字地址结构”,存储套接字 相 关 的 网 络 地 址 信 息 , 定 义 在 头 文 件中。structsockaddr_inuint8_tsin_len;/地址长度(无符号的8位整数)sa_family_tsin_family;/地址家族,设定为AF_INETin_port_tsin_port;/端口号(uint16_t)structin_addrsin_addr;/IP 地址(uint32_t)charsin_zero8;/全为0,未用在支持长度字段的实现中,sa_family_t通常是8位的无符号整数类型,而在不支持
56、长度字段的实现中,sa_family_t通常是16位的无符号整数类型。74(4)套接字地址结构套接字地址结构的一般用法n首先,定义一个sockaddr_in的结构实例,并将它清零。structsockaddr_inclient;memset(&client,0,sizeof(structsockaddr_in);n然后,为这个结构赋值。client.sin_family=AF_INET;client.sin_port=htons(8080);client.sin_addr.s_addr=htonl(INADDR-ANY);n第三步,在函数调用中使用时,将这个结构强制转换为sockaddr类型。
57、accept(listenfd,(structsockaddr*)(&client),&addrlen);752 2本机字节顺序和网络字节顺序本机字节顺序和网络字节顺序小小端端字字节节序序(little-endian)little-endian):将将低低序序字字节节存存储储在起始地址;在起始地址;大大端端字字节节序序(little-endian)little-endian):将将高高序序字字节节存存储储在起始地址;在起始地址;本本机机字字节节顺顺序序(host host byte byte order)order): : 在在具具体体计计算算机中的多字节数据的存储顺序。机中的多字节数据的存储
58、顺序。网网络络字字节节顺顺序序(network (network byte byte order)order): : 多多字字节节数数据在网络协议报头中的存储顺序。据在网络协议报头中的存储顺序。 (1)基本概念762本机字节顺序和网络字节顺序n网络应用程序要在不同的计算机中运行,本机字节顺序是不同的,但网络字节顺序是一定的。n所以,在应用程序编程的时候,在把IP地址和端口号装入套接字时,应当把它们从本机字节顺序转换为网络字节顺序;相反,在本机输出时,应将它们从网络字节顺序转换为本机字节顺序。(1)基本概念77#includeuint16_thtons(uint16_thost16bitvalu
59、e);uint32_thtonl(uint32_thost16bitvalue);uint16_tntohs(uint16_tnet16bitvalue);uint32_tntohl(uint32_tnet32bitvalue);htons():将将短整数本机短整数本机字节字节顺序转换为网络字节顺序,用于端口号。顺序转换为网络字节顺序,用于端口号。htonl():将将长整数本机顺序转换为网络顺序,用于长整数本机顺序转换为网络顺序,用于IPIP地址。地址。ntohs():将将短整数网络字节顺序转换为本机字节顺序,用于端口号。短整数网络字节顺序转换为本机字节顺序,用于端口号。ntohl():长整数
60、网络字节顺序转化为本机字节顺序,用于长整数网络字节顺序转化为本机字节顺序,用于IPIP地址。地址。 (2)字节顺序的转换函数字节顺序的转换函数2 2本机字节顺序和网络字节顺序本机字节顺序和网络字节顺序n这四个函数将被转换的数值作为函数的参数,函数返回值是转换后的结果。783 3点分十进制的点分十进制的IPIP地址的转换地址的转换n在在因因特特网网中中,IPIP地地址址常常常常用用点点分分十十进进制制的的表表示示方方法法,但但在在套套接接字字中中,IPIP地地址址是是无无符符号号的的长长整整型型数数,套套接接字字编编程程接接口口设设置置了了两两个个函函数数,专专门门用用于于两两种种形式的形式的I
61、PIP地址的转换。地址的转换。79unsignedlonginet-addr(constchar*cp)n入口参数cp:点分十进制形式的IP地址。n返回值:网络字节顺序的IP地址,是无符号的长整数。char*inet_ntoa(structin_addrin)n入口参数in:包含长整型IP地址的in_addr结构变量,n返回值:指向点分十进制IP地址的字符串的指针。 (2)inet_ntoa函数 (1)inet-addr函数3 3点分十进制的点分十进制的IPIP地址的转换地址的转换804域名服务n通常,我们使用域名来标识站点,可以将文字型的主机域名直接转换成IP地址。structhostent
62、*gethostbyname(constchar*name);n入口参数:是站点的主机域名字符串;n返回值:是指向hostent结构的指针。lhostent结构包含主机名、主机别名数组、返回地址的类型(一般是AF-INET)、地址长度的字节数、已符合网络字节顺序的主机网络地址等。812.3.1 2.3.1 套接字的工作过程套接字的工作过程n面向连接的通信方式基于面向连接的通信方式基于TCP,必须借助流式套,必须借助流式套接字来编程,应用程序分为服务器端和客户机端,接字来编程,应用程序分为服务器端和客户机端,双方是不对称的,需要分别编程。双方是不对称的,需要分别编程。l双方首先要创建并安装套接字
63、,做好准备;l建立连接,采用三次握手的方式;l数据交换,称为客户机与服务器的会话期,会话的内容必须遵守一定的格式和顺序;l释放连接。n下面介绍面向连接的服务器和客户机的编程步骤下面介绍面向连接的服务器和客户机的编程步骤.2.3面向连接的套接字编程面向连接的套接字编程822.3.1 2.3.1 套接字的工作过程套接字的工作过程图2.5 面向连接的流式套接字编程的基本编程步骤 832.3.2UNIX套接字编程接口的系统调用套接字编程接口的系统调用1 1创建套接字创建套接字SOCKET( )SOCKET( )n创建一个套接字,并返回一个整型描述符:创建一个套接字,并返回一个整型描述符:intSOCK
64、ET(intProtofamily,intType,intProtocol);n入口参数:入口参数:lProtofamily:套接字使用的协议族,例如,AF_INET表示IPv4协议,AF_INET6表示IPv6协议,AF_LOCAL表示Unix域协议。ltype:套接字类型,SOCK_STREAM表示创建面向连接的流传输的流式套接字;SOCK_DGRAM表示创建无连接的面向消息的数据报套接字,SOCK_RAW表示创建原始套接字。lprotocol:套接字使用的传输层网络协议,IPPROTO_TCP表示使用TCP协议,IPPROTO_UDP表示使用UDP协议。在Internet通信域中,取值为
65、0,以选择所给定的Protofamily和type组合的系统默认值。84n返回值:返回值:l套接字创建成功,返回一个非负整数值,与文件描述符类似,称为套接字描述符(socketdescriptor),简称sockfd,指向被维护在操作系统内核里的socket数据结构。l套接字创建出错,返回1,表示出错类型的代码保存在全局变量Errno中。n举例:举例:intsockfd=SOCKET(AF_INET,SOCK_STREAM,0)n创建套接字时,为其分配了内存,并建立了相应的数据结构,用于指定连接的种类和使用的协议,同时还有关于连接队列操作的选项结构字段,创建时设置为默认值。1 1创建套接字创建
66、套接字SOCKET( )SOCKET( )852 2值值- -结果参数结果参数n当往一个套接字函数传递一个套接字地址结构时,该结当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,即传递的是指向该结构的一构总是以引用形式来传递,即传递的是指向该结构的一个指针。该结构的长度也作为一个参数来传递,不过传个指针。该结构的长度也作为一个参数来传递,不过传递方式取决于该结构的传递方向:是从进程到内核,还递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程。是从内核到进程。n(1 1)从进程到内核传递套接字地址结构的函数有)从进程到内核传递套接字地址结构的函数有3 3个个 :
67、bindbind、connectconnect和和sendto. sendto. 这些函数的一个参数是指这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的向某个套接字地址结构的指针,另一个参数是该结构的长度,例如:长度,例如: struct sockaddr_in serv; connect(sockfd,(struct sockaddr *)&serv,sizeof(serv);862 2值值- -结果参数结果参数图图2.6 2.6 从进程到内核传递套接字地址结构从进程到内核传递套接字地址结构既然指针和指针所指内容的大小都传递给了内核,既然指针和指针所指内容的大小都传递给
68、了内核,因此内核知道到底需要从进程复制多少数据进来。因此内核知道到底需要从进程复制多少数据进来。87n(2 2)从内核到进程传递套接字地址结构的函)从内核到进程传递套接字地址结构的函数有数有4 4个个 :acceptaccept、recvfromrecvfrom、getsocknamegetsockname和和getpeername. getpeername. 这些函数的一个参数是指向这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是指某个套接字地址结构的指针,另一个参数是指向表示该结构大小的整数变量的指针,例如:向表示该结构大小的整数变量的指针,例如:2 2值值- -结果参数结果
69、参数struct sockaddr_in cliaddr;len=sizeof(cliaddr);accept(sockfd,(struct sockaddr *)&cliaddr,&len);88n把套接字地址结构的大小这个参数从一个整数改为指向某把套接字地址结构的大小这个参数从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值大小是一个值(value)(value),它告诉内核该结构的大小,这样内,它告诉内核该结构的大小,这样内核在写结构时不至于越界;当函数返回时,结构大小又是核在写结构时不至于越界;当函数返回
70、时,结构大小又是一个结果一个结果(result),(result),它告诉进程内核在该结构中究竟存储了它告诉进程内核在该结构中究竟存储了多少信息,这种类型的参数称为多少信息,这种类型的参数称为值值- -结果参数。结果参数。2 2值值- -结果参数结果参数图图2.7 2.7 从内核到进程传递套接字地址结构从内核到进程传递套接字地址结构89n当使用当使用值值- -结果参数结果参数作为套接字地址结构的长度作为套接字地址结构的长度时,如果套接字地址结构是固定长度,那么从时,如果套接字地址结构是固定长度,那么从内核返回的值总是那个固定长度,例如内核返回的值总是那个固定长度,例如IPv4IPv4的的soc
71、kaddr_insockaddr_in长度是长度是1616, IPv6 IPv6的的sockaddr_in6sockaddr_in6长度是长度是2828,然而对于可变长度的套接字地址结,然而对于可变长度的套接字地址结构(例如构(例如UnixUnix域的域的sockaddr_un)sockaddr_un),返回值可能,返回值可能小于该结构的最大长度。小于该结构的最大长度。2 2值值- -结果参数结果参数903 3绑定套接字到指定的地址绑定套接字到指定的地址 BIND( )BIND( )nintBIND(intSockfd,structsockaddr*My_addr,intAddrlen);n入
72、口参数:入口参数:lSockfd:是由SOCKET()系统调用创建的套接字描述符,要将其绑定到指定的网络地址上;lMy_addr:指向sockaddr结构变量的指针,所指结构中保存着特定的网络地址,要将套接字绑定到这个网络地址上;lAddlen:是sockaddr结构的长度。91n返回值:返回值:l如果返回0,表示已经绑定成功。l如果返回1,表示有错,出错码在Errno中。n举例:举例:Structsockaddr_inmy_addr;if(BIND(sockfd,(structsockaddr*)&my_addr,sizeof(structsockaddr_in)0)报错,并退出n在服务器端
73、,用作监听客户机端连接请求的套接字一定要绑定,因为大多数服务器进程使用熟知端口,并且服务器有时有多块网卡,会有多个IP地址。n客户机一般不用绑定。3 3绑定套接字到指定的地址绑定套接字到指定的地址 BIND( )BIND( )92n服务器调用BIND,来说明它将用来接收通信的协议端口号,同时my_addr 结构中还包含记录IP地址的域,此时如果主机是多宿主机,则in_addr字段取值常量INADDR_ANY,允许服务器在该计算机的任何IP地址上使用一个指定的端口。3 3绑定套接字到指定的地址绑定套接字到指定的地址 BIND( )BIND( )例如:struct sockaddr_in serv
74、addr;servaddr.sin_addr.s_addr=htonl(INADDR_ANY);表2.1 给bind函数指定要绑定的IP地址和/或端口号产生的结果934 4启动监听启动监听Listen( )Listen( )nintLISTEN(intSockfd,intQueuesize);n入口参数:入口参数:lSockfd:监听套接字描述符,通过它来监听来自客户机端的连接请求。lQueuesize:等待连接队列的最大长度,最大可设为20,一般设为510。(操作系统为每个监听套接字建立一个用来等待连接的“先进先出”缓冲区队列,若缓冲区队列有空就接收一个客户机端请求。)n返回值:返回值:l函
75、数正确执行则返回0,出错则返回-1。n举例:举例:LISTEN(Sockfe,10);n本调用只适用于面向连接的流式套接字,且仅用于服务器端。944 4启动监听启动监听Listen( )Listen( ) 举例:举例:LISTEN(Sockfe, 3); 图图2.8 2.8 监听套接字使用缓冲区接纳多个客户机端的连接请求监听套接字使用缓冲区接纳多个客户机端的连接请求 955 5接受连接请求接受连接请求ACCEPT( )ACCEPT( )nintACCEPT(intSockfd,structsockaddr*Addr,int*addrlen);n入口参数:入口参数:lSockfd:监听套接字描述
76、符。lAddr:sockaddr结构变量的指针,也是一个出口参数,当调用执行完毕时,变量中放置的是所连接的客户机端的网络地址。lAddrlen:整型变量指针,也是一个出口参数,是值-结果参数,调用时初始设置为Addr所指的套接字地址结构的长度,不能为0或null,调用执行完毕时,该整数值即为内核存放在该套接字地址结构内的确切字节数。96n返回值:返回值:l如果执行正确,返回一个由内核自动生成的一个全新套接字描述符,这个套接字已经与客户机端建立了连接,它代表与所返回客户机端的TCP连接,称为已连接套接字(connectedsocket),用于此后与客户机端交换数据。l如果出错,返回1。n说明:说
77、明:l调用从监听套接字的等待队列中,取出第一个连接请求,创建一个新的套接字,称为已连接套接字,并通过已连接套接字向客户机端发送连接应答,从而与客户机端建立连接,系统为已连接套接字分配一个服务器端的自由端口号,这个套接字用于后续与客户机端交换数据。5 5接受连接请求接受连接请求ACCEPT( )ACCEPT( )97n本调用仅适用于面向连接的流式套接字,与LISTEN()配套使用,入口参数Addr能返回客户机端的网络地址.n本调用以阻塞的方式工作,若监听套接字的等待队列为空,则本调用就阻塞直到有连接请求的到来。n举例:int clientfd; / 定义已连接套接字描述符变量struct soc
78、kaddr_in clientaddr; / 定义用于返回客户机端地址的结构。int addrlen=sizeof(clientaddr); / 获得套接字地址结构长度。clientfd=ACCEPT(listenfd, (struct sockaddr* )(&clientaddr), &addrlen); / 接受客户机端连接请求 5 5接受连接请求接受连接请求ACCEPT( )ACCEPT( )6请求建立连接请求建立连接CONNECT()nintCONNECT(intSockfd,structsockaddr*Servaddr,intAddrlen);n入口参数:入口参数:lSockfd
79、:客户机端的请求套接字描述符;lServaddr:存放服务器端的网络地址;lAddrlen:Servaddr结构的长度。n返回值:返回值:l连接成功,返回0;l连接未成功,返回-1。98n说明:l仅用于客户机端,请求连接到服务器;仅用于客户机端,请求连接到服务器;l对于流式套接字,使用传输层对于流式套接字,使用传输层TCP协议,服务器若接受协议,服务器若接受连接请求,即把它放入监听套接字的缓冲区队列,并调连接请求,即把它放入监听套接字的缓冲区队列,并调用用ACCEPT()来接收处理;来接收处理;l对于数据报套接字,使用传输层对于数据报套接字,使用传输层UDP协议,并不建立连协议,并不建立连接,
80、而只是将套接字置为接,而只是将套接字置为connected状态,并记录服务器状态,并记录服务器的地址。当客户机向同一服务器传送多条信息时,使用的地址。当客户机向同一服务器传送多条信息时,使用CONNECT()在套接字中记录服务器的地址,允许客户机只指明()在套接字中记录服务器的地址,允许客户机只指明服务器地址一次,而不必在每条信息中都指明目的地址。服务器地址一次,而不必在每条信息中都指明目的地址。n举例:if(CONNECT(sockfd,(structsockaddr*)(&serv_addr),sizeof(serv_addr)0)报错,并退出6 6请求建立连接请求建立连接CONNECT(
81、 )CONNECT( )997读/写套接字READ( )和WRITE( )nintREAD(intsockfd,void*buffer,intlen)nintWRITE(intsockfd,void*buffer,intlen)n入口参数:lSockfd:要读/写的套接字描述符,在客户机端是请求套接字,在服务器端是已连接套接字;lBuffer:字符串变量的指针,指向内存中用来存放数据的读写缓冲区,读套接字时存放从套接字接收到的数据,写套接字的时候存放要发送的数据;llen:读写缓冲区的长度。n返回值:l对于read()返回实际读到的字符数。对于write() 返回实际发送出去的字节数。100n
82、说明:l套接字起源于UNIX,UNIX将套接字也统一使用read和write对I/O操作进行数据传输。l必须用于已连接套接字。l优点:普遍性,应用程序能够创建一个发往或接收自一个描述符的数据传输,而不必知道这个描述符对应的是一个文件还是一个套接字。l缺点:套接字库的实现可能对所有使用套接字的应用增加额外的I/O开销。7 7读读/ /写套接字写套接字READ( )READ( )和和WRITE( )WRITE( )1018 8向套接字发送向套接字发送SEND( )SEND( )和从套接字接收和从套接字接收RECV( )RECV( )intSEND(intsockfd,void*buf,intlen
83、,intflags);intRECV(intsockfd,void*buf,intlen,intflags);n入口参数:lsockfd:用来发送/接收数据的套接字描述符,可以是由SOCKET()创建的,也可以是ACCEPT()返回的;lbuf:用于发送/接收数据的缓冲区指针;llen:要发送的或接收到的字符数;lflags:执行本调用的方式,一般置为0.102n返回值:l处理错误,返回-1;l如果正确,对于发送返回实际发送出去的字节数;对于接收返回实际读入缓冲区的字节数。n说明:l和read、write一样,必须用于已连接的套接字,无需调用者指明目的地参数。8 8发送发送 SEND( ) S
84、END( ) 和接收和接收 RECV( )RECV( )1039 9关关闭套接字套接字CLOSE()CLOSE()intCLOSE(intsockfd);n入口参数:lsockfd:要关闭的套接字描述符。n返回值:l若成功为0,若出错则为-1;n说明:l如果是面向连接的,CLOSE在关闭套接字前先终止连接;l关闭一个套接字意味着立即终止对它的使用,描述符被释放,以防止应用程序发送或接收更多的数据。1041052.3.4 进程的阻塞问题和对策1什么是阻塞n阻塞:是指一个进程执行了一个函数或者系统调用,该函数由于某种原因不能立即完成,因而不能返回调用它的进程,导致进程受控于这个函数而处于等待的状态
85、,进程的这种状态称为阻塞。106图2.10 RECV()函数的两种执行方式 1 1什么是阻塞什么是阻塞1072 2能引起阻塞的套接字调用能引起阻塞的套接字调用n在Berkeley套接字网络编程接口的模型中,套接字的默认行为是阻塞的,具体地说,在一定情况下,有多个操作套接字的系统调用会引起进程阻塞。(1)ACCEPT()(2)READ()、RECV()和RECVFROM()(3)WRITE()、SEND()和SENDTO()(4)CONNECT()(5)SELECT()(6)CLOSE()108图2.11 采用阻塞工作模式的服务器不能很好地为多个客户机服务 3阻塞工作模式带来的问题n采用阻塞工作
86、模式的单进程服务器,不能很好地同时为多个客户机服务的。图2.11是一个例子。1094一种解决方案n利用UNIX操作系统的FORK()系统调用,编制多进程并发执行的服务器程序,可以创建子进程。n对每一个客户机端,用一个专门的进程为它服务,通过进程的并发执行,来实现对多个客户机的并发服务.n基本的编程框架是:父进程代码父进程代码If(pid=FORK()=0).子进程代码子进程代码.elseif(pid0)报错信息报错信息父进程代码父进程代码1102.4 2.4 无连接的套接字编程无连接的套接字编程2.4.1 2.4.1 无连接的套接字编程的两种模式无连接的套接字编程的两种模式 使使用用数数据据报
87、报套套接接字字开开发发网网络络应应用用程程序序,既既可可以以采采用用客户机客户机/ /服务器模式,也可以采用对等模式。服务器模式,也可以采用对等模式。1客户机/服务器模式 图2.12 C/S模式的数据报套接字的编程模型 111nC/S模式无连接套接字特点:l应用双方不是对等的:服务器要先启动,被动等待访问,要经过创建套接字、绑定套接字、发送/接收数据、关闭套接字4个阶段,将套接字绑定到众所周知的端口上;l客户机套接字使用动态分配的自由端口上,不需要进行绑定;l客户机主动请求服务,并在数据报中携带双方的地址;l服务器可以接受多个客户端的数据。1 1客户机客户机/ /服务器模式服务器模式 1122
88、对等模式 图图2.13 2.13 对等模式的数据报套接字的编程模型对等模式的数据报套接字的编程模型 113n对等模式无连接套接字特点:l应用双方是对等的:要经过4个阶段,创建套接字、绑定套接字、发送/接收数据、关闭套接字;l双方都必须确切知道对方的网络地址,并在各自的进程中将约定好的网络地址绑定到自己的套接字上;l每一次传递数据时,在sendto和recvfrom系统调用中必须包含对方的网络地址信息;l进程也会因为发送或接收数据而发生阻塞。2 2对等模式对等模式 1142.4.2 两个专用的系统调用1发送数据报SENDTO( )nintSENDTO(intsockfd,constvoid*ms
89、g,intlen,unsignedintflags,structsockaddr*to,inttolen);n入口参数:lSockfd:发送方的数据报套接字描述符,包含发送方的网络地址;lmsg:字符串指针,指向发送缓冲区;llen:发送缓冲区长度;lFlags:发送方式,一般为0;lto:指向sockaddr结构的指针,包含接收方的网络地址;ltolen:sockaddr的结构长度。n返回值:成功返回发送字节数,失败返回1.1152接收数据报 RECVFROM( )nintRECVFROM(intsockfd,void*buf,intlen,unsignedintflags,structso
90、ckaddr*from,int*fromlen)n入口参数:lSockfd:接收方的数据报套接字描述符,包含接收方的网络地址;lmsg:字符串指针,指向接收缓冲区;llen:接收缓冲区长度;lflags:接收方式,一般为0;lFrom:指向sockaddr结构的指针,是个出口参数,调用成功后,这个结构中返回了发送方的网络地址;lFromlen:出口参数,sockaddr的结构长度。n返回值:成功返回接收的字节数,失败返回1.116第第3章章 Windows环境的网络环境的网络编程编程WindowsSockets规范3.1WinSock1.1的库函数3.2网络应用程序的运行环境3.31173.1
91、WindowsSockets规范3.1.1概述概述nMicrosoft公司以BerkeleySockets规范为范例,定义了WindowsSocktes规范,简称Winsock规范。n这是Windows操作系统环境下的套接字网络应用程序编程接口(API)。n包含:lBerkeley Socket 风格的库函数;l针对Windows操作系统的扩展库函数。n可以充分利用Windows的消息驱动机制编程。nWinsock规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数和相关语义,让各个软件供应商共同遵守,做到Winsock兼容。1183.1.1概述概述图3.1网网络应用用进程
92、利用程利用Windock进行通信行通信1193.1.2WindowsSockets规范规范nWindowsSockets规范是一套开放的、支持多种协议的Windows下的网络编程接口。n从1991年到1995年,从1.0版发展到2.0.8版,已成为Windows网络编程的事实上的标准。1WindowsSockets1.1版本版本n在Winsock.h包含文件中,定义了WinSock1.1版本库库函数的函数的函数的函数的语语法法法法、相关的符号常量相关的符号常量相关的符号常量相关的符号常量和数据数据数据数据结结构构构构。n库函数的实现在Winsock.dll动态链接库文件中。120(1)WinS
93、ock1.1全面全面继承了承了BerkeleySockets规范。范。nWinsock1.1继承了BerkeleySockets规范的主要特征,一部分库函数与之在形式上保持一致,包括库函数的名称、参数格式、结构定义。n表3.1列出了Winsock1.1继承了BerkeleySockets规范的函数。其中,带*号的表明该函数在某些情况下可能会发生阻塞。121主要函数SOCKET()创建一个套接字,并返回套接字的标识符BIND()把套接字绑定到特定的网络地址上LISTEN()启动指定的套接字,监听到来的连接请求ACCEPT()*接收一个连接请求,并新建一个套接字,原来的套接字返回监听状态CONNE
94、CT()*请求将本地套接字连接到一个指定的远方套接字上SEND()*向一个已经与对方建立连接的套接字发送数据SENDTO()*向一个未与对方建立连接的套接字发送数据,并指定对方网络地址RECV()*从一个已经与对方建立连接的套接字接收数据RECVFROM()*从一个未与对方建立连接的套接字接收数据,并返回对方网络地址SHUTDOWN()有选择的关闭套接字的全双工连接CLOSESOCEKT()*关闭套接字,释放相应的资源表表3.1WinSock1.1继承承BerkeleySockets的函数的函数122辅助函数辅助函数HTONL()把32位无符号数从主机字节序转换为网络字节序HTONS()把16
95、位无符号数从主机字节序转换为网络字节序NTOHL()把32位无符号数从网络字节序转换为主机字节序NTOHS()把16位无符号数从网络字节序转换为主机字节序INET_ADDR()把标准的点分十进制的IP转换成长整形地址数据INET_NTOA()把长整形的IP地址数据转换成点分十进制的字符串GETPEERNAME()获得套接字连接上对方的网络地址GETSOCKENAME()获得指定套接字的网络地址控制函数控制函数GETSOCKOPT()获得指定套接字的属性选项SETSOCKOPT()设置与指定套接字相关的属性选项IOCTLSOCKET()为套接字提供控制SELECT()*执行同步I/O多路复用表表
96、3.1WinSock1.1继承承BerkeleySockets的函数的函数(续表表)123(2)数据库函数数据库函数n表3.2列出了Winsock规范定义的数据库查询函数。n其中6个采用getXbyY()的形式,大多要借助网络上的数据库来获得信息,而不采用本地数据库来实现。表表3.2数据数据库查询函数函数函数名函数名说明说明gethostname()用来返回本地计算机的标准主机名gethostbyname()*返回对应于给定主机名的主机信息gethostbyaddr()*根据一个IP地址取回相应的主机信息getservbyname()*返回对应于给定服务名和协议名的相关服务信息getservb
97、yport()*返回对应于给定端口号和协议名的相关服务信息getprotobyname()*返回对应于给定协议名的相关服务信息getprotobynumber()*返回对应于给定协议号的相关协议信息124Winsock的注册与注销函数的注册与注销函数WSAStartup()初始化底层WindowsSocksDLLWSACleanup()从底层的WindowsSocketsDLL撤销注册异步执行的数据库查询函数异步执行的数据库查询函数WSAAsyncGetHostBYName()GetHostBYName()的异步版本WSAAsyncGetHostBYAddr()GetHostBYAddr()的
98、异步版本WSAAsyncGetServByName()GetServByName()的异步版本WSAAsyncGetServByPort()GetServByPort()的异步版本WSAAsyncGetProtobyNameGetProtobyName()的异步版本WSAAsyncGetProtobyNumber()GetProtobyNumber()的异步版本表表3.3Winsock1.1的常用的常用扩展函数展函数125表表3.3Winsock1.1的常用的常用扩展函数展函数(续)异步选择机制的相关函数异步选择机制的相关函数WSAAsyncSelect()Select()的异步版本WSACa
99、ncelAsyncRequest()取消一个未完成的WSAAsyncGetXByY()函数的实例WSACancelBlockingCall()取消未完成的阻塞的API调用WSAIsBlocking()确定线程是否被一个调用阻塞错误处理相关函数错误处理相关函数WSAGetLastError()得到最近一个Winsock调用出错的详细信息WSASetLastError()设置下一次WSAGetLastError()返回的错误信息126(4)WinSock1.1只支持只支持TCP/IP协议栈协议栈nWinsock 1.1的实现,即Winsock.dll和底层协议栈的接口是唯一的,且是独占的,只能访问
100、TCP/IP协议栈。n因此, Winsock 1.1套接字仅支持单一的通信域,即Internet域。1272WinSock2.0规范规范nWinSock2.0在源码和二进制代码方面与WinSock1.1兼容,此外还增强了许多功能。(1)支持多种协议(2)引入了重叠I/O的概念(3)使用事件对象异步通知(4)服务的质量(QOS)(5)套接口组(6)扩展的字节顺序转换例程(7)分散/聚集方式I/O(8)新增了许多函数。1283.2Winsock1.1的库函数的库函数3.2.1Winsock的注册与注销1初始化函数WSAStartup()nWinsock应用程序要做的第一件事,就是必须首先调用WSA
101、Startup()函数对Winsock进行初始化初始化。n初始化也称为注注注注册册册册,注册成功后,才能调用其他的WinsockAPI函数。(1)WSAStartup()函数的调用格式intWSAStartup(WORDwVersionRequested,LPWSADATAlpWSAData);lwVersionRequested:应用程序要使用的winsock最高版本号;llpWSAData:指向WSADATA结构,返回WinsockAPI实现细节。129(2)WSAStartup()函数的初始化函数的初始化过程程图3.2在一台在一台计算机中,使用同一算机中,使用同一Winsock实现的多个
102、网的多个网络应用程序用程序130(5)初始化Winsock的示例#include/对于于Winsock2.0,应包括包括Winsock2.h文件文件aa()WORDwVersionRequested;/应用程序所需的用程序所需的Winsock版本号版本号WSADATAwsaData;/用来返回用来返回Winsock实现的的细节信息信息Interr;/出出错代代码。wVersionRequested=MAKEWORD(1,1);/生成版本号生成版本号1.1err=WSAStartup(wVersionRequested,&wsaData);/调用初始化函数用初始化函数if(err!=0)retu
103、rn;/通知用通知用户找不到合适的找不到合适的DLL文件文件/确确认返回的版本号是客返回的版本号是客户要求的要求的1.1if(LOBYTE(wsaData.wVersion)!=1|HYBYTE(wsaData.wVersion)!=1)WSACleanup();return;/*至此,可以确至此,可以确认初始化成功,初始化成功,Winsock.DLL可用。可用。*/1312注销函数WSACleanup()n程序使用完Winsock.DLL提供的服务后,应用程序必须调用WSACleanup()函数,来解除与Winsock.DLL库的绑定,释放Winsock实现分配给应用程序的系统资源,中止对W
104、indowsSocketsDLL的使用。(1)WSACleanup()函数的调用格式函数的调用格式intWSACleanup(void);l返 回 值 : 如 果 操 作 成 功 返 回 0, 否 则 返 回SOCKET_ERROR.132(2)WSACleanup()函数的功能函数的功能n对应于一个任务进行的每一次WSAStartup()调用,必须有一个WSACleanup()调用。n只有最后的WSACleanup()做实际的清除工作;前面的调用仅仅将WindowsSocketsDLL中的内置引用计数递减。n一个简单的应用程序为确保WSACleanup()调用了足够的次数,可以在一个循环中不
105、断调用WSACleanup()直至返WSANOTINITIALISED。(3)WSACleanup()函数可能返回的函数可能返回的错误代代码lWSANOTINITIALISED:在调用本API之前应成功调用WSAStartup()。lWSAENETDOWN:检测到网络子系统故障。lWSAEINPROGRESS:一个阻塞的WinSock调用正在进行中。1333.2.3主要的主要的Winsock函数函数1创建套接口SOCKET()SOCKETsocket(intaf,inttype,intprotocol);n返回值:创建成功,返回套接字描述符,否则返回INVALID_SOCKET。n举例:SOC
106、KETsockfd=SOCKET(AF_INET,SOCK_STREAM,0);/*创建一个流式套接字。SOCKETsockfd=SOCKET(AF_INET,SOCK_DGRAM,0);/*创建一个数据报套接字。1341创建套接口SOCKET()图3.3socket()函数函数请求建立一个特定求建立一个特定类型的型的socket,其特点与,其特点与协议相关,并返回相关,并返回某一个句柄某一个句柄1352将套接口将套接口绑定到指定的网定到指定的网络地址地址BIND()intbind(SOCKETs,conststructsockaddr*name,intnamelen);图3.4bind()函
107、数通函数通过指定本地的指定本地的IP地址和端口号来地址和端口号来为本地本地socket明确地命名明确地命名1362将套接口将套接口绑定到指定的网定到指定的网络地址地址BIND()n有有许多多函函数数都都需需要要套套接接字字的的地地址址信信息息,像像UNIX套套接接字字一一样,Winsock也也定定义了了三三种种关关于于地地址址的的结构构,经常常使使用。用。通通用用的的Winsock地地址址结构构,针对各各种种通通信信域域的的套套接接字字,存存储它它们的地址信息。的地址信息。structsockaddru_shortsa_family;/地址家族charsa_data14;/协议地址137专门针
108、对Internet通信域的通信域的Winsock地址地址结构构structsockaddr_inshortsin_family;/指定地址家族,一定是AF_INET.u_shortsin_port;/指定将要分配给套接字的传输层端口号,structin_addrsin_addr;/指定套接字的主机的IP地址charsin_zero8;/全置为0,是一个填充数。专用于存用于存储IP地址的地址的结构构Structin_addrUnionStructu_chars_b1,s_b2,s_b3,s_b4;S_un_b;Structu_shorts_w1,s_w2;S_un_w;U_longS_addr;
109、138n在在使使用用Internet域域的的套套接接字字时,这三三个个数数据据结构构的的一般用法是:一般用法是:l首先,定义一个Sockaddr_in的结构实例变量,并将它清零;l然后,为这个结构的各成员变量赋值;l第三步,在调用BIND( )绑定函数时,将指向这个结构的指针强制转换为 sockaddr*类型。 139n举例:SOCKETserSock;/定义了一个SOCKET类型的变量。sockaddr_inmy_addr;/定义一个Sockaddr_in型的结构实例变量。interr;/出错码。intslen=sizeof(sockaddr);/sockaddr结构的长度。serSock=
110、SOCKET(AF_INET,SOCK_DGRAM,0);/创建数据报套接字。memset(void*)&my_addr,0,slen);/将Sockaddr_in的结构实例变量清零。my_addr.sin_family=AF_INET;/指定通信域是Internet。my_addr.sin_port=htons(21);/指定端口,将端口号转换为网络字节顺序。140/*指定IP地址,将IP地址转换为网络字节顺序。*/my_addr.sin_addr.s_addr=htonl(INADDR-ANY);/*将套接字绑定到指定的网络地址,对&my_addr进行了强制类型转换。*/if (BIND(
111、serSock, (LPSOCKADDR )&my_addr, slen)=SOCKET_ERROR)/*调用WSAGetLastError()函数,获取最近一个操作的错误代码。*/err=WSAGetLastError();/*以下可以报错,进行错误处理。*/1413.启动服务器监听客户端的连接请求LISTEN()intlisten(SOCKETs,intbacklog);n仅适用于支持连接的SOCK_STREAM类型的套接口。申请进入的连接请求被确认,并排队等待被接受。n这个函数特别适用于同时有多个连接请求的服务器;如果当一个连接请求到来时,队列已满,那么客户将收到一个WSAENOBUFS
112、错误。1424.接收连接请求ACCEPT()SOCKETaccept(SOCKETs,structsockaddr*addr,int*addrlen);图3.5accept()函数从函数从刚连接的接的socket上接收上接收远端的端的IP地址和端口号地址和端口号1435请求求连接接CONNECT()intconnect(SOCKETs,structsockaddr*name,intnamelen);l举例structsockaddr_indaddr;memset(void*)&daddr,0,sizeof(daddr);daddr.sin_family=AF_INET;daddr.sin_po
113、rt=htons(8888);daddr.sin_addr.s_addr=inet_addr(133.197.22.4);connect(ClientSocket,(structsockaddr*)&daddr,sizeof(daddr);144图3.6connect()函数指定函数指定远程的程的IP地址和端口号地址和端口号,向服向服务器器socket发起网起网络通信通信5请求求连接接CONNECT( )145图3.7完成关完成关联的建立后,客的建立后,客户端和服端和服务器端就得知了器端就得知了对方方的的socket名称,两个名称,两个socket名称的名称的结合定合定义一个关一个关联146图
114、3.8协议栈使用本地和使用本地和远程程socket名称来完整地名称来完整地识别一一个个socket,每个数据包都携每个数据包都携带本地和本地和远程程socket名称名称1476向一个已向一个已连接的套接口接的套接口发送数据送数据SEND()intsend(SOCKETs,char*buf,intlen,intflags);图3.9Send()和和Recv()都是都是对本地套接字的操作本地套接字的操作图3.9说明了明了send和和recv的作用,套接字的作用,套接字缓冲区与冲区与应用用进程程缓冲区的关系,以及冲区的关系,以及协议栈所作的所作的传送。送。1486向一个已向一个已连接的套接口接的套接
115、口发送数据送数据SEND()图3.10同步套接字的同步套接字的Send()函数的函数的执行流程行流程1497从一个已从一个已连接套接口接收数据接套接口接收数据RECV()intrecv(SOCKETs,char*buf,intlen,intflags);图3.11Recv()是是对本地套接字的操作本地套接字的操作1508按按照照指指定定目目的的地地向向数数据据报套套接接字字发送送数数据据SENDTO()intsendto(SOCKETs,char*buf,intlen,intflags,structsockaddr*to,inttolen);9接接收收一一个个数数据据报并并保保存存源源地地址址
116、,从从数数据据报套套接接字字接收数据接收数据RECVFROM()intrecvfrom(SOCKETs,char*buf,intlen,intflags,structsockaddr*from,int*fromlen);15110关关闭套接字套接字CLOSESOCKET()intclosesocket(SOCKETs);l关闭一个套接字,释放套接口描述符s,以后对s的访问均以WSAENOTSOCK错误返回。l若本次为对套接字的最后一次访问,则相应的名字信息及数据队列都将被释放。lclosesocket()的语义受SO_LINGER与SO_DONTLINGER选项影响,对比如下:选项间隔关闭方式
117、等待关闭与否SO_DONTLINGER不关心优雅否SO_LINGER零强制否SO_LINGER非零 优雅是15211 禁禁 止止 在在 一一 个个 套套 接接 字字 上上 进 行行 数数 据据 的的 接接 收收 与与 发 送送 SHUTDOWN()intshutdown(SOCKETs,inthow);n用于任何类型的套接口禁止接收、禁止发送或禁止收发。l如果how参数为0,则该套接口上的后续接收操作将被禁止。l若how为1,则禁止后续发送操作,对于TCP,将发送FIN。l若how为2,则同时禁止收和发。n请注意shutdown()函数并不关闭套接字,且套接字所占有的资源将被一直保持到clos
118、esocket()调用。1533.2.4Winsock的的辅助函数助函数1Winsock中的字节顺序转换函数图3.12两种本机字两种本机字节顺序序小端字小端字节序序大端字大端字节序序154WinsockAPI特为此设置了四个函数。(1)htonl()l将主机的无符号长整型数本机顺序转换为网络字节顺序(HosttoNetworkLong),用于IP地址。u_longPASCALFARhtonl(u_longhostlong);lhostlong是主机字节顺序表达的32位数。htonl()返回一个网络字节顺序的值。(2)htons()l将主机的无符号短整型数转换成网络字节顺序(HosttoNetw
119、orkShort),用于端口号。u_shortPASCALFARhtons(u_shorthostshort);lhostshort:主机字节顺序表达的16位数。htons()返回一个网络字节顺序的值。1Winsock中的字中的字节顺序序转换函数函数155(3)ntohl()l将一个无符号长整型数从网络字节顺序转换为主机字节顺序。(NetworktoHostLong),用于IP地址。u_longPASCALFARntohl(u_longnetlong);lnetlong是一个以网络字节顺序表达的32位数,ntohl()返回一个以主机字节顺序表达的数。(4)ntohs()l将一个无符号短整型数从
120、网络字节顺序转换为主机字节顺序。(NetworktoHostSort),用于端口号u_shortPASCALFARntohs(u_shortnetshort);lnetshort是一个以网络字节顺序表达的16位数。ntohs()返回一个以主机字节顺序表达的数。1562获取与套接口相取与套接口相连的外地网的外地网络地址地址GETPEERNAME()intgetpeername(SOCKETs,structsockaddr*name,int*namelen);3获取一个套接口的本地网取一个套接口的本地网络地址地址GETSOCKNAME()intgetsockname(SOCKETs,structs
121、ockaddr*name,int*namelen);1574将将一一个个点点分分十十进制制形形式式的的IP地地址址转换成成一一个个长整整型数型数INET_ADDR()unsignedlonginet_addr(constchar*cp);5将将网网络地地址址转换成成点点分分十十进制制的的字字符符串串格格式式INET_NTOA()char*inet_ntoa(structin_addrin);158第第4章章 MFC WinSock类的类的编程编程CAsyncSocket类类4.1CSocket类类4.24.3CAsyncSocket类的应用实例类的应用实例CSocket类的编程模型类的编程模型
122、4.4 CAsyncSocket类,在很低的层次上对Windows Sockets API进行了封装。 它的成员函数和Windows Sockets API的函数调用直接对应。一个CAsyncSocket对象代表了一个Windows套接字。它是网络通信的端点,除了把套接字封装成C+的面向对象的形式供程序员使用以外,这个类唯一所增加的抽象就是将那些与套接字相关的Windows消息变为CAsyncSocket类的回调函数。159CSocket类 , 从 CAsyncSocket类 派 生 , 是 对Windows Sockets API的高级封装。CSocket类继承了CAsyncSocket类的
123、许多成员函数,用法一致。CSocket类的高级表现在三个方面:(1)CSocket结合Carchive类来使用套接字。(2)CSocket管理了通信的许多方面,如字节顺序问题和字符串转换问题。 (3)CSocket类为Windows消息的后台处理提供了阻塞的工作模式。1604.1 CAsyncSocket类类 CAsyncSocket类从Cobject类派生而来,如图4.1所示:图4.1 CAsyncSocket类的派生关系1614.1.1 使用使用CAsyncSocket类的一般步骤类的一般步骤网络应用程序一般采用客户/服务器模式,它们使用CAsyncSocket类编程的步骤有所不同,见下表
124、。序号服务器(Server)客户机(Client)1/构造一个套接字CAsyncSocketsockServ;/构造一个套接字CAsyncSocketsockClient;2/创建Socket句柄到指定端口sockServ.Create(nPort);/创建Socket句柄,参数默认sockClient.Create();3/启动监听,准备接受连接请求sockServ.Listen();4/请求连接到服务器sockClient.Connect(strAddr,nPort);162163序号服务器(Server)客户机(Client)5/构造一个新的空的套接字CAsyncSocketsockRe
125、cv;sockRecv.Accept(sockRecv);6/接收数据sockRecv.Receive(pBuf,nLen);/发送数据sockClient.Send(pBuf,nLen);7/发送数据sockRecv.Send(pBuf,nLen);/接收数据sockClient.Receive(pBuf,nLen);8/关闭套接字sockRecv.Close();/关闭套接字sockClient.Close();4.1.2 创建创建CAsyncSocket类对象类对象 CAsyncSocket类对象称为异步套接字对象。创建异步套接字对象一般分为两个步骤,首先构造一个CAsyncSocket
126、对象,再创建该对象的底层的SOCKET句柄。1创建空的异步套接字对象创建空的异步套接字对象 通过调用CAsyncSocket类的构造函数,创建一个新的空CAsyncSocket类套接字对象,构造函数不带参数。然后必须调用它的Create成员函数,来创建底层的套接字数据结构,并绑定它的地址。164有两种使用方法,会在不同的位置创建。(1)如:CAsyncSocket aa; aa.Create(。);(2)如: CAsyncSocket * Pa; Pa = new CAsyncSocket; Pa-Create(。);1652创建异步套接字对象的底层套接字句柄创建异步套接字对象的底层套接字句柄
127、 通过调用CAsyncSocket类的Create()成员函数,创建该对象的底层套接字句柄,决定套接字对象的具体特性。调用格式为:BOOL Create( UINT nSocketPort=0, Int nSocketType = SOCK_STREAM, Long Ievent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT |FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );166举例:创建一个使用27端口的流式异步套接字对象。CAsyncSocket* pSocket = new CAs
128、yncSocket;int nPort = 27;pSocket-Create( nPort, SOCK_STREAM );1674.1.3 关关于于CAsyncSocket类类可可以以接接受受并并处处理理的的消息事件消息事件1六种套接字相关的事件与通知消息六种套接字相关的事件与通知消息参数Ievent可以选用的六个符号常量是在winsock.h文件中定义的。#define FD_READ 0x01#define FD_WRITE 0x02#define FD_OOB 0x04#define FD_ACCEPT 0x08#define FD_CONNECT 0x10#define FD_CLO
129、SE 0x20168 它们代表MFC套接字对象可以接受并处理的六种网络事件,当事件发生时,套接字对象会收到相应的通知消息,并自动执行套接字对象响应的事件处理函数。(1)FD_READ事件通知:通知有数据可读。(2)FD_WRITE事件通知:通知可以写数据。(3)FD_ACCEPT事件通知:通知监听套接字有连接请求可以接受。(4)FD_CONNECT事件通知:通知请求连接的套接字,连接的要求已被处理。(5)FD_CLOSE事件通知:通知套接字已关闭。(6)FD_OOB事件通知:通知将有带外数据到达1694.1.4 客客户户机机端端套套接接字字对对象象请请求求连连接接到到服服务器端套接字对象务器端
130、套接字对象 在服务器端套接字对象已经进入监听状态之后,客户机应用程序可以调用CAsyncSocket类的Connect()成员函数,向服务器发出一个连接请求 。格式一: BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );格式二: BOOL Connect( const SOCKADDR* lpSockAddr, int nSockAddrLen );1704.1.5 服务器接受客户机的连接请求服务器接受客户机的连接请求 在服务器端,使用CAsyncSocket流式套接字对象,一般按照以下步骤来接收客户端套接字对象的连接请求。(1)服
131、务器应用程序必须首先创建一个CAsyncSocket流式套接字对象,并调用它的Create成员函数创建底层套接字句柄。这个套接字对象专门用来监听来自客户机的连接请求,所以称它为监听套接字对象。171(2)调用监听套接字对象的Listen成员函数,使监听套接字对象开始监听来自客户端的连接请求。此函数的调用格式是: BOOL Listen( int nConnectionBacklog = 5);当Listen函数确认并接纳了一个来自客户端的连接请求后,会触发FD_ACCEPT事件,监听套接字会收到通知,表示监听套接子已经接纳了一个客户的连接 请 求 , MFC框 架 会 自 动 调 用 监 听
132、套 接 字 的OnAccept事件处理函数,它的原型调用格式如下: virtual void OnAccept( int nErrorCode ); 编程者一般应重载此函数,在其中调用监听套接字对象的Accept函数,来接收客户端的连接请求。172(3)创建一个新的空的套接字对象,不需要使用它的Create函数来创建底层套接字句柄。这个套接字专门用来与客户端连接,并进行数据的传输。一般称它为连接套接字,并作为参数传递给下一步的Accept成员函数。(4)调用监听套接字对象的Accept成员函数,调用格式为:virtual BOOL Accept( CAsyncSocket& rConnSock
133、et,SOCKADDR* lpSockAddr = NULL,int* lpSockAddrLen = NULL );1734.1.6 发送与接收流式数据发送与接收流式数据 当服务器和客户机建立了连接以后,就可以在服务器端的连接套接字对象和客户机端的套接字对象之间传输数据了。对于流式套接字对象,使用CAsyncSocket类的Send成员函数向流式套接字发送数据,使用Receive成员函数从流式套接字接收数据。1741用用Send成员函数发送数据成员函数发送数据格式:virtual int Send( const void * lpBuf, int nBufLen, int nFlags =
134、0); 对于一个CAsyncSocket套接字对象, 当它的发送缓冲区为空时,会激发FD_WRITE事件,套接字会得到通知,MFC框架会自动调用这个套接字对象的OnSend事件处理函数。一般编程者会重载这个函数,在其中调用Send成员函数来发送数据。1752用用Receive成员函数接收数据成员函数接收数据格式: Virtual int Receive( Void* lpBuf, int nBufLen, Int nFlags = 0); 对于一个CAsyncSocket套接字对象,当有数据到达它的接收队列时,会激发FD_READ事件,套接字会得到已经有数据到达的通知,MFC框架会自动调用这个
135、套接字对象的OnReceive事件处理函数。一般编程者会重载这个函数,在其中调用Receive成员函数来接收数据。在应用程序将数据取走之前,套接字接收的数据将一直保留在套接字的缓冲区中。1764.1.7 关闭套接字关闭套接字1使用使用CAsyncSocket类的类的Close成员函数成员函数格式:virtual void Close( );2使使用用CAsyncSocket类类的的ShutDown()成成员函数员函数 使用CAsyncSocket类的ShutDown()成员函数,可以选择关闭套接字的方式。将套接字置为不能发送数据,或不能接收数据,或二者均不能的状态。格式:BOOL ShutDo
136、wn( int nHow = sends );1772发送和接收数据发送和接收数据 如果创建的是数据报类型的套接字,用SendTo()成员函数来向指定的地址发送数据,事先不需要建立发送端和接收端之间的连接,用ReceiveFrom()成员函数可以从某个指定的网络地址接收数据。178 发送数据SendTo的调用格式,有两种重载的形式,区别在于参数不同:int SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 );int SendTo( con
137、st void* lpBuf, int nBufLen, const SOCKADDR* lpSockAddr, int nSockAddrLen, int nFlags = 0 );179 接收数据ReceiveFrom的调用格式,也有两种重载的形式,区别在于参数不同:int ReceiveFrom( void* lpBuf, int nBufLen, Cstring & rSocketAddress, UINT& rSocketPort, int nFlags = 0 );int ReceiveFrom( void* lpBuf, int nBufLen, SOCKADDR* lpSock
138、Addr, int* lpSockAddrLen, int nFlags = 0 );1804.2 CSocket类类 CSocket类是从CAsyncSocket类派生而来的,它们的派生关系如图4.2:图4.2 CSocket类的派生关系1814.2.1 创建创建CSocket对象对象 分为两个步骤:(1)调用CSocket类的构造函数,创建一个空的CSocket对象。(2)调用此CSocket对象的Create()成员函数,创建对象的底层套接字。调用格式是:BOOL Create( UINT nSocketPort = 端口号, Int nSocketType = SOCK_STREAM
139、| SOCK_DGRAM, LPCTSTR lpszSocketAddress = 套接字所用的网络地址 ); 如果打算使用CArchive对象和套接字一起进行数据传输工作,必须使用流式套接字。1824.2.2 建立连接建立连接 CSocket类使用基类CAsyncSocket的同名成员函数Connect()、Listen()、Accept()来建立服务器和客户机套接字之间的连接,使用方法相同。不同的是:CSocket类的Connect()和Accept()支持阻塞调用。比如:在调用Connect()函数时会发生阻塞,直到成功地建立了连接或有错误发生才返回,在多线程的应用程序中,一个线程发生阻
140、塞,其他的线程仍能处理Windows事件。 CSocket对象从不调用OnConnect()事件处理函数。1834.2.3 发送和接收数据发送和接收数据 在创建CSocket类对象后,对于数据报套接字,直接使用CSocket类的SendTo()、ReceiveFrom()成员函数来发送和接收数据。对于流式套接字,首先在服务器和客户机之间建立连接,然后使用CSocket类的Send()、Receive()成员函数来发送和接收数据,它们的调用方式与CAsyncSocket类相同。不同的是:CSocket类的这些函数工作在阻塞的模式。比如,一旦调用了Send()函数,在所有的数据发送之前,程序或线程
141、将处于阻塞的状态。一般将CSocket类与CArchive类和CSocketFile类结合,来发送和接收数据,这将使编程更为简单。CSocket对象从不调用OnSend()事件处理函数。1844.2.4 CSocket类与类与CArchive类和类和CSocketFile类类 使用CSocket类的最大优点在于,应用程序可以在连接的两端通过CArchive对象来进行数据传输。具体做法是:(1)创建CSocket类对象(2)创建一个基于CSocketFile类的文件对象,并把它的指针传给上面的已创建的CSocket对象。(3)分别创建用于输入和输出的CArchive对象,并将它们与这个CSock
142、etFile文件对象连接。(4)利用CArchive对象来发送和接收数据。185下面是一段示例代码:CSocket exSocket;/ 创建一个空的CSocket对象CSocketFile* pExFile;/定义一个CSocketFile对象指针/ 定义一个用于输入的Carchive对象指针CArchive* pCArchiveIn; /定义一个用于输出的Carchive对象指针CArchive* pCArchiveOut;/创建Csocket对象的底层套接字exSocket.Create();186/创建CSocketFile对象,并将CSocket对象的指针传递给它pExFile =
143、new CSocketFile( & exSocket,TRUE);/创建用于输入的CArchive对象pCArchiveIn = new CArchive(pExFile, CArchive:load);/创建用于输出的CArchive对象。pCArchiveOut = new CArchive(pExFile, CArchive:store); 187图4.3 CSocket、CArchive和CSocketFile类在传输数据时的作用1884.2.5 关闭套接字和清除相关的对象关闭套接字和清除相关的对象 在使用完CSocket对象以后,应用程序应调用它的Close()成员函数来释放套接字
144、占用的系统资源,也可以调用它的ShutDown()成员函数来禁止套接字读写。而对于相应的CArchive对象、CSocketFile对象和CSocket对象,可以将它们销毁;也可以不作处理,因为当应用程序终止时,会自动调用这些对象的析构函数,从而释放这些对象占用的资源。1894.3 CSocket类的编程模型类的编程模型 下面给出针对流式套接字的CSocket类的编程模型。分为服务器端和客户机端。1服务器端服务器端(1)CSocket sockServ; /创建空的服务器端监听套接字对象(2)sockServ.Create( nPort ); /用众所周知端口,创建监听套接字对象的底层套接字句
145、柄(3)sockServ.Listen(); / 启动对于客户机端连接请求的监听(4) CSocket sockRecv; / 创建空的服务器端连接套接字对象sockServ.Accept( sockRecv); / 接收客户机端的连接请求,并将其他的任务转交给连接套接字对象190(5)CSocketFile* file ; /创建文件对象并关联到连接套接字对象 file = new CSockFile( &sockRecv); (6)CArchive* arIn, arOut; / 创建用于输入的归档对象 arIn = CArchive(&file, CArchive:load); / 创建
146、用于输出的归档对象 arOut = CArchive( &file, CArchive:store); 注:注:归档对象必须关联到文件对象。归档对象必须关联到文件对象。191(7)/进行数据的输入或输出,可以反复进行 arIn dwValue; / 进行数据输入 adOut dwValue; / 进行数据输入。 adOut dwValue; / 进行数据输出。(7)sockClient.Close(); / 传输完毕,关闭套接字对象。194195第第5章章 WinInet编程编程使用使用WinInet APIWinInet API的共性问题的共性问题5.1使用使用WinInet APIWinI
147、net API编制编制FTPFTP客户程序的要点客户程序的要点5.21965.1使用WinInet API的共性问题nWinInet是WindowsInternet扩展应用程序高级编程接口,是专为开发具有Internet功能的客户端应用程序而提供的。nWinInet有两种形式:lWinInet API包含一个C语言的函数集(Win32Internetfunctions);lMFCWinInet类层次则是对前者的面向对象的封装。nWinInet支持FTP、HTTP、Gopher协议。n使用WinInet可以使客户端应用程序轻松地与这三种服务器通信,而无需考虑底层通信细节。1975.1.1WinI
148、netAPI函数使用的函数使用的HINTERNET句柄句柄nHINTERNET句柄是一种特殊的数据类型,由少数WinInetAPI函数创建,大多数WinInetAPI函数通过使用HINTERNET类型的句柄来实现函数的操作。lHINTERNET句柄可以代表Internet会话;l可以代表应用程序与Internet上特定服务器的连接;l还可以代表各种打开的文件或查询结果。nHINTERNET句柄与普通的Win32句柄相似。n区别在于:处于不同层次的HINTERNET句柄形成了一个树形体系,且只有少数函数能够创建HINTERNET句柄。198图5.1各种各种HINTERNET句柄形成的句柄形成的树
149、形体系形体系结构构1995.1.2典型的操作流程和它们使用的句柄典型的操作流程和它们使用的句柄1.使用使用InternetOpenUrl直接打开因特网上指定的文件直接打开因特网上指定的文件图5.2依依赖由由InternetOpenUrl所所创建句柄的三个函数建句柄的三个函数2002FTP操作的操作的层级结构构(1)对FTP服服务器的目器的目录和文件和文件进行操作行操作图5.3对FTP服服务器的目器的目录和文件和文件进行操作的流程行操作的流程201(2)使用内存使用内存缓冲区来操作冲区来操作FTP服服务器上的文件器上的文件.图5.4使用内存使用内存缓冲区来操作冲区来操作FTP服服务器上的文件器上
150、的文件202(3)查询FTP服务器上的文件图5.5查询FTP服服务器上的文件器上的文件2035.2使用使用WinInetAPI编制编制FTP客户机程序的要点客户机程序的要点5.2.1一般步骤一般步骤nFTP客户端应用程序的一般步骤是:1.调 用 InternetAttemptConnect函 数 测 试 主 机 与Internet的连接状态;2.调用InternetOpen函数,创建HINTERNET会话根句柄;3.创建FTP会话句柄,调用函数时需要服务器名、FTP端 口 号 、 用 户 名 和 口 令 , 设 置INTERNET_SERVICE_FTP标志,若将端口号设置为HINTERNT_
151、INVALID_PORT_NUMBER,则使用默认端口号;4.对于FTP服务器执行需要的操作:4.对于于FTP服服务器器执行需要的操作:行需要的操作:搜寻并列举FTP服务器上的文件和目录l使用FtpFindFirstFile和InternetFindNextFile函数。查知或改变FTP服务器的当前目录l使用FtpGetCurrentDirectory和FtpSetCurrentDirectory函数。操作服务器上的目录l使用FtpCreateDirectory和FtpRemoveDirectory函数.下载FTP服务器中的文件l使用FtpOpenFile和InternetReadFile函数
152、下载和上传文件l使用FtpGetFile和FtpPutFile函数。2045.2.2搜寻并列举搜寻并列举FTP服务器上的文件和目录服务器上的文件和目录n使用FTP的主要目的是操作文件,经常要在FTP服务器上查找符合一定条件的目录或文件,称为目录列举。n符合条件的对象可能有很多,需要使用两个WinInet函数才能把所有的都搜索出来。l首先,调用FtpFindFirstFile函数,找到服务器上第 一 个 匹 配 的 文 件 或 目 录 , 并 返 回 一 个HINTERNET句柄。l在在此此基基础上上,可可以以使使用用这个个句句柄柄,反反复复调用用InternetFindNextFile函函数数
153、,搜搜寻到到其其它它的的匹匹配配文文件件或或目目录,直直到到返返回回ERROR_NO_MORE_FILES时,说明明所所有有匹匹配配的的对象都找到了。象都找到了。205nFtpFindFirstFile函数的原型:HINTERNETFtpFindFirstFile(INHINTERNEThFtpSession,/指定FTP会话句柄INLPCSTRlpszSearchFile,/指定要寻找的目录或文件路径OUTLPWIN32_FIND_DATAlpFindFileData,/返回的搜寻结果INDWORDdwFlags,/设置影响函数执行的标志INDWORDdwContext/环境值);n如 果
154、函 数 执 行 成 功 , 返 回 一 个 有 效 的 句 柄 , 用 于InternetFindNextFile,继续查询,否则返回NULL。n如果函数找不到匹配对象,则调用GetLastError函数返回一个ERROR_NO_MORE_FILES错误。206n在 一 个 FTP会 话 期 内 , 只 可 以 调 用 一 次 FtpFindFirstFile函数,此后应使用InternetFindNextFile函数列举出其余符合条件的目录和文件。n并将结果返回到WIN32_FIND_DATA结构中。nInternetFindNextFile函数比较简单,其原型是:BOOLInternetF
155、indNextFile(INHINTERNEThFind,/查找句柄OUTLPVOIDlpvFindData);/查找的结果n函数返回值是BOOL型,如果执行成功返回TRUE,否则返回NULL。n如果函数找不到匹配对象,则调用GetLastError函数返回一个ERROR_NO_MORE_FILES错误。2075.2.3查知或改变FTP服务器的当前目录n应用程序对于FTP服务器上的当前目录的控制称为目录导航。l调用FtpGetCurrentDiretory函数能够查知FTP服务器上的当前目录是哪一个;l调用FtpSetCurrentDiretory函数能够将FTP服务器上的当前目录改变到指定目
156、录。208nFtpGetCurrentDiretory函数的原型:BOOLFtpGetCurrentDirectory(INHINTERNEThFtpSession,/FTP会话句柄OUTLPSTRlpszCurrentDirectory,/返回当前目录的缓冲区指针INOUTLPDWORDlpdwCurrentDirectory/缓冲区的字符长度。);nFtpSetCurrentDirectory函数的原型:BOOLFtpSetCurrentDirectory(INHINTERNEThFtpSession,/有效的FTP会话句柄INLPCSTRlpszDirectory/要设置的新当前目录路径
157、);n返回值是BOOL型的,成功返回TRUE,失败返回FALSE.2095.2.4操作服务器上的目录n使用WinInetAPI函数可以在FTP服务器上创建和删除目录,当然应用程序应当具有相应的权限。n在调用InternetConnect时,指定具有相应权限的用户名和口令,正确地登录到FTP服务器。n调用FtpCreateDirectory可以在FTP服务器上创建新的目录,函数原型是:BOOLFtpCreateDirectory(INHINTERNEThFtpSession,/有效的FTP会话句柄INLPCSTRlpszDirectory/字符串指针,用于指定要创建的目录路径(名));210n调
158、用FtpRemoveDirectory可以删除FTP服务器上的指定的目录,函数原型是:BOOLFtpRemoveDirectory(INHINTERNEThFtpSession,/有效的FTP会话句柄INLPCSTRlpszDirectory/字符串指针,用于指定要删除的目录路径(名));n以上两函数的返回值是BOOL型,调用成功,返回TRUE,否则返回FALSE。n应用程序可以使用FtpGetCurrentDirectory函数来决定远方FTP站点的当前工作目录。2115.2.5下载下载FTP服务器中的文件服务器中的文件nWinInet为客户端应用程序提供了三种从FTP服务器上获取文件的方法
159、。(1)使用InternetOpenURL和InternetReadFile函数n如果用户确切的知道文件有效的URL,并且应用程序想要更紧密的控制下载的过程,同时在FTP服务器上不需要进行其它的操作,可以使用这种方法。n应用程序直接调用InternetOpenURL函数打开由URL指定的服务器文件,创建文件句柄,再调用InternetReadFile函数下载文件的内容,这种方法允许应用程序对下载有更强的控制,是通用的下载方法。212(2)使用FtpOpenFile和InternetReadFile函数n如果应用程序已经调用InternetConnect函数创建了一个到服务器的FTP会话句柄,可
160、以首先调用FtpOpenFile函数打开服务器上的现存文件,再调用InternetReadFile来下载文件,并保持与FTP服务器的连接,因此允许执行更多的其他命令。n在如下两种情况应使用这种下载方法:l应用程序需要从服务器获得一个文件,并把文件信息首先装入应用程序控制的内存缓冲区,而不是直接写到磁盘的一个文件中;l应用程序需要对文件的传送过程进行很好的控制,如下载过程中显示进度指示器。213nFtpOpenFile函数原型是:HINTERNETFtpOpenFile(INHINTERNEThFtpSession,/Ftp会话句柄.INLPCSTRlpszFileName,/字符串指针,指向要
161、访问的远程文件名INDWORDfdwAccess,/对文件做的操作,只读或写INDWORDdwFlags, /传送方法与缓存方法.INDWORDdwContext,/环境值.);n函数返回HINTERNET句柄,初始化对远地文件的访问,如 果 成 功 返 回 一 个 句 柄 , 供 InternetReadFile或InternetWriteFile使用,如果失败返回NULL。214n在使用FtpOpenFile函数打开了FTP服务器上的一个文件,并成功地返回了一个文件句柄之后,应用程序必须使用InternetReadFile函数下载文件的内容。nInternetReadFile函数的原型是:
162、BOOLInternetReadFile(INHINTERNEThFile,/FtpOpenFile返回的文件句柄.INLPCVOIDlpBuffer,/接收数据的内存缓冲区指针.INDWORDdwNumberOfByetesToRead,/要读的字节数.OUTLPDWORDlpdwNumberOfByetesRead,/实际读到的字节数.);n函数成功执行返回TRUE,否则返回FALSE。n如果返回值是TRUE,并且读到的字节数是0,说明传送已经完成,可调用InternetCloseHandle释放连接。215nlpBuffer缓冲区应足够大,下载一个服务器文件往往要多次调用Internet
163、ReadFile函数,直到把数据都读完。n每次调用时,函数会尽量多读数据,如果网络上的数据尚未到达,函数会等待,直到读够数据。n在一个FTP会话中,仅仅可以打开一个文件,只能同时存在一个文件句柄。n如果在未调用InternetCloseHandle关闭前面的文件句柄时,就继续调用FtpOpenFile,会产生ERROR_FTP_TRANSFER_IN_PROGRESS错误。216(3)使用FtpGetFile函数n如果应用程序并不需要紧密地控制下载,可以使用FtpGetFile函数,直接指定服务器上的远程文件名和下载后在本地存储的文件名,来获得文件。n函数同样要求已经用InternetConn
164、ect建立了到服务器的FTP会话连接。217n应用程序可以调用FtpGetFile函数将远程文件存为本地系统中的文件,此函数将文件从远程FTP服务器中读出,并以指定的文件名存在本地系统中。nFtpGetFile函数的原型是:BOOLFtpGetFile(INHINTERNEThFtpSession,/Ftp会话句柄.INLPCSTRlpszRemoteFile,/服务器上的远程文件名INLPCSTRlpszNewFile,/存在本地的文件名.INBOOLfFailIfExists,/如何处理同名文件.INDWORDdwLocalFlagsAndAttributes,/新创建的文件的属性.IND
165、WORDdwInternetFlags,/传送方法与缓存方法.INDWORDdwContext,/环境值.);n函数成功执行返回TRUE,否则返回FALSE。2185.2.6上传文件上传文件n上传文件是指在FTP服务器上放置一个文件。n在WinInet中有两种方法。(1)使用使用FtpOpenFile和和InternetWriteFilel如果应用程序要发送一些数据到FTP服务器,并且要把这些数据作为一个文件存在服务器上,而应用程序并没有一个本地文件包含这些数据,数据在内存缓冲区内,可以使用FtpOpenFile以写的方式打开文件,创建文件句柄,然后再调用InternetWriteFile将数
166、据发送到服务器的文件中。(2)使用使用FtpPutFilel如果本地文件已经存在,应用程序可以使用FtpPutFile函数将文件上传到FTP服务器219(1)使用使用FtpOpenFile和和InternetWriteFilenFtpOpenFile的用法与下载文件基本相同,区别在于必须以写的方式打开。n然后调用InternetWriteFile将本地内存缓冲区中的数据上传到服务器。n函数原型是:BOOLInternetWriteFile(INHINTERNEThFile,/FtpOpenFile返回的文件句柄.INLPCVOIDlpBuffer,/指向本地内存缓冲区的指针,存放要写的数据.I
167、NDWORDdwNumberOfByetesToWrite,/要写到文件中的字节数.OUTLPDWORDlpdwNumberOfByetesWriten,/实际被写的字节数.);220(2)使用使用FtpPutFile函数函数n使用FtpPutFile函数可以直接将文件上传到FTP服务器n函数原型是:BOOLFtpPutFile(INHINTERNEThFtpSession,/Ftp会话句柄.INLPCSTRlpszLocalFile,/要上传的本地文件名INLPCSTRlpszNewRemoteFile,/要在服务器上创建的新文件名.INDWORDdwFlags, /传送方法与缓存方法.IN
168、DWORDdwContext,/环境值.);nFtpPutFile是一个高级函数,处理了按照FTP从本地读取一个文件并把它存储在远方服务器上,形成一个文件的全部细节,返回TRUE成功,FALSE失败。2215.2.7删除Ftp服务器中的文件n使用FtpDeleteFile函数可以删除服务器中的文件。n应用程序应当具有相应的权限,在调用InternetConnect时,指定具有相应权限的用户名和口令,正确地登录服务器。n函数的原型是:BOOLFtpDeleteFile(INHINTERNEThFtpSession,/有效的FTP会话句柄,必须是InternetConnect函数返回的INLPCS
169、TRlpszFileName/要删除的FTP服务器上的文件路径,可以是绝对或相对路径);n函数删除一个存储在FTP服务器上的文件,成功返回TRUE,失败返回FALSE。2225.2.8重命名FTP服务器上的文件或目录n使用FtpRenameFile函数可以重命名FTP服务器上的文件和目录,函数的原型是:BOOLFtpRenameFile(INHINTERNEThFtpSession,/有效的FTP会话句柄INLPCSTRlpszExisting,/文件的原名路径INLPCSTRlpszNew/文件的新的文件名);n如果成功则返回TRUE,否则返回FALSE。223224第第6 6章章 Wins
170、ockWinsock的多线程编程的多线程编程WinSock为什么需要多线程编程为什么需要多线程编程6.1Win32操作系统下的多进程多线程机制操作系统下的多进程多线程机制6.2VC+ 6.0对多线程网络编程的支持对多线程网络编程的支持6.36.1.1 WinSock的两种输入输出模式的两种输入输出模式 n“阻塞”模式,又称为同步模式,执行I/O操作完成前会一直进行等待,不会将控制权交给程序,工作在“阻塞”模式的套接字称为阻塞套接字。l套接字默认为阻塞模式。l可以通过多线程技术进行处理。 n“非阻塞”模式,又称为异步模式,执行I/O操作时,Winsock函数会返回并交出控制权。工作在“非阻塞”模
171、式下的套接字称为非阻塞套接字。l使用 起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回WSAEWOULDBLOCK错误,但功能强大。225WinSock为什么需要多线程编程为什么需要多线程编程6.1226l在大多数情况下,非阻塞模式调用都会失败,返回一个WSAEWOULDBLOCK错误,表示操作的条件尚不具备,但又不允许等待完成请求的操作。l非阻塞模式下会频繁返回错误,应仔细检查返回代码;并且在不成功的情况下不应反复轮询.n“非阻塞”模式6.1.2 两种模式的优缺点及解决方法两种模式的优缺点及解决方法n“阻塞”与“非阻塞”模式各有其优点和缺点。n阻塞套接字的I/O操作工作情况比较确
172、定,无非是调用、等待、返回。大部分情况下,I/O操作都能成功地完成,不过就是花费了等待的时间l因而比较容易使用,容易编程;l但在应付诸如需要建立多个套接字连接来为多个客户服务的时候,或在数据的收发量不均匀的时候,或在输入输出的时间不确定的时候,却显得性能低下,甚至无能为力。 227n使用非阻塞套接字,需要编写更多的代码,因为必须恰当地把握调用I/O函数的时机,尽量减少无功而返的调用,还必须详加分析每个Winsock调用中收到的WSAEWOULDBLOCK错误,采取相应的对策。l这种I/O操作的随机性使得非阻塞套接字显得难于操作。n所以,我们必须采取一些适当的对策,克服这两种模式的缺点,让阻塞和
173、非阻塞套接字能够满足各种场合的要求。l对于非阻塞的套接字工作模式,进一步引入了五种“套接字I/O模型”。l对于阻塞的套接字工作模式,则进一步引入了多线程机制。 2286.2.1 Win32 OS是单用户多任务的操作系统是单用户多任务的操作系统n最早的DOS是单用户单任务的。n后来发展到图形界面的Windows,发展到Windows 95,Windows 98,就都支持多任务了。n从Windows NT起,Windows操作系统更是发展成了一个真正的抢占式多任务操作系统。l一个运行中的应用进程实例,就是一个进程。l一个基于Win32的应用程序可以包含一个或多个进程。Win32操作系统下的多进程多
174、线程机制操作系统下的多进程多线程机制6.22296.2.2 Win32 OS是支持多线程的操作系统是支持多线程的操作系统nWin32操作系统还支持同一进程的多线程。在一个Windows进程内,可以包含多个线程。n一个线程(thread)是进程内的一条执行路径,具体地说,是一个应用程序中的一条可执行路径,往往是应用程序中的一个或多个函数。n一个进程中至少要有一个线程,习惯将它称为主线程。n任何一个应用程序进程都有一个主线程。一般C程序中的Main或WinMain函数就规定了主线程的执行代码。2302316.2.2 Win32 OS是支持多线程的操作系统是支持多线程的操作系统n当你启动了一个应用程
175、序时,操作系统在为它创建了进程之后,也创建了该进程的主线程,并根据Main或WinMain函数的地址,开始执行该进程的主线程。n主线程可以创建并启动其他辅助线程。n由主线程创建的线程又可以创建并启动更多的线程。l线程的代码执行完毕时会自动终止,并将占用的资源释放给进程;l进程的所有线程都终止时,进程也就终止了,并会将占用的资源释放给操作系统。n一个线程需要占用一定的系统资源,一类是此线程专用的,另一类则是与进程的其他线程共享的。 n线程是进程中相对独立的执行单位,也是Win32操作系统中可调度的最小的执行单位。n多个进程中的多个线程并发地执行。n对于拥有多个处理机的计算机系统,调度程序可以将不
176、同的线程安排到不同的处理机上去运行,一方面平衡了CPU的负载,另一方面也提高了系统的运行效率。2326.2.2 Win32 OS是支持多线程的操作系统是支持多线程的操作系统 VC+ 6.0为程序员提供了Windows应用程序的集成开发环境,在这个环境下,有两种开发程序的方法。既可以直接使用Win32 API来编写C风格的Win32应用程序,也可以利用MFC基础类库编写C+风格的应用程序。 在这两种Windows应用程序的开发方式下,多线程的编程原理是一致的。233VC+ 6.0对多线程网络编程的支持对多线程网络编程的支持6.36.3.1 MFC支持的两种线程支持的两种线程 微软的基础类库MFC
177、提供了对于多线程应用程序的支持。在MFC中,线程分为两种, 一种是用户接口线程(user-interface thread),或称用户界面线程; 另一种是工作线程(the worker thread),这两类线程可以满足不同任务的处理需求。2341用户接口线程用户接口线程 用户接口线程通常用来处理用户输入产生的消息和事件,并独立地响应正在应用程序其它部分执行的线程产生的消息和事件,MFC特别地为用户接口线程提供了一个消息泵(a message pump)。用户接口线程包含一个消息处理的循环,以应对各种事件。 在MFC应用程序中,所有的线程都是由CWinThread对象来表示的。CWinThre
178、ad类(可以理解为C+的Windows 线程类)是用户接口线程的基类,CWinApp就是从CWinThread类派生出来的,我们在编写用户接口线程的时候,也需要从CWinThread类派生出自己的线程类,借助ClassWizard可以很容易地做这项工作。2352工作线程工作线程 工作线程(the worker thread),适用于处理那些不要求用户输入并且比较消耗时间的其他任务。对用户来说,工作线程运行在后台。这就使得工作线程特别适合去等待一个事件的发生。 CWinThread类同样是工作线程的基类,同样是由CWinThread对象来表示的。但在编写工作线程的时候,你甚至不必刻意地从CWin
179、Thread类派生出自己的线程类对象。你可以调用MFC框架的AfxBeginThread帮助函数,它会为你创建CWinThread对象。2366.3.2 创建创建MFC的工作线程的工作线程 下面介绍利用MFC创建工作线程所必需的步骤。 创建一个工作线程是一个相对简单的任务,只要经过两个步骤就能使你的工作线程运行:第一步是编程实现控制函数,第二步是创建并启动工作线程。一般不必从CWinThread派生一个类。当然,如果你需要一个特定版本的CWinThread类,也可以去派生;但对于大多数的工作线程是不要求的。你可以不作任何修改地使用CWinThread类。237238第第7 7章章 Winsoc
180、kWinsock的输入的输入/ /输出输出模型模型Select模型模型7.1WSAAsyncSelect异步选择模型异步选择模型7.2WSAEventSelect事件选择模型事件选择模型7.3重叠重叠I/O模型模型7.4完成端口模型完成端口模型7.5239nWinSock的I/O可以采用阻塞模式或非阻塞模式。n在非阻塞模式下,由于I/O操作的随机性,使非阻塞套接字难于操作,给编程带来困难。n为解决这个问题,对于非阻塞的套接字工作模式,进一步引入了五种“套接字I/O模型”,有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。 select(选择)模型是Winsock中最常
181、见的I/O模型。它的中心思想是利用select函数,实现对多个套接字I/O的管理。利用select函数,可以判断套接字上是否存在数据,或者能否向一个套接字写入数据。只有在条件满足时,才对套接字进行输入输出操作,从而避免无功而返的I/O函数调用,避免频繁产生WSAEWOULDBLOCK错误,使输入输出变得有序。Select模型模型7.12401select的函数的函数select的函数原型如下,其中fd_set数据类型,代表着一系列特定套接字的集合。int select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds,fd_set FAR
182、* exceptfds,const struct timeval FAR * timeout );241说明:select函数对readfds、writefds和exceptfds三个集合中指定的套接字进行检查,看是否有数据可读、可写或有带外数据,如果有至少一个套接字符合条件,就立即返回。符合条件的套接字仍在集合中,不符合条件的套接字则被删去。如果一个也没有,则等待。但最多等待timeout所指定的时间,便返回。2422操作套接字集合的宏操作套接字集合的宏 在应用程序中,用select对套接字进行管理之前,必须先将要检查的套接字句柄分配给某个集合,设置好相应的fd_set结构,再来调用sele
183、ct函数,便可知道一个套接字上是否正在发生上述的I/O活动。Winsock提供了下列宏操作,专门对fd_set数据类型进行操作(1)FD_CLR(s, *set):从set中删除套接字s。243(2)FD_ISSET(s, *set):检查s是否set集合的一名成员;如果是,则返回TRUE。(3)FD_SET(s, *set):将套接字s加入集合set。(4)FD_ZERO ( *set):将set初始化成空集合。其中,参数s是一个要检查的套接字,参数set是一个fd_set集合类型的指针。例如,调用select函数前,可使用FD_SET宏,将指定的套接字加入到fd_read集合中,selec
184、t函数完成后,可使用FD_ISSET宏,来检查该套接字是否仍在fd_read集合中。244 异步I/O模型通过调用WSAAsyncSelect函数实现。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。该模型最早出现于Winsock 1.1中,以适应其多任务消息环境。WSAAsyncSelect异步选择模型异步选择模型7.22451WSAAsyncSelect函数函数函数的定义是:int WSAAsyncSelect( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);246247事件类型事件类型含含义
185、义FD_READ应用程序想要接收有关是否有数据可读的通知,以便读入数据应用程序想要接收有关是否有数据可读的通知,以便读入数据FD_WRITE应用程序想要接收有关是否有可写的通知,以便发送数据应用程序想要接收有关是否有可写的通知,以便发送数据FD_OOB应用程序想要接收是否有应用程序想要接收是否有OOB数据抵达的通知数据抵达的通知FD_ACCEPT应用程序想要接收与进入的连接请求有关的通知应用程序想要接收与进入的连接请求有关的通知FD_CONNECT应用程序想要接收一次连接请求操作已经完成的通知应用程序想要接收一次连接请求操作已经完成的通知FD_CLOSE应用程序想要接收与套接字关闭有关的通知应
186、用程序想要接收与套接字关闭有关的通知表7.1用于WSAAsyncSelect函数的网络事件类型 WSAEventSelect事件选择模型和WSAAsyncSelect模型类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。表7.1中由WSAAsyncSelect模型采用的网络事件,均可原封不动地移植到事件选择模型中。也就是说,在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于,网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。下面介绍使用此模型的编程步骤。WSAEventSelect事件选择模型事件选择模型7.32481创建事件对象句
187、柄创建事件对象句柄 事件选择模型要求应用程序针对每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数,它的定义如下:WSAEVENT WSACreateEvent(void); 函数的返回值很简单,就是一个创建好的事件对象句柄。2492关联套接字和事件对象,注册关心的网络事件关联套接字和事件对象,注册关心的网络事件 有了事件对象句柄后,接下来必须将其与某个套接字关联在一起,同时注册感兴趣的网络事件类型(表7.1),这就需要调用WSAEventSelect函数,函数的定义为:int WSAEventSelect(SOCKET s,WSAEVENT hEventObj
188、ect,long lNetworkEvents);2503. 等待网络事件触发事件对象句柄的工作状态等待网络事件触发事件对象句柄的工作状态 将一个套接字同一个事件对象句柄关联在一起以后,应用程序便可以调用WSAWaitForMultipleEvents函数,等待网络事件触发事件对象句柄的工作状态。该函数用来等待一个或多个事件对象句柄,当其中一个或所有句柄进入“已传信”状态后,或在超过了一个规定的时间期限后,立即返回。该函数的定义:DWORD WSAWaitForMultipleEvents(DWORD cEvents,const WSAEVENT FAR * lphEvents,BOOL fW
189、aitAll,DWORD dwTimeout,BOOL fAlertable);2514检查套接字上所发生的网络事件类型检查套接字上所发生的网络事件类型 知道了造成网络事件的套接字后,接下来可调用WSAEnumNetworkEvents函数,检查套接字上发生了什么类型的网络事件。该函数定义如下:int WSAEnumNetworkEvents(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents);2525处理网络事件处理网络事件 在确定了套接字上发生的网络事件类型后,可以根据不同的情况做出相应的处理。完成了对WSA
190、NETWORKEVENTS结构中的事件的处理之后,应用程序应在所有可用的套接字上,继续等待更多的网络事件。 应用程序完成了对一个事件对象的处理后,便应调用WSACloseEvent函数,释放由事件句柄使用的系统资源。函数的定义如下:BOOL WSACloseEvent(WSAEVENT hEvent); 该函数也将一个事件句柄作为自己唯一的参数,并会在成功后返回TRUE,失败后返回FALSE。253 在Winsock中,重叠I/O(Overlapped I/O)模型能使应用程序达到更佳的性能。重叠模型的基本原理是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock的I/O请求。针
191、对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。自Winsock 2.0发布开始,重叠I/O便已集成到新的Winsock函数中,比如WSASend和WSARecv等。因此,重叠I/O模型适用于安装了Winsock 2.0的所有Windows平台。重叠重叠I/O模型模型7.42548.4.1重叠I/O(OverlappedI/O)模型的优点(1)可以运行在支持Winsock2的所有Windows平台。(2)使用重叠模型的应用程序通知缓冲区收发系统直接使用数据。能使应用程序性能更佳,优于阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型。(3)可以处
192、理数万SOCKET连接,且性能良好。2557.4.2重叠I/O模型的基本原理重叠模型的基本原理是让应用程序使用一个重叠的数据结构,一次投递一个或多个WinsockIO请求。当系统完成I/O操作后通知应用程序。系统向应用程序发送通知的形式有两种:事件通知,或者完成例程。由应用程序设置接收I/O操作完成的通知形式。2567.4.3重叠I/O模型的关键函数和数据结构1创建套接字SOCKETs=WSASocket(AF_INET,SOCK_STEAM,0,NULL,0,WSA_FLAG_OVERLAPPED);2WSAOVERLAPPED结构typedefstruct_WSAOVERLAPPEDDWO
193、RDInternal;DWORDInternalHigh;DWORDOffset;DWORDOffsetHigh;WSAEVENThEvent;/此参数用来关联WSAEvent对象WSAOVERLAPPED,*LPWSAOVERLAPPED;2573输入输出系列函数intWSARecv(SOCKETs,/用来接收数据的套接字LPWSABUFlpBuffers,/指向WSABUF结构数组的指针,接收缓冲区DWORDdwBufferCount,/数组中成员的数量LPDWORDlpNumberOfBytesRecvd,/如果接收操作立即完成,此参数返回所接收到数据的字节数。LPDWORDlpFlag
194、s,/标志位,设置为0即可LPWSAOVERLAPPEDlpOverlapped,/指向WSAOVERLAPPED结构指针,用来绑定重叠结构LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine/指向完成例程的指针,若选择事件通知的方式,应设置为NULL);2584WSAWaitForMultipleEvents函数DWORDWSAWaitForMultipleEvents(DWORDcEvents,/等候事件的总数量constWSAEVENT*lphEvents,/事件数组的指针BOOLfWaitAll,/如果设置为TRUE,则事件数组中所
195、有事件被传信时,函数才会返回,/如果设置为FALSE,则任何一个事件被传信时,函数就返回,一般设置为FALSE。DWORDdwTimeout,/超时时间,如果超时,函数会返回WSA_WAIT_TIMEOUT。/如果设置为0,函数会立即返回。/如果设置为WSA_INFINITE只有在某一个事件被传信后才会返回。BOOLfAlertable/在完成例程方式中使用,选择事件通知应设置为FALSE);2595WSAGetOverlappedResult函数BOOLWSAGetOverlappedResult(SOCKETs,LPWSAOVERLAPPEDlpOverlapped,LPDWORDlpcb
196、Transfer,BOOLfWait,LPDWORDlpdwFlags);2606调用WSAWaitForMultipleEvents函数,等待重叠操作返回的结果7使用WSAResetEvent函数重设当前这个用完的事件对象8使用WSAGetOverlappedResult函数取得重叠调用的返回状态9使用接收到的数据10回到第5步,在套接字上继续投递WSARecv请求,重复步骤692617.5 完成完成端口模型端口模型 完成端口I/O模型(I/Ocompletionport,IOCP)是最复杂的一种I/O模型。当应用程序需要管理为数众多的套接字时,完成端口模型提供了最佳的系统性能。这个模型也提
197、供了最好的伸缩性,它非常适合用来处理成百上千个套接字。2627.5.1 什么是完成什么是完成端口模型端口模型完成端口I/O模型是应用程序使用线程池处理异步I/O请求的一种机制。首先创建一个Win32完成端口对象,再创建一定数量的工作线程,应用程序发出一些异步I/O请求,当这些请求完成时,系统将把这些工作项目排序到完成端口,这样,在完成端口上等待的线程池便可以处理这些完成的I/O,为已经完成的重叠I/O请求提供服务。263264第第8 8章章 HTTPHTTP协议及高级编程协议及高级编程HTTPHTTP协议协议8.1利用利用CHtmlView类创建类创建Web浏览器型的应用程序浏览器型的应用程序
198、8.2Web浏览器应用程序实例浏览器应用程序实例8.3 HTTP是超文本传输协议(Hypertext Transfer Protocol)的简称,HTTP协议也是基于TCP/IP的客户机/服务器协议。 1990年,在万维网应用的开发中,为了解决HTML文档在网上的传输问题,诞生了HTTP协议。至今已有了三个版本,HTTP0.9,HTTP1.0以及HTTP1.1。为了适应下一代的因特网,还出现了HTTPng。事实证明,HTTP比以前的任何一种协议都简单有效,能将信息很好地组织起来,让人们方便地、直接地从Internet上检索和获取所需的信息。HTTPHTTP协议协议8.12658.1.1 HTT
199、P的基本概念的基本概念 RFC2068是HTTP1.1的最新的详细描述,本节介绍HTTP的基本概念。图图8.1 HTTP8.1 HTTP的会话过程的会话过程 266 HTTP的会话周期由连接、请求、响应和断开4个阶段组成:(1)建立TCP/IP连接(TCP/IP connection)(2)Web客 户 机 向 服 务 器 发 送 HTTP请 求(HTTP request)(3)服务器向客户机回送HTTP响应(HTTP response)(4)断开TCP/IP连接(disconnection)267 HTTP协议就是规定了Web客户和服务器之间的信息交换规程,以及HTTP请求和HTTP响应消息
200、的内容和格式。 HTTP协议应在Web浏览器和Web服务器中实现。换句话说,Web浏览器和Web服务器应按照HTTP协议交换信息。2688.1.2 8.1.2 HTTPHTTP消息的一般格式消息的一般格式图8.2 HTTP消息报文的一般格式2698.2.1 CHtmlView类与类与WebBrowser控件控件 CHtmlView类在afxhtml.h包含文件中定义,是从CView派生的,如图8.3所示。在标准的MFC框架应用程序中,无论是基于SDI或MDI的,所有从CView派生的类,都提供了由CView提供的功能。270利用利用CHtmlView类创建类创建Web浏览器型的应用程序浏览器型
201、的应用程序8.2 CHtmlView类的主要功能是访问Web网站和HTML文档。这是由于CHtmlView类在MFC的文档/视图结构(MFCs document/view architecture)环境中,进一步提供了WebBrowser控件的功能,可以说CHtmlView类是对WebBrowser控件的封装。 WebBrowser控件支持通过超链接和统一资源定位器URL导航的Web浏览。CHtmlView提供的WebBrowser 使得应用程序成了一个Web浏览器(web browser)。 要创建一个Web浏览器型的应用程序,可以使用CHtmlView类。通过MFC应用程序向导实现。271
202、第第9 9章章 电子邮件电子邮件协议与协议与编程编程电子邮件系统的工作原理电子邮件系统的工作原理9.1简单邮件传送协议简单邮件传送协议SMTP9.2电子邮件信件结构详述电子邮件信件结构详述9.3MIME编码解码与发送附件编码解码与发送附件9.4POP3与接收电子邮件与接收电子邮件9.52729.1.1 电子邮件的特点电子邮件的特点 电子邮件(electronic mail,简称e-mail)是因特网上使用最多的一种应用,它为用户在因特网上设立了存放邮件的电子邮箱,发信人可以随时将电子邮件发送到收信人的电子邮箱,收信人也可以随时上网读取,发信人与收信人以异步的方式通信。电子邮件系统的工作原理电子
203、邮件系统的工作原理9.12732741传统的电子邮件系统传统的电子邮件系统在电子邮件应用中,发送方通过邮件客户程序邮件客户程序将编辑好的信件向邮件服务器(ISP主机)发送,如图9.1所示。图图9.1 传统的电子邮件系统的收发过程传统的电子邮件系统的收发过程 9.1.2 电子邮件系统的分类电子邮件系统的分类2752基于基于Web的电子邮件系统的电子邮件系统基于Web的电子邮件系统如图9.2所示。图图9.2 基于基于Web的电子邮件系统的电子邮件系统 276图图9.3 电子邮件的发送与接收过程电子邮件的发送与接收过程 9.1.3 电子邮件系统的实现电子邮件系统的实现 9.2.1 概述概述 简 单
204、邮 件 传 送 协 议 SMTP( Simple Mail Transfer Protocol)是因特网的正式标准,最初在1982年 由 RFC821规 定 , 目 前 它 的 最 高 版 本 是RFC2821。 SMTP协议采用C/S模式,专用于电子邮件的发送,规定了发信人把邮件发送到收信人的电子邮箱的全过程中,SMTP客户机与SMTP服务器这两个相互通信的进程之间应如何交换信息。即规定了SMTP的会话过程。用户直接使用的是用于编写和发送的客户机端软件,而通常的SMTP服务器运行在远程站点上。客户机/服务器之间的通信是通过TCP/IP协议进行的。简单邮件传送协议简单邮件传送协议SMTP9.2
205、2779.2.2 SMTP客户与客户与SMTP服务器之间的会话服务器之间的会话1SMTP会话会话 如图9.4,说明了SMTP客户机与SMTP服务器之间的会话。图图9.4 SMTP客户机与客户机与SMTP服务器之间的会话服务器之间的会话 2789.2.6 使使用用Winsock来来实实现现电电子子邮邮件件客客户户机机与与服服务务器的会话器的会话 (1)启动SMTP服务器,在指定的传输层端口监听客户机端的连接请求,为SMTP服务器保留的端口是25 (2)客户机端设置Winsock连接的IP地址或域名,指定端口号,主动发出连接请求,连接到SMTP服务器。比如,网易的SMTP服务器的域名是,监听端口是
206、25。 (3)服务器接收客户机端的连接请求,并发回响应。客户机端应收到类似220 BigFox ESMTP service ready这样的信息, 这就说明客户机端已经与服务器建立TCP/IP连接,成功地实现了第一步。279(4)客户机端和服务器分别向对方发送数据。(5)客户机端或服务器分别读取自己缓冲区中的数据(6)以上两步是SMTP会话的主要部分,要按照SMTP协议的规定,按照一定顺序,客户机向服务器发送命令,服务器向客户机发送应答,以上两步要多次重复。 (7)会话完毕,关闭客户机端和服务器之间的连接。2809.3.1 Internet文本信件的格式标准文本信件的格式标准- RFC822
207、在电子邮件系统的环境中,电子邮件信件是它传递的对象。最早规定电子邮件信件内容结构的标准是在1982发表的,称作RFC822,至今它仍然是Internet上电子邮件信件的当前标准。RFC822定义了信件从主机传递到主机时需要的格式化方式。它的主要用途是为信件提供规范化的格式,使不同类型的网络可以相互传递电子邮件。该标准的最新文本是RFC2822。电子邮件信件结构详述电子邮件信件结构详述9.3281 RFC822规定,电子邮件信件的内容全部由ASCII字符组成,就是通常所说的文本文件, 从组织上看,RFC822将信件内容结构分为信头和信体两大部分,中间用一个空白行。 对于一行的字符数,有一个100
208、0/80的限制规则。 对于信件的行数,RFC822没有特别的限制。282 为了能利用电子邮件传送各种信息,在RFC1341中提出了一种方法,并在RFC2045至RFC2049中作了进一步的完善,这就是多用途Internet邮件扩展(Multipurpose Internet Mail Extensions),简称MIME,已经成为电子邮件的标准。按照MIME标准构造的邮件称为MIME邮件,或MIME信件,有时也称为MIME实体(MIME entity)。 MIME的基本思想是:第一,不改动SMTP和POP3等电子邮件传输协议;第二,仍然要继续使用RFC822的格式来传输邮件。MIME编码解码与
209、发送附件编码解码与发送附件9.4283图图9.5 MIME与电子邮件协议之间的关系与电子邮件协议之间的关系 284 MIME主要包括三部分内容:(1)扩展了可以在邮件中使用的信头字段。这些新定义的信头字段说明了MIME的版本,邮件内容的类型,编码方式,以及邮件的标识和描述等信息。(2)定义了邮件信体的格式,给出了多媒体电子邮件的标准化表示方法,为信体增加了结构。而在RFC822中,对邮件信体没有作任何结构方面的规定。(3)定义了传送编码方法,可以将任何格式的内容转换为符合RFC822的ASCII文本格式。 按照MIME规范,可以构造复杂的邮件,发送附件就是利用MIME实现的。285表表表表9.
210、1 9.1 RFC2046 RFC2046中定义的邮件内容类型和子类型中定义的邮件内容类型和子类型中定义的邮件内容类型和子类型中定义的邮件内容类型和子类型主类型标识符子类型标识符说明Textplain不包含格式化信息的无格式化文本enrich包含简单格式化标记的文本ImagegifGIF格式的图像jpegJPEG格式的图像Audiobasic用PCM(脉码调制)获得的音频数据VideompegMPEG格式的影片Applicationoctet-stream不加解释的不间断的字节序列postscriptPostScript格式的可打印文档Messagerfc822MIMERFC822邮件part
211、ial为了传输而讲一个邮件分成几个external-body必须从其它地方获取邮件的内容Multipartmixed按照特定顺序的几个独立不发alternative不同格式的同一邮件parallel必须同时读取的几个不发digest每一个不发是一个完整的RFC822邮件2869.5.1 POP3协议协议 用来接收电子邮件的POP邮局协议最初是在1984年发表的RFC918中定义的,1985年的RFC937发表了它的第二个版本。随着POP协议的广泛使用,1988年的RFC1081又发表了它的第三版本,简称POP3,这个版本又在RFC1225,RFC1460,RFC1725,RFC1939中几经修
212、订,其中RFC1939是当前的POP3标准。POP3与接收电子邮件与接收电子邮件9.52879.5.2 POP3的会话过程的会话过程 图图9.6 POP3会话会话 288 POP3也使用客户机/服务器工作模式,在接收邮件的用户的PC机中,运行POP3客户机程序,在用户所连接的ISP的邮件服务器中,运行POP3服务程序,二者之间按照POP3协议相互发送信息,POP3客户机发送给POP3服务器的消息称为POP3命令,POP3服务器返回的消息称为POP响应。交互的过程称为POP3会话。例如:(Connect to the POP3 Server .) /首先连接到POP3服务器S: +OK POP3
213、 server ready /服务器已经准备好C: USER Wang /用户名是Wang2899.5.3 POP3会话的三个状态会话的三个状态 POP3会话一共有3个状态:验证状态,事务状态和更新状态。每个状态都是会话过程中的特定阶段。 当连接服务器后,POP3会话首先进入验证状态,在这个阶段里,可以使用USER Pass Quit这三个POP3命令,客户机端送交用户名和口令,服务器验证是否合法。290 通过服务器验证后,服务器锁定该用户的信箱,从而防止多个POP3客户机端同时对此邮箱进行邮件操作,比如删除,取信等。但是可以让新的邮件加入。这时会话过程转变为事务状态,在事务状态客户机端可用的
214、POP3命令有:Noop Stat Quit List Retr Top Dele Rset Uidl。使用这些命令进行各种邮件操作,POP对话的大部分时间都处在事务状态中。291 当客户机发出Quit命令后,结束事务状态,POP3会话过程进入更新状态。在事务状态进行的一些操作,最终在更新状态中才得以体现。比如在事务状态使用Dele命令删除邮件,实际服务器并没有将邮件删除,只是做了一个删除标志;到了会话过程的更新状态,邮件才被删除。更新状态只是会话中的一个过程,该状态没有可使用的命令,目的是用户在事务状态后用以确认已经进行的操作。在进入该状态后,紧接着就完成了POP3的会话过程,断开了与服务器的连接。要注意,由于异常原因导致的与服务器终止对话并没有进入更新状态。在事务状态删除的邮件没有被删除,下次进入信箱时邮件还是存在的。292