linux程序设计——多客户(第十五章)

15.4    多客户

到目前为止,本章一直介绍的是,如果用套接字来实现本地的和跨网络的客户/服务器系统.一旦连接建立,套接字连接的行为就类似于打开的底层文件描述符,而且在很多方面类似于双向管道.

现在考虑有多个客户同时连接一个服务器的情况.服务器程序在接受来自客户的一个新连接时,会创建出一个新的套接字,而原先的监听套接字将被保留以继续监听以后的连接.如果服务器不能立刻接受后来的连接,它们将被放到队列中以等待处理.

原先的套接字仍然可用并且套接字的行为就像文件描述符,这一事实提供了一种同时服务多个客户的方法.如果服务器调用fork为自己创建第二份副本,打开的套接字就将被新的子进程所继承.新的子进程可以和连接的客户进行通信,而主服务器进程可以继续接受以后的客户连接.这些改动对服务器程序来说是非常容易的.

因为创建子进程,但并不等待它们的完成,所有必须安排服务器忽略SIGCHLD信号以避免出现僵尸进程.

程序    可以同时服务多个客户的服务器

编写程序server4.c

/*************************************************************************
 > File Name:    server4.c
 > Description:  server4.c
 > Author:       Liubingbing
 > Created Time: 2015年07月25日 星期六 17时22分52秒
 > Other:        server4.c
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>

int main()
{
	int server_sockfd, client_sockfd;
	int server_len, client_len;
	struct sockaddr_in server_address;
	struct sockaddr_in client_address;
	/* socket函数创建套接字 */
	server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

	/* 套接字地址由结构sockaddr_in来指定,一个AF_INET套接字由它的域,IP地址和端口号完全确定 */
	server_address.sin_family = AF_INET;
	server_address.sin_addr.s_addr = htonl(INADDR_ANY);
	server_address.sin_port = htons(9734);
	server_len = sizeof(server_address);

	/* bind函数给套接字命名,使AF_INET套接字关联到一个IP端口号
	 * 此外bind调用需要就爱那个一个特定的地址结构转换为指向通用地址类型(struct sockaddr *) */
	bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

	/* 创建一个连接队列,忽略子进程的退出细节,等待客户的到来 */
	listen(server_sockfd, 5);

	signal(SIGCHLD, SIG_IGN);

	while (1) {
		char ch;
		printf("server waiting\n");
		/* 接受连接 */
		client_len = sizeof(client_address);
		client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
		/* 通过fork调用为这个客户创建一个子进程,然后测试在父进程还是子进程 */
		if (fork() == 0) {
			read(client_sockfd, &ch, 1);
			sleep(5);
			ch++;
			write(client_sockfd, &ch, 1);
			close(client_sockfd);
			exit(0);
		}
		else {
			close(client_sockfd);
		}
	}
}

在处理客户请求时插入的5秒延迟是为了模拟服务器的计算时间或数据库访问时间.如果在前面的服务器中这样做,client3的每次运行都将花费5秒钟的时间,而新服务器可以同时处理多个client3程序,所花费的总时间将只有5秒钟多一点.如下所示:

程序解析

服务器程序现在将创建一个新的子进程来处理每个客户,所以将看到好几个服务器在等待消息,而主进程将继续等待新的连接.server4进程正在等待新的客户,而3个client进程正在由3个服务器的子进程进行服务.在经过5秒的暂停后,所有的客户都得到了它们的结果并结束运行.服务器的子进程也都退出,只留下主服务器进程在运行.

服务器程序用fork函数处理多个客户,但在数据库应用程序中,这可能不是最佳的解决方案,因为服务器程序可能会相当大,而且在数据库访问方面还存在着需要协调多个服务器副本的问题.事实上,真正需要的是,如果让单个服务器进程在不阻塞,不等待客户请求到达的前提下处理多个客户.这个问题的解决方案设计如何同时处理多个打开的文件描述符,并且它不仅仅局限于套接字,请看下一节的select系统调用.

15.4.1    select系统调用

在编写linux应用程序时,经常会遇到需要检查好几个输入的状态才能确定下一步行动的情况.例如,像终端仿真器这样的通信程序,需要有效地同时读取键盘和串行口.如果是在一个单用户系统中,运行一个"忙等待"循环还是可以接受的,它不停地扫描输入设置看是否有数据,如果有数据到达就读取它,但这种做法很消耗CPU的时间.

