《网络编程实用教程第2章.ppt》由会员分享,可在线阅读,更多相关《网络编程实用教程第2章.ppt(92页珍藏版)》请在三一办公上搜索。
1、第2章 UNIX中的套接字网络编程接口,UNIX套接字网络编程接口的产生与发展过程套接字与UNIX操作系统的关系套接字编程的基本概念套接字的特点、应用场合、使用的数据类型面向连接的套接字编程套接字的工作过程、系统调用、编程实例借助实例分析进程的阻塞问题和对策无连接的套接字编程无连接套接字编程的两种模式(C/S和对等)数据报套接字的对等模式编程实例,2.1 UNIX套接字网络编程接口的产生与发展,2.1.1 问题的提出应用程序与协议软件进行交互时须说明许多细节:是服务器还是客户机,主动还是被动通信?发送方需说明发送的数据;接收方需说明接收的数据如何存放。站在应用程序实现的角度,应用程序如何方便地
2、使用协议栈软件进行通信呢?如果能在应用程序与协议栈软件之间提供一个软件接口,就可以方便客户与服务器软件的编程。UNIX最早将TCP/IP协议簇集成到内核中,UNIX的开发者提出并实现了套接字应用编程接口。,套接字应用程序编程接口:是网络应用程序通过网络协议栈进行通信时所使用的接口,即应用程序与协议栈软件之间的接口,简称套接字编程接口(Socket API)。定义了应用程序与协议栈软件进行交互时可以使用的一组操作,决定了应用程序使用协议栈的方式、应用程序所能实现的功能、以及开发具有这些功能的程序的难度。套接字编程接口给出了应用程序能够调用的一组过程,以及这些过程所需的参数,每个独立的过程完成一个
3、与协议栈软件交互的基本操作(如:建立连接、接收数据、释放链接)。,2.1.2 套接字编程接口的起源与应用加州大学伯克利分校开发了一个包括TCP/IP协议簇的BSD UNIX,并迅速得到推广,套接字编程接口是这个操作系统的一部分。TCP/IP标准并没有定义应用程序用来与该协议进行交互的应用程序编程接口,只规定了应该提供的一般操作,并允许各个操作系统去定义用来实现这些操作的具体API。一个协议标准可能只是建议某个操作在应用程序发送数据时是需要的,而由应用程序编程接口来定义具体的函数名和每个参数的类型。,尽管协议标准允许操作系统设计者开发自己的应用程序编程接口,但由于BSD UNIX的广泛使用,后来
4、的许多操作系统及编程语言都选择了对套接字编程接口的支持。由于这个套接字规范最早是由Berkeley大学开发的,一般将它称为Berkeley Sockets规范。Berkeley Sockets规范规定了一系列与套接字使用有关的库函数,为在UNIX操作系统下不同计算机中的应用程序进程之间,使用TCP/IP协议簇进行网络通信提供了一套应用程序编程接口。,2.1.3 套接字编程接口的两种实现方式采用两种实现套接字编程接口的方式:在操作系统的内核中增加相应的软件来实现;通过开发操作系统之外的函数库来实现。在BSD UNIX及起源于它的操作系统中,套接字函数是操作系统本身的功能调用,是操作系统内核的一部
5、分。其他操作系统供应商为了不修改基本操作系统,开发了套接字库(Socket Library)来提供套接字编程接口。套接字库中的每个过程具有与UNIX套接字函数相同的名字与参数,向没有本机套接字的操作系统上的应用程序提供套接字编程接口。,套接字库与操作系统内核中实现的套接字在语义上是相同的。应用程序调用套接字过程不必管它是由操作系统内核过程提供的,还是由库过程提供的。这种方式提供了程序的可移植性:将程序从一台计算机移植到另一台计算机时,程序的源代码不需改动,只要用新计算机上的套接字库重新编译即可。套接字库与操作系统直接提供的本机套接字编程接口在实现上是不同的。套接字库的过程需要链接到应用程序中;
6、并驻留于应用程序地址空间;调用时控制从应用程序转向库程序,并进一步调用一个或多个底层操作系统的功能调用。,2.1.4 套接字通信与UNIX操作系统的输入/输出套接字编程接口采用传统的UNIX输入/输出模式。UNIX操作系统对文件和所有其它的输入/输出设备采用一种统一的操作模式,就是“打开-读-写-关闭”(open-read-write-close)的I/O模式。调用open命令获得对指定文件或设备的使用权,并返回一个用来标识该文件或设备的短整型描述符,作为用户在打开的文件或设备上进行I/O的句柄。多次调用read或write命令来传输数据,期间用描述符作为命令的参数,指明要操作的对象。传输完成
7、后调用close命令,通知操作系统已经完成了对某对象的调用,释放所占用的资源。,当TCP/IP协议被集成到UNIX内核中的时候,相当于在UNIX系统中引入了一种新型的I/O操作。应用程序通过网络协议栈来交换数据。操作系统和应用程序都将套接字编程接口也看作一种输入/输出机制。操作过程类似,创建套接字-使用-删除;操作方法类似,申请生成一个套接字后,系统返回一个短整型描述符来标识这个套接字对象;使用的过程名可以是相同的,如write过程可以将数据发送给另一个程序、文件或网络中的另一个进程。,UNIX系统对于各种I/O的集成提供了灵活性。应用程序可以编写成向任何地方传输数据,取决于描述符对象代表什么
8、(设备、文件、套接字)。系统对套接字和其它I/O使用相同的描述符空间,使得单个应用程序既可以进行网络通信,又可以实现本地数据传输。,用户进程与网络协议的交互,实际要比用户进程与传统I/O设备的交互复杂得多。仅提供open、read、write、close四个过程远不够.使用套接字的应用程序必须说明许多细节。应用程序使用的协议簇、远程计算机的地址、该应用程序是客户机还是服务器、希望的服务类型是面向连接的还是无连接的,等等。为了提供这些细节,每个套接字有许多参数与选项,需要应用程序提供具体值。为避免单个套接字函数参数过多,套接字编程接口的设计者定义了多个函数。例如,创建套接字时,先调用一个函数创建
9、套接字,再调用其它函数说明使用套接字的细节。,2.2 套接字编程的基本概念,图2.1 电插座与电话插座的作用,2.2.1 什么是套接字(SOCKET),套接字是对网络中不同主机上应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制。,图2.2 应用进程、套接口、网络协议栈及操作系统的关系,两个应用进程只要分别连接到自己的套接字,就可以通过网络痛惜,不用去管复杂的网络结构及数据传输过程。,从多个层面来理解套接字这个概念的内涵从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议栈进行通信的接口,是应用程序与网
10、络协议栈交互的接口.从实现的角度来讲,非常复杂。套接字是一个复杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。从使用的角度来讲,非常简单。对套接字的操作形成了一种网络应用程序的编程接口(API),提供了一组系统调用或库函数,可以用于构造套接字、安装绑定套接字、连接套接字、通过套接字交换数据、关闭套接字,实现各种分布式应用。套接字编程接口是一套操作套接字的编程接口函数,套接字是它的操作对象。,2.2.2 套接字的特点1通信域通信域:是一个计算机网络的范围,在这个范围中,所有的计算机使用同一种网络体系结构及协议栈。套接字存在于通信域中。套接字通常只和同一域中的套接字交换数据。
11、如果数据交换要穿越域的边界,就一定要执行某种解释程序。这里,仅仅针对Internet域,并且使用Internet协议族(即TCP/IP协议族)来通信。,2套接字具有三种类型每一个正被使用的套接字都有它确定的类型。只有相同类型的套接字才能相互通信。(1)数据报套接字(Datagram SOCKET)数据报套接字提供无连接的、不保证可靠的、独立的数据报传输服务。在Internet通信域中,数据报套接字使用UDP数据报协议形成的进程间通路,具有UDP协议为上层所提供的服务的所有特点。一般用于轻载通信,并具有多播通信的能力。,图2.3 在Internet通信域中,数据报套接字基于UDP协议,(2)流式
12、套接字(Stream SOCKET)流式套接字提供双向的、有序的、无重复的、无记录边界的可靠的数据流传输服务。在Internet通信域中,流式套接字使用TCP协议形成的进程间通路,具有TCP协议为上层所提供的服务的所有特点。在使用流式套接字传输数据之前,必须在数据的发送端和接收端之间建立连接。一般用于交换大批量的数据时,或者要求数据按照发送的顺序无重复的到达目的地的时候。,图2.4 在Internet通信域中,流式套接字基于TCP协议,(3)原始式套接字(RAW SOCKET)原始式套接字允许对较低层次的协议(如IP、ICMP)直接访问,用于检验新协议的实现。原始套接字保存了数据包中的完整IP
13、头;前面两种套接字只能收到用户数据;因此可以通过原始套接字对数据进行分析。,3套接字的创建套接字由应用层的通信进程创建,并为其服务。即,每一个套接字都有一个相关的应用进程,操作该套接字的代码是该进程的组成部分。4使用确定的IP地址和传输层端口号在生成套接字的描述符后,要将套接字与计算机上的特定的IP地址和传输层端口号相关联,这个过程称为绑定。一个套接口要使用一个确定的三元组网络地址信息,才能使它在网络中唯一地被标识。,2.2.3 套接字的应用场合并非所有的网络应用编程都要使用套接字。套接字适合开发具有如下特点的新网络应用。不管是采用对等模式或者客户机/服务器模式,通信双方的应用程序都需要开发。
14、双方所交换数据的结构和交换数据的顺序有特定的要求,不符合现在成熟的应用层协议,甚至需要自己去开发应用层协议,自己设计最适合的数据结构和信息交换规程。套接字编程层次较低,自由度较大。,2.2.4 套接字使用的数据类型和相关的问题1三种表示套接字地址的结构在套接字编程接口中,定义了三种结构型的数据类型,用来存储协议相关的网络地址,在套接字编程接口的函数调用中要用到它们。(1)sockaddr结构:针对各种通信域的套接字,用来表示地址的一般格式,然后要求每个协议族说明其协议地址如果具体使用这个一般格式的。struct sockaddr u_charsa_len;/地址总长u_char sa_fami
15、ly;/地址家族charsa_data14;/协议地址,(2)sockaddr_in结构:专门针对Internet通信域,存储套接字相关的网络地址信息,例如IP地址,传输层端口号等信息。struct sockaddr_in u_charsa_len;/地址总长u_char sin_family;/地址家族,必须设定为u_shortsin_port;/端口号,2字节struct in_addrsin_addr;/IP 地址,4字节charsin_zero8;/未用,全为0,8字节(3)in_addr结构:专门用来存储 IP地址。Struct in_addr Unsigned longs_addr
16、l;,AF_INET,14字节,(4)这些数据结构的一般用法首先,定义一个sockaddr_in的结构实例,并将它清零。struct sockaddr_in myad;memset(,2本机字节顺序和网络字节顺序本机字节顺序:在具体计算机中的多字节数据的存储顺序。网络字节顺序:多字节数据在网络协议报头中的存储顺序。网络应用程序要在不同的计算机中运行,本机字节顺序是不同的,但网络字节顺序是一定的。所以,在应用程序编程的时候,在把IP地址和端口号装入套接字时,应当把它们从本机字节顺序转换为网络字节顺序;相反,在本机输出时,应将它们从网络字节顺序转换为本机字节顺序。,套接字编程接口为解决这个问题设置
17、了四个函数:htons()短整数本机顺序转换为网络顺序,用于端口号。htonl()长整数本机顺序转换为网络顺序,用于IP地址。ntohs()短整数网络顺序转换为本机顺序,用于端口号。ntohl()长整数网络顺序转化为本机顺序,用于IP地址。这四个函数将被转换的数值作为函数的参数,函数返回值是转换后的结果。,3点分十进制的IP地址的转换在因特网中,IP地址常用点分十进制的表示方法,在套接字中,IP地址是无符号的长整型数,套接字接口设置了两个函数用于两种形式IP地址的转换。(1)inet-addr函数unsigned long inet-addr(const char*cp)入口参数cp:点分十进
18、制形式的IP地址。返回值:网络字节顺序的IP地址,是无符号的长整数。,(2)inet_ntoa函数char*inet_ntoa(struct in_addr in)入口参数in:包含长整型IP地址的 in_addr 结构变量;返回值:指向点分十进制IP地址的字符串的指针.,4域名服务通常,我们使用域名来标识站点,可以将文字型的主机域名直接转换成IP地址。struct hostent*gethostbyname(const char*name);入口参数:是站点的主机域名字符串;返回值:是指向hostent 结构的指针。hostent结构包含主机名、主机别名数组、返回地址的类型(一般是AF-IN
19、ET)、地址长度的字节数、已符合网络字节顺序的主机网络地址等。,2.3 面向连接的套接字编程,2.3.1 套接字的工作过程面向连接的通信方式基于TCP,必须借助流式套接字来编程,应用服务器分为服务器端和客户端,双方是不对称的,需要分别编程。双方首先要创建并安装套接字,做好准备;建立连接,采用三次握手的方式;数据交换,称为客户机与服务器的会话期,会话的内容必须遵守一定的格式和顺序;释放连接。下面介绍面向连接的服务器和客户端的编程步骤.,2.3.2 UNIX套接字编程接口的系统调用1创建套接字SOCKET()创建一个套接字,并返回一个整型描述符:int SOCKET(int Protofamily
20、,int Type,int Protocol);入口参数:Protofamily:套接字使用的协议簇,一般为PF_INET,表示TCP/IP协议簇,PF_DECnet表示DEC协议。type:套接字类型,SOCK_STREMA表示创建面向连接的流传输的流式套接字;SOCK_DGRAMA表示创建无连接的面向消息的数据报套接字。protocol:套接字使用的传输层网络协议,一般为IPPROTO_IP,internet通信域中一般取值为0。,返回值:套接字创建成功,返回一个int型的整数,即所创建套接字的描述符,指向被维护在操作系统内核里的socket数据结构。套接字创建出错,返回1,表示出错类型的
21、代码保存在全局变量Errno中。举例:int sockfd=SOCKET(PF_INTE,SOCK_STREAM,0)创建套接字时,为其分配了内存,并建立了相应的数据结构,用于指定连接的种类和使用的协议,同时还有关于连接队列操作的选项结构字段,创建时设置为默认值。,2绑定套接字到指定的地址 BIND()int BIND(int Sockfd,struct sockaddr*My_addr,int Addrlen);入口参数:Sockfd:是由SOCKET()系统调用创建的套接字描述符,要将其绑定到指定的网络地址上;My_addr:指向sockaddr结构变量的指针,所指结构中保存着特定的网络地
22、址,要将套接字绑定到这个网络地址上;Addlen:是sockaddr结构的长度。,返回值:如果返回0,表示已经绑定成功。如果返回1,表示有错,出错码在Errno中。举例:Struct sockaddr_in my_addr;if(BIND(sockfd,(sockaddr*)&my_addr,sizeof(struct sockaddr_in)0)报错,并退出在服务器端,用作监听客户机连接请求的套接字一定要绑定,因为大多数服务器进程使用熟知端口,并且服务器有时有多块网卡,会有多个IP地址。客户机一般不用绑定。,返回值:如果返回0,表示已经绑定成功。如果返回1,表示有错,出错码在Errno中。服
23、务器调用BIND,来说明它将用来接收通信的协议端口号,同时my_addr 结构中还包含记录IP地址的域,此时如果主机是多宿主机,则in_addr字段取值常量INADDR_ANY,允许服务器在该计算机的任何IP地址上使用一个指定的端口。,3启动监听Listen()int LISTEN(int Sockfd,int Queuesize);入口参数:Sockfd:套接字描述符,通过它来监听来自客户机端的连接请求。Queuesize:等待连接队列的最大长度,最大可设为20,一般设为510。(操作系统为每个监听套接字建立一个用来等待连接的“先进先出”缓冲区队列,若缓冲区队列有空就接收一个客户端请求。)返
24、回值:函数正确执行则返回0,出错则返回-1。举例:LISTEN(Sockfe,10);本套接字只适用于面向连接的流式套接字。,举例:LISTEN(Sockfe,10);,图2.6 监听套接字使用缓冲区接纳多个客户端的连接请求,4接收连接请求ACCEPT()int ACCEPT(int Sockfd,struct sockaddr*Addr,int*addrlen);入口参数:Sockfd:监听客户端连接请求的套接字描述符。Addr:sockaddr结构变量的指针,是一个出口参数,当调用执行完毕时,变量中放置的是所接收客户机端的网络地址。Addrlen:整型变量指针,也是一个出口参数,调用时初始
25、设置为Addr结构的长度,不能为0或null,调用执行完毕时,返回所接收的客户机端网络地址长度。,返回值:如果执行正确,返回一个新的套接字描述符,这个套接字已经与客户机端建立了连接,并专用于此后与客户机端交换数据。如果出错,返回1。说明:调用从监听套接字的等待队列中,取出第一个连接请求,创建一个新的套接字,并通过这个新套接字向客户机端发送连接应答,从而与客户机端建立连接,系统为这个新的套接字分配一个服务器端的自由端口号,这个套接字用于后续与客户机端交换数据,称为响应套接字。,本调用仅适用于面向连接的流式套接字,与LISTEN()配套使用,两个入口参数能返回客户端的网络地址.本调用以阻塞的方式工
26、作,若监听套接字的等待队列为空,则本调用就阻塞直到有连接请求的到来。举例:int clientfd;/定义响应套接字描述符变量int addrlen=sizeof(sockaddr);/获得套接字地址结构长度。struct sockaddr_in cltsockaddr;/定义用于返回客户端地址的结构。clientfd=ACCEPT(listenfd,(sockaddr*)(/接收客户连接请求,5请求建立连接CONNECT()int CONNECT(int Sockfd,struct sockaddr*Service_addr,int Addrlen);入口参数:Sockfd:客户机端的请求套
27、接字;Service_addr:存放服务器端的网络地址;Addrlen:sockaddr结构的长度。返回值:连接成功,返回0;连接未成功,返回-1。,说明:用于客户端请求连接到服务器;对于流式套接字,使用传输层TCP协议,服务器若接受连接请求,即把它放入监听套接字的缓冲区队列,并调用ACCEPT()来接收处理;对于数据报套接字,使用传输层UDP协议,并不建立连接,而只是将套接字置为connected的状态,客户机向同一服务器传送多条信息时,意义在于使用CONNECT在套接字中记录服务器的地址,允许客户机只指明服务器地址一次,而不必在每条信息中都指明目的地址。,举例:if(CONNECT(soc
28、kfd,(struct sockaddr*)(&serv_addr),sizeof(struct sockaddr)0)报错,并退出,6读/写套接字READ()和WRITE()int READ(int sockfd,void*buffer,int len)int WRITE(int sockfd,void*buffer,int len)入口参数:Sockfd:要读/写的套接字描述符,在客户机端是请求套接字,在服务器端是响应套接字;Buffer:字符串变量的指针,指向内存中用来存放数据的读写缓冲区,读套接字时存放从套接字接收到的数据,写套接字的时候存放要发送的数据;Len:读写缓冲区的长度。返回
29、值:对于read()返回实际读到的字符数。,说明:套接字起源于UNIX,UNIX将套接字也统一使用read和write对I/O操作进行数据传输。优点:普遍性,应用程序能够创建一个发往或接收自一个描述符的数据传输,而不必知道这个描述符对应的是一个文件还是一个套接字。缺点:套接字库的实现可能对所有使用套接字的应用增加额外的I/O开销。,7发送 SEND()和接收 RECV()int SEND(int sockfd,char*buf,int len,int flags);int RECV(int sockfd,char*buf,int len,int flags);入口参数:sockfd:用来发送/
30、接收数据的套接字描述符,可以是由SOCKET()创建的,也可以是ACCEPT()返回的;buf:用于发送/接收数据的缓冲区指针;len:要发送的或接收到的字符数;flags:执行本调用的方式,一般置为0.,返回值:处理错误,返回-1;如果正确,对于发送返回实际发送出去的字节数;对于接收返回实际读入缓冲区的字节数。说明:和read、write一样,必须用于已连接的套接字,无需调用者指明目的地参数。,8关闭套接字CLOSE()int CLOSE(int sockfd);入口参数:sockfd:要关闭的套接字描述符。说明:如果是面向连接的,CLOSE在关闭套接字前先终止连接;关闭一个套接字意味着立即
31、终止对它的使用,描述符被释放,以防止应用程序发送或接收更多的数据。,2.3.3 面向连接的套接字编程实例1实例的功能服务器对来访的客户计数,并向客户报告这个计数值;客户建立与服务器的一个连接并等待它的输出;每当连接请求到达时,服务器生成一个可打印的ASCII串信息,将它在连接上发回,然后关闭连接;客户显示收到的信息,然后退出。例如,对于服务器接收的第10次客户连接请求,该客户将收到并打印如下信息:This server has been contacted 10 times.,2实例程序的命令行参数实例是UNIX环境下的C程序,客户和服务器程序在编译后,均以命令行的方式执行。服务器程序执行时可
32、以带一个命令行参数,用来接收请求的监听套接字的协议端口号。这个参数是可选的,如果不指定端口号,代码将使用程序内定的缺省端口号5188。客户程序执行时可以带两个命令行参数:一个是服务器所在计算机的主机名;另一个是服务器监听的协议端口号。,这两个参数都是可选的。如果没有指定协议端口号,客户使用程序内定的缺省值5188。如果一个参数也没有,客户使用缺省端口和主机名localhost,localhost是映射到客户所运行的计算机的一个别名。允许客户与本地机上的服务器通信,对调试是很有用的。,3客户程序代码/*-*程序:client.c*目的:创建一个套接字,通过网络连接一个服务器,并打印来自服务器的信
33、息*语法:client host port*host-运行服务器的计算机的名字*port-服务器监听套接字所用协议端口号*注意:两个参数都是可选的。如果未指定主机名,客户使用localhost;如果未指定端口号,*客户将使用PROTOPORT中给定的缺省协议端口号*-*/,#include/*基本系统数据类型*/#include/*UNIX下,套接字接口包含文件*/#include/*Internet地址簇*/#include/*Internet定义*/#include/*网络数据库操作*/#include/*标准输入输出*/#include/*字符串处理*/#define PROTOPORT
34、 5188/*缺省协议端口号*/extern int errno;char localhost=“localhost”;/*缺省主机名*/,main(argc,argv)int argc;char*argv;struct hostent*ptrh;/*指向主机列表中一个条目的指针*/struct sockaddr_in servaddr;/*存放服务器端网络地址的结构*/intsockfd;/*客户端的套接字描述符*/intport;/*服务器端套接字协议端口号*/char*host;/*服务器主机名指针*/int n;/*读取的字符数*/char buf1000;/*缓冲区,接收服务器发来的
35、数据*/,memset(char*)/*否则,使用缺省端口号*/if(port0)/*如果端口号是合法的数值,就将它装入网络地址结构*/,servaddr.sin_port=htons(u_short)port);else/*否则,打印错误信息并退出*/fprintf(stderr,”bad port number%sn”,argv2);exit(1);/*检查主机参数并指定主机名*/if(argc1)host=argv1;/*如果指定了主机名参数,就使用它*/elsehost=localhost;/*否则,使用缺省值*/,/*将主机名转换成相应的IP地址并复制到servaddr 结构中*/*
36、从服务器主机名得到相应的IP地址*/ptrh=gethostbyname(host);if(char*)ptrh=null)/*检查主机名的有效性,无效则退出*/fprintf(stderr,”invalid host:%sn”,host);exit(1);memcpy(,Struct hostent char*h_name;char*h_aliases;int h_addrtype;int h_length;char*h_addr_list;,/*创建一个套接字*/sockfd=SOCKET(AF_INET,SOCK_STREAM,0);if(sockfd 0)fprintf(stderr,
37、”socket creation failedn”);exit(1);/*请求连接到服务器*/if(connect(sockfd,(struct sockaddr*),/*从套接字反复读数据,并输出到用户屏幕上*/n=recv(sockfd,buf,sizeof(buf),0);while(n 0)write(1,buf,n);n=recv(sockfd,buf,sizeof(buf),0);/*关闭套接字*/closesocket(sockfd);/*终止客户程序*/exit(0);,4服务器实例代码/*-*程序:server.c*目的:分配一个套接字,然后反复执行如下几步:*(1)等待客户
38、的下一个连接*(2)发送一个短消息给客户*(3)关闭与客户的连接*(4)转向(1)步*命令行语法:server port*port 服务器端监听套接字使用的协议端口号*注意:端口号可选。如果未指定端口号,服务器使用PROTOPORT中指定的缺省*端口号*-*/,#include#include#include#include#include#include#define PROTOPORT 5188/*监听套接字的缺省协议端口号*/#define QLEN 6/*监听套接字的请求队列大小*/int visits=0;/*对于客户连接的计数*/,main(argc,argc)int argc;c
39、har*argv;struct hostent*ptrh;/*指向主机列表中一个条目的指针*/struct sockaddr_in servaddr;/*存放服务器网络地址的结构*/struct sockaddr_in clientaddr;/*存放客户网络地址的结构*/int listenfd;/*监听套接字描述符*/int clientfd;/*响应套接字描述符*/int port;/*协议端口号*/int alen;/*地址长度*/char buf1000;/*供服务器发送字符串所用的缓冲区*/,/*清空sockaddr结构*/memset(char*)/*否则,使用缺省端口号*/,if
40、(port 0)/*测试端口号是否合法*/servaddr.sin_port=htons(u_short)port);else/*打印错误信息并退出*/fprintf(stderr,”bad port number%sn”,argv1);exit(1);/*创建一个用于监听的流式套接字*/listenfd=SOCKET(AF_INET,SOCK_STREAM,0);if(listenfd 0)fprintf(stderr,“socket creation failedn”);exit(1);,/*将本地地址绑定到监听套接字*/if(bind(listenfd,(struct sockaddr*
41、),/*服务器主循环接受和处理来自客户端的连接请求*/while(1)alen=sizeof(clientaddr);/*接受客户端连接请求,并生成响应套接字*/if(clientfd=accept(listenfd,(struct sockaddr*)/*关闭响应套接字*/,关于阻塞的问题,图2.7 服务器进程因调用ACCEPT()而被阻塞,2.3.4 进程的阻塞问题和对策1什么是阻塞阻塞:是指一个进程执行了一个函数或者系统调用,该函数由于某种原因不能立即完成,因而不能返回调用它的进程,导致进程受控于这个函数而处于等待的状态,进程的这种状态称为阻塞。,图2.8 RECV()函数的两种执行方式
42、,2能引起阻塞的套接字调用在Berkeley套接字网络编程接口的模型中,套接字的默认行为是阻塞的,具体地说,在一定情况下,有多个操作套接字的系统调用会引起进程阻塞。(1)ACCEPT()(2)READ()、RECV()和READFORM()(3)WRITE()、SEND()和SENDTO()(4)CONNECT()(5)SELECT()(6)CLOSESOCKET(),图2.9 采用阻塞工作模式的服务器不能很好地为多个客户服务,3阻塞工作模式带来的问题采用阻塞工作模式的单进程服务器,不能很好地同时为多个客户服务的。图2.9是一个例子。,4一种解决方案利用UNIX操作系统的FORK()系统调用,
43、编制多进程并发执行的服务器程序,可以创建子进程。对每一个客户端,用一个专门的进程为它服务,通过进程的并发执行,来对实现多个客户的并发服务.基本的编程框架是:,父进程代码If(pid=FORK()=0).子进程代码.else if(pid0)报错信息父进程代码,举例:#include#include#include#include void main(int argc,char*argv)int listenfd,clientfd,pid;struct sockaddr_in ssockaddr,csockaddr;char buffer1024;int addrlen,n;,/*创建监听套接字
44、*/listenfd=socket(AF_INET,SOCK_STREAM,0);if(listenfd 0)fprintf(stderr,socket error!n);exit(1);,/*为监听套接字绑定网络地址*/memset(,/*启动套接字的监听*/listen(listenfd,5);addrlen=sizeof(sockaddr);/*服务器进入循环,接受并处理来自不同客户端的连接请求*/while(1)clientfd=accept(listenfd,(sockaddr*)(/*accept调用返回时,表明有客户端请求连接,创建子进程处理连接*/If(pid=FORK()=0
45、),/*显示客户端的网络地址*/printf(Client Addr:%s%dn,inet_ntoa(csockaddr.sin_addr),ntohs(csockaddr.sin_port);/*读取客户端发送来的数据,在将它们返回到客户端*/while(n=read(clientfd,buffer,1024)0)buffern=0;printf(Client Send:%s,buffer);write(clientfd,buffer,n);if(n 0)fprintf(stderr,read error!n);exit(3);,/*通信完毕,关闭与这个客户连接的套接字*/printf(cl
46、ent%s closed!n,inet_ntoa(csockaddr.sin_addr);close(clientfd);exit(1);else if(pid 0)printf(fork failed!n);close(clientfd);close(listenfd);/*关闭监听套接字*/,2.4 无连接的套接字编程,2.4.1 无连接的套接字编程的两种模式使用数据报套接字开发网络应用程序,既可以采用客户/服务器模式,也可以采用对等模式。,图2.10 对等模式的数据报套接字的编程模型,1对等模式,对等模式无连接套接字特点:应用双方是对等的:要经过4个阶段,创建套接字、绑定套接字、发送/接
47、收数据、关闭套接字;双方都必须确切知道对方的网络地址,并在各自的进程中将约定好的网络地址绑定到自己的套接字上;每一次传递数据时,在sendto和recvfrom系统调用中必须包含对方的网络地址信息;进程也会因为发送或接收数据而发生阻塞。,2客户/服务器模式,图2.11 C/S模式的数据报套接字的编程模型,C/S模式无连接套接字特点:应用双方不是对等的:服务器要先启动,被动等待访问,要经过创建套接字、绑定套接字、发送/接收数据、关闭套接字4个阶段,将套接字绑定到众所周知的端口上;客户机套接字使用动态分配的自由端口上,不需要进行绑定;客户机主动请求服务,并在数据报中携带双方的地址;服务器可以接受多
48、个客户端的数据。,2.4.2 两个专用的系统调用1发送数据报SENDTO()int SENDTO(int sockfd,const void*msg,int len,unsigned int flags,struct sockaddr*to,int tolen);入口参数:Sockfd:发送方的描述符,包含发送方的网络地址;msg:字符串指针,指向发送缓冲区;len:发送缓冲区长度;Flags:发送方式,一般为0;To:指向sockaddr结构的指针,包含接收方的网络地址;Tolen:sockaddr的结构长度。返回值:成功返回发送字节数,失败返回1.,2接收数据报 RECVFROM()int
49、 RECVFROM(int sockfd,void*buf,int len,unsigned int flags,struct sockaddr*from,int*fromlen)入口参数:Sockfd:接收方的描述符,包含发送方的网络地址;msg:字符串指针,指向接收缓冲区;len:接收缓冲区长度;flags:接收方式,一般为0;From:指向sockaddr结构的指针,是个出口参数,调用接收后包含发送方的网络地址;Fromlen:出口参数,sockaddr的结构长度。返回值:成功返回接收的字节数,失败返回1.,2.4.3 数据报套接字的对等模式编程实例聊天程序#include#includ
50、e#include#include#include#includeint int_proc(int signo)(),int int_proc(int signo)()void main(int argc,char*argv)struct sockaddr_in daddr,saddr,cmpaddr;int sockfd;int timer=3;char buffer1024;int addrlen,n;,if(argc!=5)printf(please uselike:./a.out 192.192.1.88 192.192.1.227);exit(0);sockfd=socket(AF_