第24章 网络编程进阶.pdf
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_05.gif)
《第24章 网络编程进阶.pdf》由会员分享,可在线阅读,更多相关《第24章 网络编程进阶.pdf(30页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、 第 24 章 网络编程进阶 上一章讨论了套接字编程的基本操作,本章将讨论套接字编程的高级技巧。使用这些技巧有助于编写高质量的网络应用程序。本章最后还介绍了使用套接字进行本地的进程之间的通信。在 Linux 环境中这种进程间的通信方法是经常使用的。24.1 套接字编程深入 套接字编程中有许多高级技巧,使用这些高级技巧可以更好地操作套接字,完成网络通信的任务。掌握这些技巧,不仅可以开发出高质量的网络应用程序,而且可以帮助读者从本质上了解套接字的行为。本小节将介绍一些关于套接字的内容。24.1.1 bind 函数的作用 面向连接的网络应用程序通常分为服务器端和客户端两个部分,服务器端的执行流程一般
2、分为以下 4 步:?调用 socket 函数,建立一个套接字,该套接字用于接下来的网络通信。?调用 bind 函数,将该套接字绑定一个地址,并指定一个端口号。?调用 listen 函数,使用该套接字监听连接请求。?当请求到来时,调用 accept 函数,复制该套接字处理请求。客户端程序相对简单,只需要以下两个步骤:?调用 socket 函数,创建一个套接字。?调用 connect 使用该套接字与服务器进行连接。服务器程序和客户端程序的显著区别在于客户端程序不需要调用 bind 函数。bind 函 数的作用是将套接字绑定一个 IP 地址和端口号,因为这两个元素可以在网络环境中唯一的标识一个进程。
3、如果套接字没有使用 bind 函数绑定地址和端口,那么在调用 listen 函数和connect 函数的时候内核会自动为套接字绑定。由此可知,客户端程序实际上在调用 connect 函数时由内核负责为其套接字绑定地址和端口,服务器程序也可以在调用 listen 函数时由内核自动绑定套接字。因此,调用 bind函数的这个步骤理论上讲是可以省略的。事实并非如此。虽然 listen 函数和 connect 函数都可以绑定套接字,但是其效果不一样。connect 函数使用一个设置好的地址结构(sockaddr_in)作为参数,结构中指定了服务器的IP 地址和需要通信的端口号。但是 listen 函数没
4、有这个参数,所以 listen 函数不能使用设置好的地址结构,只能由系统设置 IP 地址和端口号,如图 24-1 所示。第 24 章 网络编程进阶 741 图 24-1 使用 bind 函数的作用 服务器端的程序不关心客户端的 IP 地址,内核会将其绑定为任意值(INADDR_ANY),端口号也会由内核临时指派一个可用的端口。由于是临时指派,所以就会导致一个问题,就是每次执行服务器程序时,所使用的端口不一样。这个问题很严重,因为客户端需要指定需要通信的服务器的端口,这样做的结果就是每次重新启动服务器程序后都要对客户端程序作调整,这显然是不可能的。由此可知,bind函数对于服务器端程序是多么的重
5、要。24.1.2 并发服务器 读者可以总结出一般的面向连接的服务器程序的代码框架。该代码框架创建一个进程,处理客户端的连接请求,处理连接和监听连接是并发的,其一般模型如下:int main(void)socket(.);/*步骤 1 创建套接字*/bind(.);/*步骤 2 绑定地址和端口号*/listen(.);/*步骤 3 启动监听*/while(1)/*服务器程序多是驻留程序*/accept(.);/*接受并处理一个连接请求*/while(1)/*如果客户端程序是一个交互式程序,这里又出现一个死循环*/read(.);process(.);/*与客户端交互,客户端与用户交互,直到用户退
6、出客户端*/write(.);close(.);/*客户端退出,关闭连接,通信结束*/return 0;Linux C 程序设计大全 742 这种面向连接的服务器有一个弊端:服务器一次只能处理一个客户端的请求,只有在该客户的所有请求都满足后,服务器才可以继续后面的请求。如果有一个客户端占用服务器不放时,其他的客户机将都不能工作。?注意:由于客户端程序可能是交互的,所以如果有一个客户端程序长时间连接在服务器上又得不到用户的命令时,其他排队的请求就会被“饿死”。所以一般的面向连接服务器不会使用一个循环的框架,取而代之的是使用对进程处理的方式处理多个请求,这就是并发服务器的框架,其执行流程如图 24
7、-2 所示。图 24-2 并发服务器模型 第 24 章 网络编程进阶 743并发服务器的一般模型如下所示:int main(void)socket(.);bind(.);listen(.);while(1)accept(.);if(fork(.)=0)/*子进程处理客户端的请求*/while(1)close(.);/*关闭监听套接字*/read(.);/*即使客户端是交互式的也没关系,父进程可以接受其他请求*/process(.);write(.);close(.);/*关闭连接处理套接字*/exit(.);/*通信结束,子进程退出*/else close(.);/*关闭连接处理套接字*/cl
8、ose(.);/*关闭监听套接字*/return 0;子进程负责处理连接请求,因此关闭监听套接字;父进程继续监听请求,因此关闭连接套接字。当服务器程序退出后,则关闭监听套接字。由此可见,在并发服务器的代码框架中,套接字的创建和关闭也是一一对应的,读者可以使用这种方法检查自己的程序是否正确。对于面向连接的服务器,并发服务器可以解决循环服务器客户机独占服务器的情况。但是也同时带来了以下两个新的问题:?服务器要创建子进程来处理客户端的连接请求,而创建子进程是一种非常消耗资源的操作。为了提高效率必须使用更好的算法和更优秀的代码。?子进程结束运行后,要注意对其资源的回收,否则会造成大量的僵尸进程,这是一
9、个可以使系统崩溃的潜在问题。因此,在使用并发服务器程序的代码框架时,需要注意以上两个问题。24.1.3 UDP 协议的 connect 函数应用 connect 函数用于在两个套接字之间建立一个连接。当连接建立好后,通信两端的地址和端口号就已经确定了,数据可以通过该连接进行传输,这时传输的数据包不用再由用户提供通信地址。因此这种函数多用在面向连接的通信协议,例如 TCP 协议。UDP 协议属于无连接网络通信协议,connect 函数也可以应用于基于 UDP 协议的通信。Linux C 程序设计大全 744 因为 UDP 协议虽然属于无连接通信协议,但是如果通信时数据报的目的总是一个一个固定的通
10、信地址时,其通信两端的地址事实上总是不变的。因此在系统内部执行提取通信目的地端的地址和端口的操作就显得意义不大了。?说明:这时使用 connect 函数为通信的两端建立一个连接反而使通信变得更有效率。下面实例演示使用 connect 函数为一个基于 UDP 协议的网络程序建立连接。调用connect 函数建立起一个连接之后,该套接字就可以使用基于面向连接的协议的读写函数了(例如,send,write 函数等)。这些函数一般用于基于面向连接协议的套接字,因此不包含任何地址信息。执行流程如图 24-3 所示。图 24-3 使用 connect 函数的无连接客户端执行流程 第 24 章 网络编程进阶
11、 745其程序代码如下:程序清单 24-1 client.c 一个使用 connect 函数与服务器建立连接的客户端程序#include#include#include#include#include#include#include#include#include#include iolib.h/*添加用户自己的 I/O 函数库*/#define MAX_LINE 80 int main(int argc,char*argv)struct sockaddr_in sin;int port=8000;/*端口号,使用 8000 端口*/int s_fd;char bufMAX_LINE;char
12、 str=test;char addr_pINET_ADDRSTRLEN;int n;if(argc=2)str=atgv1;memset(&sin,sizeof(pin),0);/*设置地址结构*/sin.sin_family=AF_INET;inet_pton(AF_INET,127.0.0.1,&sin.sin_addr);sin.sin_port=htons(port);s_fd=socket(AF_INET,SOCK_DGRAM,0);/*建立一个使用 UDP 协议的套接字*/if(s_fd=-1)perror(fail to create socket);exit(1);/*使用
13、connect 函数与服务器进行连接,连接之后就相当于使用一个 TCP 的套接字进行通 信了*/n=connect(s_fd,(struct sockaddr*)&sin,sizeof(sin);if(n=-1)perror(fail to connect);exit(1);else printf(connection has been establishedn);Linux C 程序设计大全 746 n=my_write(s_fd,str,strlen(str)+1);/*发送字符串,该串包含0结 束符*/if(n=-1)/*写操作失败,程序退出*/exit(1);/*读取服务器程序发回的串
14、,由于是同一台主机通信,不存在延时的问题 *但是在真正的网络环境中,要处理读操作的延时问题 */n=my_read(s_fd,buf,MAX_LINE);if(n=-1)/*读失败,退出程序*/exit(1);printf(recive from server:%sn,buf);/*打印该串*/if(close(s_fd)=-1)/*关闭套接字,结束通信*/perror(fail to close);exit(1);return 0;修改之后,使用程序清单 23-11 所示的 server.c 程序作为服务器端程序,并且使用执行makefile 文件重新编译该工程,之后得到两个程序。打开两个终
15、端分别运行程序之后得到和该工程相同的结果。由此可知,使用 UDP 协议创建的套接字也可以使用面向连接的方法进行通信。?注意:如果其通信数据量很大,这种方法的效率将高于使用传统的无连接通信方法。24.2 多路选择 I/O 多路选择 I/O 提供另一种处理 I/O 的方法。这种方法比传统的 I/O 方法更好,更具有效率。多路选择是一种充分利用系统时间的典型。在网络应用中,这种方法更是被发挥的淋漓尽致。本章将评细介绍多路选择 I/O。24.2.1 多路选择 I/O 的概念 当用户需要从网络设备上读取数据时,会发生的读操作一般分为两步。?等待数据准备好。等待数据的到达,并且将其复制到内核的缓冲区,该缓
16、冲区在系统态。?复制数据。将数据从内核缓冲区中复制到用户指定的缓冲区中,该复制是跨越权级的(由系统态到用户态)。一般的读操作形式为:第 24 章 网络编程进阶 747int nbytes=read(sfd,buf,MAX);如果需要的数据没有准备好,例如,数据尚未到达时,read 函数将发生阻塞,直到所需要的数据到达,read 函数才将其复制到用户指定的缓冲区,并且返回。如果数据一直未到,那么 read 函数将一直阻塞下去,该进程也会陷入僵死状态。这种 I/O 模型称为阻塞 I/O,其模型示意如图 24-4 所示。图 24-4 阻塞 I/O 示意图?注意:为了防止 I/O 阻塞使进程陷入僵死状
17、态,可以使用多路选择 I/O。这种方法的思想是先构造一张需要读取数据的设备(通常是文件描述符,因为 Linux不区分文件与设备)的表,调用一个函数轮询这个表中的设备,直到有一个设备可以读写,该函数才返回,如图 24-5 所示为多路选择 I/O 的模型。图 24-5 多路选择 I/O 示意图 多路选择 I/O 需要使用两个系统调用,一个负责检查并返回可用设备的文件描述符,一个负责对该文件描述符进行读写。24.2.2 实现多路选择 I/O Linux 环境下使用 select 函数实现多路选择 I/O,其函数原型如下:Linux C 程序设计大全 748#include int select(in
18、t maxfdp1,fd_set*restrict readfds,fd_set*restrict writefds,fd_set*restrict exceptfds,struct timeval*restrict tvptr);select 的第 1 个参数表示所关心状态的描述符的个数,其正确的解释是最大描述符 加 1。如果 maxfdp1 的值是 2,那么就表示用户关心的描述符数为 2;最大的文件描述符为1 时,描述符为 0 和 1 的设备(文件)都会被该函数查询,而描述符的值大于 1 的设备则不关心其状态。用户也可以将 maxfdp1 的值设置为 FD_SETSIZE,这个宏定义在 s
19、ys/select.h文件中,其值为 1024,因为一个进程最多可以拥有 1024 个文件描述符。使用该值表示关心所有的设备描述符(01023),不过这个值太大了,一般应用程序不会使用那么多的描述符。?说明:接下来的三个参数很特殊,不仅因为其使用了一种新的数据类型 fd_set,而且因为这些参数既是参数又是 select 函数运行的结果之一。首先来解释 fd_set 这种数据类型。fd_set 数据类型本质上是一个位向量,是一个无符号整数。其每一位代表一个设备的状态,如果是“1”则表示被设置,如果是“0”则表示没有被设置。Linux 环境提供专门的函数对这种位向量进行操作,其用法如下:#inc
20、lude int FD_ISSET(int fd,fd_set*fdset);void FD_CLR(int fd,fd_set*fdset);void FD_SET(int fd,fd_set*fdset);void FD_ZERO(fd_set*fdset);FD_ISSET 函数用来测试指定的位是否被设置。参数 fd 表示需要测试的位。参数 fdset则表示需要测试的位向量,其每一位代表一个设备的状态。如果该位被设置,则返回非零值,否则返回 0。FD_CLR 函数清除位向量指定的位,FD_SET 函数设置位向量指定的位,而 FD_ZERO函数则清空位向量所有的位。参数 fd 表示指定的位
21、,参数 fd_set 表示需要操作的位向量。这三个参数都没有返回值。下面实例演示了如何在位向量中设置标准输出(描述符为 1)文件的状态。(1)在 vi 编辑器中编辑该程序如下:程序清单 24-2 fdset.c 位向量操作演示#include#include int main(void)fd_set fdset;FD_ZERO(fdset);/*清空 fdset*/第 24 章 网络编程进阶 749 FD_SET(STDOUT_FILENO,fdset);/*设置 stdout 所对应的位*/if(FD_ISSET(STDOUT_FILENO,fdset)!=0)/*测试该位*/printf(
22、stdout has been settedn);else printf(stdout has not been settedn)FD_CLR(STDOUT_FILENO,fdset);/*清空 stdout 所对应的位*if(FD_ISSET(STDOUT_FILENO,fdset)!=0)/*在此测试该位*/printf(stdout has been settedn);else printf(stdout has not been settedn)return 0;(2)在 shell 中编译该程序如下:$gcc fdset.c o fdset (3)在 shell 中运行该程序如下:$
23、./fdset stdout has been setted stdout has not been setted 清楚了 fd_set 数据类型的本质之后再来看 select 函数的 3 个参数。readfds、writefds 和exceptfds 分别表示用户关心的可读、可写和异常的各个描述符。这 3 个参数是三个位向量,每一位对应一个文件描述符的状态,如图 24-6 所示。图 24-6 文件向量示意图 这三个参数可以是 NULL,NULL 表示对该状态不关心。例如,如果关心描述符 02的读写状态,忽略其异常状态,调用 select 函数如下:Linux C 程序设计大全 750 fd_
24、set rfds,wfds;struct timeval t;select(3,&rfds,&wfds,NULL,&t);select函数的第三个参数表示用户期望等待的时间,如果超过tvptr所指定的时间,select函数将返回 0,表示没有任何一个设备准备好;如果在等待时间以内任一用户关心的设备准备完毕,则 select 函数提前返回。如果 tvptr=NULL 表示一直等待设备就绪,这是一种死等的办法。?说明:以上两种等待都可以被信号所中断,select 返回值为-1,errno 被设置为 INTER。如果 tvptr 结构中的 tv_sec 和 tv_usec 都设置为 0 时,sele
25、ct 函数将不会等待,检查完指定的设备后就立即返回结果。因此可以使用这种用法实现轮询设备。代码如下:int max=3;fd_set rfds,wfds;struct timeval t;FD_ZERO(&rfds);/*清空位向量*/FD_ZERO(&rfds);FD_SET(0,&rfds);/*检查 stdin 的可读性*/FD_SET(1,&wfds);/*检查 stdout 的可写性*/FD_SET(2,&wfds);/*检查 stderr 的可写性*/t.tv_sec=0;/*等待时间设置为 0*/t.tv_usec=0;while(select(max,&rfds,&wfds,N
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第24章 网络编程进阶 24 网络 编程 进阶
![提示](https://www.taowenge.com/images/bang_tan.gif)
限制150内