select系统调用允许程序同时在多个底层文件描述符上等待输入的到达(或输出的完成).这意味着终端仿真程序可以一直阻塞到有事情可做为止.类似地,服务器也可以通过同时在多个打开的套接字上等待请求到来的方法来处理多个客户.

select函数对数据结构fd_set进行操作,它是由打开的文件描述符构成的集合,有一组定义好的宏可以用来控制这些集合:

#include <sys/types.h>
#include <sys/time.h>
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ISSET(int fd, fd_set *fdset);

FD_ZERO用于将fd_set初始化为空集合.

FD_SET和FD_CLR分别用于在集合中设置和清除由参数fd传递的文件描述符.

如果FD_ISSET宏中由参数fd指向的文件描述符是由参数fdset指向的fd_set集合中的一个元素,FD_ISSET将返回非零值.

fd_set结构中可以容纳的文件描述符的最大数目由常量FD_SETSIZE指定.

select函数还可以用一个超时值来防止无限期的阻塞,这个超时值是由一个timeval结构给出,这个结构定义在文件件sys/time.h中,它由一下几个成员组成:

struct timeval {
    time_t tv_sec;    /* seconds */
    long tv_usec;    /* microseconds */
};

类型time_t在头文件sys/types.h中被定义为一个整数类型.

select系统调用的原型如下所示:

#include <sys/types.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

select调用用于测试文件描述符集合中,是否有一个文件描述符已处于可读状态或可写状态或错误状态,它将阻塞以等待某个文件描述符进入上述这些状态.

参数nfds指定需要测试的文件描述符的数目,测试的描述符访问从0到nfds-1.3个描述符集合都可以被设为空指针,着表示不执行相应的测试.

select函数会发生以下情况时返回:readfds集合中描述符可读,writefds集合中有描述符可写或errorfds集合中有描述符遇到错误条件,如果这3种情况都没有发生,select将在timeout指定的超时时间经过后返回.如果timeout参数是一个空指针并且套接字上也没有任何活动,这个调用将一直阻塞下一.

当select返回时,描述符集合将被修改以指示哪些描述符正处于可读,可写或者错误的状态.可以用FD_ISSET对描述符进行测试,来找到需要注意的描述符.可以修改timeout值来表明剩余的超时时间,但这并不是在X/Open规范中定义的行为.如果select是因为超时而返回的话,所有描述符集合都将被清空.

select调用返回状态发生变化的描述符总数.失败时它将返回-1并设置errno来描述错误.可能出现的错误有:EBADF(无效的描述符),EINTR(因中断而返回),EINVAL(nfs或timeout取值错误)

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 00:12:32

linux程序设计——多客户(第十五章)的相关文章

linux程序设计——数据报(第十五章)

15.5    数据报 在本章中,重点介绍了如何编写与客户之间维持连接的应用程序.使用面向连接的TCP套接字来完成这一工作.但在某些情况下,在程序中花费时间来建立和维持一个套接字连接是不必要的. 早先,在程序getdate.c中所使用的daytime服务就是一个很好的例子,首先创建一个套接字,然后建立连接,读取一个响应,读取一个响应,最后关闭连接.在这一过程中,使用了很多操作步骤,仅仅为了获取一个日期. daytime服务还可以用数据报通过UUDP来访问.为了访问它,发送一个数据报给该服务,然后

【linux高级程序设计】(第十五章)UDP网络编程应用 2

UDP广播通信 单播:一对一,TCP和UDP均可完成 广播:只能UDP完成.广播时发送方只发送一个数据包,但是网络上的交换机默认转发广播数据包到所有端口.路由器默认不转发任何广播数据包.故广播在局域网范围内. 组播:只有UDP可以完成.发送消息到同一个组播组的主机.视频电话.视频会议多采用. 广播IP地址:主机号全1.网络号正常 广播MAC地址:全1,即FF:FF:FF:FF:FF:FF 广播数据帧格式 处理过程: 网卡驱动程序对比自己的MAC地址与目的MAC地址,发现是广播MAC地址,统一接收

【linux高级程序设计】(第十五章)UDP网络编程应用 4

