资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,吉林大学软件学院,第,2,章,UNIX,中的套接字网络编程接口,UNIX,套接字网络编程接口的产生与发展过程,套接字与,UNIX,操作系统的关系,套接字编程的基本概念,套接字的特点、应用场合、使用的数据类型,面向连接的套接字编程,套接字的工作过程、系统调用、编程实例,借助实例分析进程的阻塞问题和对策,无连接的套接字编程,无连接套接字编程的两种模式(,C/S,和对等),数据报套接字的对等模式编程实例,2.1 UNIX,套接字网络编程接口的产生与发展,2.1.1,问题的提出,应用程序与协议软件进行交互时须说明许多细节,:,是服务器还是客户机,主动还是被动通信?,发送方需说明发送的数据;,接收方需说明接收的数据如何存放。,站在应用程序实现的角度,应用程序如何方便地使用协议栈软件进行通信呢?,如果能在应用程序与协议栈软件之间提供一个软件接口,就可以方便客户与服务器软件的编程。,UNIX,最早将,TCP/IP,协议簇集成到内核中,,UNIX,的开发者提出并实现了套接字应用编程接口。,套接字应用程序编程接口:,是网络应用程序通过网络协议栈进行通信时所使用的接口,即应用程序与协议栈软件之间的接口,简称,套接字编程接口,(Socket API),。,定义了应用程序与协议栈软件进行交互时可以使用的一组操作,决定了应用程序使用协议栈的方式、应用程序所能实现的功能、以及开发具有这些功能的程序的难度。,套接字编程接口给出了应用程序能够调用的一组过程,以及这些过程所需的参数,每个独立的过程完成一个与协议栈软件交互的基本操作(如:建立连接、接收数据、释放链接)。,2.1.2,套接字编程接口的起源与应用,加州大学伯克利分校开发了一个包括,TCP/IP,协议簇的,BSD,UNIX,,并迅速得到推广,套接字编程接口,是,这个操作系统的一部分。,TCP/IP,标准并没有定义应用程序用来与该协议进行交互的应用程序编程接口,只规定了应该提供的一般操作,并允许各个操作系统去定义用来实现这些操作的具体,API,。,一个协议标准可能只是建议某个操作在应用程序发送数据时是需要的,而由应用程序编程接口来定义具体的函数名和每个参数的类型。,尽管协议标准允许操作系统设计者开发自己的应用程序编程接口,但由于,BSD UNIX,的广泛使用,后来的许多操作系统及编程语言都选择了对套接字编程接口的支持。,由于这个套接字规范最早是由,Berkeley,大学开发的,一般将它称为,Berkeley Sockets,规范。,Berkeley Sockets,规范规定了一系列与套接字使用有关的库函数,为在,UNIX,操作系统下不同计算机中的应用程序进程之间,使用,TCP/IP,协议簇进行网络通信提供了一套应用程序编程接口。,2.1.3,套接字编程接口的两种实现方式,采用两种实现套接字编程接口的方式:,在操作系统的内核中增加相应的软件来实现;,通过开发操作系统之外的函数库来实现。,在,BSD UNIX,及起源于它的操作系统中,套接字函数是操作系统本身的功能调用,是操作系统内核的一部分。,其他操作系统供应商为了不修改基本操作系统,开发了套接字库(,Socket Library,)来提供套接字编程接口。,套接字库中的每个过程具有与,UNIX,套接字函数相同的名字与参数,向没有本机套接字的操作系统上的应用程序提供套接字编程接口。,套接字库与操作系统内核中实现的套接字,在语义上是相同的,。,应用程序调用套接字过程不必管它是由操作系统内核过程提供的,还是由库过程提供的。,这种方式提供了程序的可移植性:,将程序从一台计算机移植到另一台计算机时,程序的源代码不需改动,只要用新计算机上的套接字库重新编译即可。,套接字库与操作系统直接提供的本机套接字编程接口,在实现上是不同的,。,套接字库的过程需要链接到应用程序中;,并驻留于应用程序地址空间;,调用时控制从应用程序转向库程序,并进一步调用一个或多个底层操作系统的功能调用。,2.1.4,套接字通信与,UNIX,操作系统的输入,/,输出,套接字编程接口采用传统的,UNIX,输入,/,输出模式。,UNIX,操作系统对文件和所有其它,的输入,/,输出设备,采用一种统一的操作模式,就是,“打开,-,读,-,写,-,关闭”(,open-read-write-close,),的,I/O,模式。,调用,open,命令,获得对指定文件或设备的使用权,并返回一个用来标识该文件或设备的短整型描述符,作为用户在打开的文件或设备上进行,I/O,的句柄。,多次调用,read,或,write,命令,来传输数据,期间用描述符作为命令的参数,指明要操作的对象。,传输完成后调用,close,命令,,通知操作系统已经完成了对某对象的调用,释放所占用的资源。,当,TCP/IP,协议被集成到,UNIX,内核中的时候,相当于在,UNIX,系统中引入了一种新型的,I/O,操作。,应用程序通过网络协议栈来交换数据。,操作系统和应用程序都将套接字编程接口也看作一种输入,/,输出机制。,操作过程类似,创建套接字,-,使用,-,删除;,操作方法类似,申请生成一个套接字后,系统返回一个短整型描述符来标识这个套接字对象;,使用的过程名可以是相同的,如,write,过程可以将数据发送给另一个程序、文件或网络中的另一个进程。,UNIX,系统对于各种,I/O,的集成提供了灵活性。,应用程序可以编写成向任何地方传输数据,取决于描述符对象代表什么,(,设备、文件、套接字,),。,系统对套接字和其它,I/O,使用相同的描述符空间,使得单个应用程序既可以进行网络通信,又可以实现本地数据传输。,用户进程与网络协议的交互,实际要比用户进程与传统,I/O,设备的交互复杂得多。,仅提供,open,、,read,、,write,、,close,四个过程远不够,.,使用套接字的应用程序必须说明许多细节。,应用程序使用的协议簇、远程计算机的地址、该应用程序是客户机还是服务器、希望的服务类型是面向连接的还是无连接的,等等。,为了提供这些细节,每个套接字有许多参数与选项,需要应用程序提供具体值。,为避免单个套接字函数参数过多,套接字编程接口的设计者定义了多个函数。,例如,创建套接字时,先调用一个函数创建套接字,再调用其它函数说明使用套接字的细节。,2.2,套接字编程的基本概念,图,2.1,电插座与电话插座的作用,2.2.1,什么是套接字(,SOCKET,),套接字是对网络中不同主机上应用进程之间进行双向通信的端点的抽象。,一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议栈交换数据的机制,。,图,2.2,应用进程、套接口、网络协议栈及操作系统的关系,两个应用进程只要分别连接到自己的套接字,就可以通过网络痛惜,不用去管复杂的网络结构及数据传输过程。,从多个层面来理解套接字这个概念的内涵,从所处的地位来讲,,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议栈进行通信的接口,是应用程序与网络协议栈交互的接口,.,从实现的角度来讲,,非常复杂。套接字是一个复杂的软件机构,包含了一定的数据结构,包含许多选项,由操作系统内核管理。,从使用的角度来讲,,非常简单。对套接字的操作形成了一种网络应用程序的编程接口(,API,),提供了一组系统调用或库函数,可以用于,构造套接字、安装绑定套接字、连接套接字、通过套接字交换数据、关闭套接字,,实现各种分布式应用。,套接字编程接口,是一套操作套接字的编程接口函数,套接字是它的操作对象。,2.2.2,套接字的特点,1,通信域,通信域:,是一个计算机网络的范围,在这个范围中,所有的计算机使用同一种网络体系结构及协议栈。,套接字存在于通信域中。,套接字通常只和同一域中的套接字交换数据。,如果数据交换要穿越域的边界,就一定要执行某种解释程序。,这里,仅仅针对,Internet,域,并且使用,Internet,协议族(即,TCP/IP,协议族)来通信。,2,套接字具有三种类型,每一个正被使用的套接字都有它确定的类型。,只有相同类型的套接字才能相互通信。,(1),数据报套接字(,Datagram SOCKET,),数据报套接字提供无连接的、不保证可靠的、独立的数据报传输服务。,在,Internet,通信域中,数据报套接字使用,UDP,数据报协议形成的进程间通路,具有,UDP,协议为上层所提供的服务的所有特点。,一般用于轻载通信,并具有多播通信的能力。,图,2.3,在,Internet,通信域中,数据报套接字基于,UDP,协议,(2),流式套接字(,Stream SOCKET,),流式套接字提供双向的、有序的、无重复的、无记录边界的可靠的数据流传输服务。,在,Internet,通信域中,流式套接字使用,TCP,协议形成的进程间通路,具有,TCP,协议为上层所提供的服务的所有特点。,在使用流式套接字传输数据之前,必须在数据的发送端和接收端之间建立连接。,一般用于交换大批量的数据时,或者要求数据按照发送的顺序无重复的到达目的地的时候。,图,2.4,在,Internet,通信域中,流式套接字基于,TCP,协议,(3),原始式套接字(,RAW SOCKET,),原始式套接字允许对较低层次的协议,(,如,IP,、,ICMP),直接访问,用于检验新协议的实现。,原始套接字保存了数据包中的完整,IP,头;,前面两种套接字只能收到用户数据;,因此可以通过原始套接字对数据进行分析。,3,套接字的创建,套接字由应用层的通信进程创建,并为其服务。,即,每一个套接字都有一个相关的应用进程,操作该套接字的代码是该进程的组成部分。,4,使用确定的,IP,地址和传输层端口号,在生成套接字的描述符后,要将套接字与计算机上的特定的,IP,地址和传输层端口号相关联,这个过程称为,绑定,。,一个套接口要使用一个确定的三元组网络地址信息,才能使它在网络中唯一地被标识。,2.2.3,套接字的应用场合,并非所有的网络应用编程都要使用套接字。,套接字适合开发具有如下特点的新网络应用。,不管是采用对等模式或者客户机,/,服务器模式,通信双方的应用程序都需要开发。,双方所交换数据的结构和交换数据的顺序有特定的要求,不符合现在成熟的应用层协议,甚至需要自己去开发应用层协议,自己设计最适合的数据结构和信息交换规程。,套接字编程层次较低,自由度较大。,2.2.4,套接字使用的数据类型和相关的问题,1,三种表示套接字地址的结构,在套接字编程接口中,定义了三种结构型的数据类型,用来存储协议相关的网络地址,在套接字编程接口的函数调用中要用到它们。,(1)sockaddr,结构:,针对各种通信域的套接字,用来表示地址的一般格式,然后要求每个协议族说明其协议地址如果具体使用这个一般格式的。,struct sockaddr,u_charsa_len;/,地址总长,u_char sa_family;/,地址家族,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_addrl;,AF_INET,14,字节,(4),这些数据结构的一般用法,首先,定义一个,sockaddr_in,的结构实例,并将它清零。,struct sockaddr_in myad;,memset(,然后,为这个结构赋值。,myad.sin_family=AF_INET;,myad.sin_port=htons(8080);,myad.sin_addr.s_addr=htonl(INADDR-ANY);,第三步,在函数调用中使用时,将这个结构强制转换为,sockaddr,类型。,accept(listenfd,(sockaddr*)(,2,本机字节顺序和网络字节顺序,本机字节顺序:,在具体计算机中的多字节数据的存储顺序。,网络字节顺序:,多字节数据在网络协议报头中的存储顺序。,网络应用程序要在不同的计算机中运行,本机字节顺序是不同的,但网络字节顺序是一定的。,所以,在应用程序编程的时候,在把,IP,地址和端口号装入套接字时,应当把它们从本机字节顺序转换为网络字节顺序;相反,在本机输出时,应将它们从网络字节顺序转换为本机字节顺序。,套接字编程接口为解决这个问题设置了四个函数:,htons(),短整数本机顺序转换为网络顺序,用于端口号。,htonl(),长整数本机顺序转换为网络顺序,用于,IP,地址。,ntohs(),短整数网络顺序转换为本机顺序,用于端口号。,ntohl(),长整数网络顺序转化为本机顺序,用于,IP,地址。,这四个函数将被转换的数值作为函数的参数,函数返回值是转换后的结果。,3,点分十进制的,IP,地址的转换,在因特网中,,IP,地址常用点分十进制的表示方法,在套接字中,,IP,地址是无符号的长整型数,套接字接口设置了两个函数用于两种形式,IP,地址的转换。,(1)inet-addr,函数,unsigned long inet-addr(const char*cp),入口参数,cp,:点分十进制形式的,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-INET,)、地址长度的字节数、已符合网络字节顺序的主机网络地址等。,2.3,面向连接的套接字编程,2.3.1,套接字的工作过程,面向连接的通信方式基于,TCP,,必须借助流式套接字来编程,应用服务器分为服务器端和客户端,双方是不对称的,需要分别编程。,双方首先要创建并安装套接字,做好准备;,建立连接,采用三次握手的方式;,数据交换,称为客户机与服务器的会话期,会话的内容必须遵守一定的格式和顺序;,释放连接。,下面介绍面向连接的服务器和客户端的编程步骤,.,2.3.2 UNIX,套接字编程接口的系统调用,1,创建套接字,SOCKET(),创建一个套接字,并返回一个整型描述符:,int SOCKET(int Protofamily,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,,表示出错类型的代码保存在全局变量,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,结构变量的指针,所指结构中保存着特定的网络地址,要将套接字绑定到这个网络地址上;,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,中。,服务器调用,BIND,,来说明它将用来接收通信的协议端口号,同时,my_addr,结构中还包含记录,IP,地址的域,此时如果主机是多宿主机,则,in_addr,字段取值常量,INADDR_ANY,,允许服务器在该计算机的任何,IP,地址上使用一个指定的端口。,3,启动监听,Listen(),int LISTEN(int Sockfd,int Queuesize);,入口参数:,Sockfd,:套接字描述符,通过它来监听来自客户机端的连接请求。,Queuesize,:等待连接队列的最大长度,最大可设为,20,,一般设为,5,10,。(操作系统为每个监听套接字建立一个用来等待连接的“先进先出”缓冲区队列,若缓冲区队列有空就接收一个客户端请求。),返回值:,函数正确执行则返回,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,:整型变量指针,也是一个出口参数,调用时初始设置为,Addr,结构的长度,不能为,0,或,null,,调用执行完毕时,返回所接收的客户机端网络地址长度。,返回值:,如果执行正确,返回一个新的套接字描述符,这个套接字已经与客户机端建立了连接,并专用于此后与客户机端交换数据。,如果出错,返回,1,。,说明:,调用从监听套接字的等待队列中,取出第一个连接请求,创建一个新的套接字,并通过这个新套接字向客户机端发送连接应答,从而与客户机端建立连接,系统为这个新的套接字分配一个服务器端的自由端口号,这个套接字用于后续与客户机端交换数据,称为响应套接字。,本调用仅适用于面向连接的流式套接字,,与,LISTEN(),配套使用,两个入口参数能返回客户端的网络地址,.,本调用以阻塞的方式工作,,若监听套接字的等待队列为空,则本调用就阻塞直到有连接请求的到来。,举例:,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,:客户机端的请求套接字;,Service_addr,:存放服务器端的网络地址;,Addrlen,:,sockaddr,结构的长度。,返回值:,连接成功,返回,0,;,连接未成功,返回,-1,。,说明:,用于客户端请求连接到服务器;,对于流式套接字,使用传输层,TCP,协议,服务器若接受连接请求,即把它放入监听套接字的缓冲区队列,并调用,ACCEPT(),来接收处理;,对于数据报套接字,使用传输层,UDP,协议,并不建立连接,而只是将套接字置为,connected,的状态,客户机向同一服务器传送多条信息时,意义在于使用,CONNECT,在套接字中记录服务器的地址,允许客户机只指明服务器地址一次,而不必在每条信息中都指明目的地址。,举例:,if(CONNECT(sockfd,(struct sockaddr*)(&serv_addr),sizeof(struct sockaddr)2),port=atoi(argv2);,/*,如果指定了协议端口,就转换成整数*,/,else,port=PROTOPORT;,/*,否则,使用缺省端口号*,/,if(port0),/*,如果端口号是合法的数值,就将它装入网络地址结构*,/,servaddr.sin_port=htons(u_short)port);,else,/*,否则,打印错误信息并退出*,/,fprintf(stderr,”,bad port number%sn,”,argv2);,exit(1);,/*,检查主机参数并指定主机名*,/,if(argc1),host=argv1;,/*,如果指定了主机名参数,就使用它*,/,else,host=localhost;,/*,否则,使用缺省值*,/,/*,将主机名转换成相应的,IP,地址并复制到,servaddr,结构中*,/,/*,从服务器主机名得到相应的,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,”,socket creation failedn,”,);,exit(1);,/*,请求连接到服务器*,/,if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)0),write(1,buf,n);,n=recv(sockfd,buf,sizeof(buf),0);,/*,关闭套接字*,/,closesocket(sockfd);,/*,终止客户程序*,/,exit(0);,4,服务器实例代码,/*-,*,程序:,server.c,*,目的:分配一个套接字,然后反复执行如下几步:,*(,1,)等待客户的下一个连接,*(,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;,char*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*),servaddr.sin_family=AF_INET;,/*,设置为因特网协议族*,/,servaddr.sin_addr.s_addr=INADDR_ANY;,/*,设置本地,IP,地址*,/,/*,检查命令行参数,若已指定,则使用该端口号,否则使用缺省端口号*,/,if(argc 1),port=atoi(argv1);,/*,如果指定了端口号,就将它转换成整数*,/,else,port=PROTOPORT;,/*,否则,使用缺省端口号*,/,if (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*)&servaddr,sizeof(servaddr)0),fprintf(stderr,”,bind failedn,”,);,exit(1);,/*,开始监听,并指定监听套接字请求队列的长度*,/,if (listen(listenfd,QLEN)0),fprintf(stderr,”,listen filedn,”,);,exit(1);,/*,服务器主循环,接受和处理来自客户端的连接请求*,/,while(1),alen=sizeof(clientaddr);,/*,接受客户端连接请求,并生成响应套接字*,/,if(clientfd=accept(listenfd,(struct sockaddr*)&clientaddr,&alen)0),fprintf(stderr,“,accept failedn,”,);,exit(1);,visits+;,/*,累加访问的客户数*,/,sprintf(buf,“,this server has been contacted%d time n,”,visits);,send(clientfd,buf,strlen(buf),0);,/*,向客户端发送信息*,/,closesocket(clientfd);,/*,关闭响应套接字*,/,关于阻塞的问题,图,2.7,服务器进程因调用,ACCEPT(),而被阻塞,2.3.4,进程的阻塞问题和对策,1,什么是阻塞,阻塞:,是指一个进程执行了一个函数或者系统调用,该函数由于某种原因不能立即完成,因而不能返回调用它的进程,导致进程受控于这个函数而处于等待的状态,进程的这种状态称为阻塞。,图,2.8 RECV(),函数的两种执行方式,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(),系统调用,编制多进程并发执行的服务器程序,可以创建子进程。,对每一个客户端,用一个专门的进程为它服务,通过进程的并发执行,来对实现多个客户的并发服务,.,基本的编程框架是:,父进程代码,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;,/*,创建监听套接字*,/,listenfd=socket(AF_INET,SOCK_STREAM,0);,if (listenfd 0),fprintf(stderr,socket error!n);,exit(1);,/*,为监听套接字绑定网络地址*,/,memset(,ssockaddr.sin_family=AF_INET;,ssockaddr.sin_addr.s_addr=htonl(INADDR_ANY);,ssockaddr.sin_port=htons(8080);,if (bind(listenfd,&ssockaddr,sizeof(struct sockaddr_in)0),buffern=0;,printf(Client Send:%s,buffer);,write(clientfd,buffer,n);,if(n 0),fprintf(stderr,read error!n);,exit(3);,/*,通信完毕,关闭与这个客户连接的套接字*,/,printf(clent%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,个阶段,创建套接字、绑定套接字、发送,/,接收数据、关闭套接字,;,双方都必须确切知道对方的网络地址,并在各自的进程中将约定好的网络地址绑定到自己的套接字上;,每一次传递数据时,在,sendto,和,recvfrom,系统调用中必须包含对方的网络地址信息;,进程也会因为发送或接收数据而发生阻塞。,2,客户,/,服务器模式,图,2.11 C/S,模式的数据报套接字的编程模型,C/S,模式无连接套接字特点:,应用双方不是对等的:服务器要先启动,被动等待访问,要经过创建套接字、绑定套接字、发送,/,接收数据、关闭套接字,4,个阶段,将套接字绑定到众所周知的端口上;,客户机套接字使用动态分配的自由端口上,不需要进行绑定;,客户机主动请求服务,并在数据报中携带双方的地址;,服务器可以接受多个客户端的数据。,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 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#include#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_INET,SOCK_DGRAM,0);addrlen=sizeof(struct sockaddr_in);memset(,addrlen=sizeof(struct sockaddr_in);memset(,bind(sockfd,
展开阅读全文