socket信号驱动 为了使一个套接字能够使用信号驱动I/O,至少需要以下3步操作. 1.安装SIGIO信号 2.套接字的拥有者设定为当前进程.因为SIGIO信号只会送到socket拥有者进程. 通过fcntl的F_SETOWN 3.套接字必须被允许使用异步I/O. 通过fcntl的F_SETFL,设置为O_ASYNC 在UDP通信中,下面情况会产生SIGIO信号 在TCP通信中,下面情况会产生SIGIO信号 例子: 下面的代码好奇怪,说是UDP的,但是发送接收用的是send, recv 而且客

【linux高级程序设计】(第十五章)UDP网络编程应用 5

域名与IP信息解析 /etc/hosts 文件中有部分IP地址与域名主机名的信息 /etc/resolv.conf 里面有DNS服务器的IP地址 struct hostent { char *h_name; //主机的正式名字 char **h_aliases; //主机备选名称,以NULL结尾的链表 int h_addrtype; //返回地址的类型 有两种 AF_INET或 AF_INET6 int h_length; //地址长度 以字节为单位 char **h_addr_list; //

《Linux Device Drivers》第十五章 内存映射和DMA——note

简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现过程 讲述怎样跨越边界直接訪问用户空间的内存页 讲述了直接内存訪问(DMA)I/O操作,它使得外设具有直接訪问系统内存的能力 Linux的内存管理 地址类型 Linux是一个虚拟内存系统,这意味着用户程序所使用的地址与硬件使用的物理地址是不等同的 有了虚拟内存,在系统中执行的程序能够分配比物理内存很

linux程序设计——多线程(第十二章)

12.8    多线程 之前,总是让程序的主线程仅仅创建一个线程,这节将演示如何在同一个程序中创建多个线程,然后如何以不同于其启动顺序将它们合并在一起.此外,还演示多线程编程时容易出现的时序问题. 编写程序thread8.c /************************************************************************* > File Name: thread8.c > Description: thread8.c程序创建多个线程,然后以不同

JavaScript高级程序设计学习笔记第十五章--使用Canvas绘图

一.基本用法 1.要使用<canvas>元素,必须先设置其 width 和 height 属性,指定可以绘图的区域大小.能通过 CSS 为该元素添加样式,如果不添加任何样式或者不绘制任何图形,在页面中是看不到该元素的. 2.要在这块画布(canvas)上绘图,需要取得绘图上下文.而取得绘图上下文对象的引用,需要调用getContext()方法并传入上下文的名字.在使用<canvas>元素之前,首先要检测 getContext()方法是否存在,这一步非常重要.检测可以用如下方法进行:

第十五章 文件属性类的实现

                  第十五章    文件属性类的实现        根用户的权限也不能是无限大.必须考虑到保护用户的隐私!用户的文件内容.程序代码可设置为根用户也不能观看,只能是文件拥有者可以查看.修改.但根用户可以删除一切非根用户的文件.也可以查看用户的目录.所以,i_mode字符更改如下:      BU16 i_mode; // 描述文件的访问权限:文件的读.写.执行权限  // i_mode.15-13  ftype; 文件类型: 0-符号软连接文件, // 1-硬连接文

20190901 On Java8 第十五章 异常

第十五章 异常 要想创建健壮的系统,它的每一个构件都必须是健壮的. 异常概念 C++的异常处理机制基于 Ada,Java 中的异常处理则建立在 C++的基础之上(尽管看上去更像 Object Pascal). 基本异常 异常参数 所有标准异常类都有两个构造器:一个是无参构造器:另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器. Throwable 是异常类型的根类. 自定义异常 对异常来说,最重要的部分就是类名. 异常与记录日志 对于异常类来说,getMessage() 方法有点

第四十五章

第四十五章1 老子是在教导我们不追求完美吗? 大成若缺,其用不弊 最完美的东西,好似有残缺一样,但它的作用永远不会衰竭. 做事忘记结果,才能更坦然. 各位朋友大家好,今天我们接着来讲<道德经>,来听听老子老先生给我们带来什么样的人生启发.今天我们来到了第四十五章的讲解. 时光过的非常快,从我去年开始讲到现在,已经讲了二百多期,已经第四十五章了,<道德经>共八十一章,我们讲了一半出头了,这时间还是比较快的.有很多朋友听完以后觉得特别开心,心里不纠结了,我看到这样的留言我很开心.有人说