340 likes | 491 Views
服务器并发处理. 主讲:孟宁 电话:0512-68839302 E-mail:mengning@ustc.edu.cn 主页:http://staff.ustc.edu.cn/~mengning 地址:苏州工业园区独墅湖高等教育区仁爱路166号明德楼A302室. 2010年12月. 服务器并发处理. 并发处理的理解 5种I/O模型 循环服务器 多进程(线程)的并发 I/O复用方式的并发. 并发的概念. 并发有真正的并发( 并行: Parallelism )和表面上的并发( 并发 : Concurrency )(一般采用分时机制). 并行模型. 并发模型.
E N D
服务器并发处理 主讲:孟宁 电话:0512-68839302 E-mail:mengning@ustc.edu.cn 主页:http://staff.ustc.edu.cn/~mengning 地址:苏州工业园区独墅湖高等教育区仁爱路166号明德楼A302室 2010年12月
服务器并发处理 • 并发处理的理解 • 5种I/O模型 • 循环服务器 • 多进程(线程)的并发 • I/O复用方式的并发
并发的概念 • 并发有真正的并发(并行: Parallelism)和表面上的并发(并发:Concurrency)(一般采用分时机制) 并行模型 并发模型
网络中的并发 • 单个网络各个机器之间许多成对进程好像独立使用网络资源(通道,机器等) • 一个计算机系统中存在并发(分时) • 一组机器上所有的客户之间存在并发 互联网 C1 C3 C2 C4 C5
服务器中的并发 • 单个服务器必须并发处理多个传入请求 • 并发服务器可以让多个远程用户同时使用服务,实现起来比较复杂 C1 互联网 服务器 C2 C3 C4
操作系统的并发功能 • 多进程操作系统 • 进程的概念:进程定义了一个计算的基本单元,它是一个执行某一个特定程序的实体,它拥有独立的地址空间、执行堆栈、文件描述符等。 • 多线程细化了并发处理的粒度,也给编程带来了新的困难 - 维护函数的可重入性
创建进程 #include <sys/types.h> #include <unistd.h> pid_t fork(void) 返回:父进程中返回子进程的进程ID, 子进程返回0,-1-出错(注意为什么?) • fork后,父子进程共享数据空间、代码空间、堆栈、所有的文件描述字;
程序的并发版本fork #include <unistd.h> #include <stdio.h> #include <sys/types.h> int main () { pid_t pid; pid=fork(); if (pid < 0) printf("error in fork!"); else if (pid == 0) printf("i am the child process, my process id is %d\n",getpid()); else printf("i am the parent process, my process id is %d\n",getpid()); }
I/O复用-select/poll • 某些OS允许单个线程控制并发的输入输出操作 • 使用select询问操作系统I/O设备的情况 • 例子: • 用户从TCP接收数据并且显示,还允许用户从键盘输入命令控制显示。 • 两个输入:TCP, 键盘 • 总是等着一个输入,会阻塞 • 使用select/poll询问输入是否就绪
select Function #include <sys/select.h> #include <sys/time.h> int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); Returns: positive count of ready descriptors, 0 on timeout, –1 on error void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */ void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */ void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */ int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */
poll Function #include <poll.h> int poll (struct pollfd *fdarray, unsigned long nfds, int timeout); Returns: count of ready descriptors, 0 on timeout, –1 on error struct pollfd { int fd; /* descriptor to check */ short events; /* events of interest on fd */ short revents; /* events that occurred on fd */ };
服务器软件设计概述 • 无连接和面向连接的服务器访问 • 无状态和有状态的服务器应用 • 循环和并发的服务器的实现 • 简单服务器的算法: • 创建套接字 • 绑定到一个熟知端口 • 期望在这个端口上接收请求 • 进入无限循环,接受客户请求并应答 • 只适用于最简单的服务…
并发服务器和循环服务器 • 循环服务器:一个时刻只处理一个请求 • 并发服务器:一个时刻可以处理多请求 • 多数只提供表面并发:执行多个线程,每个线程处理一个请求 • 使用单线程的可能性:计算量小,主要是异步I/O, 便于同时使用多个通信信道 • 并发处理多个请求,而不是指下层是否使用了多个并发线程 • 循环服务器容易构建,但是性能差;并发服务器难以构建和设计,但是性能好
四种基本类型的服务器 • 循环的或者并发的 • 使用面向连接的或者无连接的传输
将套接字置于被动模式 • 调用listen:将套接字置于被动模式 • 一个参数指明套接字内部的请求队列长度 • 请求队列保存一组TCP传入连接请求,来自客户,都向这个服务器请求一个连接 • 接收连接并使用这些连接 • 调用accept:获得下一个传入连接请求 • 返回新的连接的套接字的描述符 • 服务器接收连接,使用read获得来自客户的应用协议,使用write发回应答。 • 服务器结束连接,使用close释放套接字
循环服务器概述 • 循环服务器的类型 • 使用无连接传输,常见 • 使用面向连接的服务 • 循环无连接服务器 • 使用无连接的循环服务器 • chatsys socketwraper hello回射服务的例子 • 循环面向连接的服务器 • 循环的面向连接的服务器 • daytime服务的例子 • 特点: • 每次处理时间都很少 • 服务器实现简单 函数的具体使用方法请大家查阅相关资料。
无连接循环服务器的算法 • 循环服务器的设计,编程,排错,修改很容易。往往使用无连接的协议。 • 循环服务器对于小的处理时间的服务工作很好。 • 无连接服务器算法如下: • 1、创建套接字并将其绑定到所提供服务的熟知端口上; • 2、重复读取来自客户的请求,构造响应,按照应用协议向客户发回响应。
serverfd=socket(AF_INET,SOCK_DGRAM,0); bzero(&servaddr,sizeof(struct sockaddr_in)); servaddr.sin_family=AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(SERVER_PORT); bind(serverfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr_in)); while(1) { recvfrom(serverfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&clientaddr,&addrlen); sendto(serverfd,msg,i4PduSize,0,(struct sockaddr*)&clientaddr,sizeof(struct sockaddr_in)); }
并发服务器的算法 • 给多个客户提供快速响应时间需要使用并发服务器 • 有相当的I/O时间的响应 • 可以部分重叠地使用处理器和外设 • 各个请求所要求的处理时间变化很大 • 时间分片允许单个处理那些只要求少量处理的请求尽快完成 • 服务器运行在具有多个处理器的计算机上 • 不同的处理器处理不同的请求 • 并发服务器通过使处理和I/O部分重叠来达到高性能。
主线程和从线程 • 尽管可以使用一个单线程实现并发服务器,但是大多数使用多线程: • 主线程最先开始执行在熟知的端口上打开一个套接字,等待一个请求,并为每个请求创建一个从线程(可能在一个新进程中) • 主线程不与客户直接通信,每个从线程处理一个客户的通信。 • 从线程构成响应并发送给客户后,这个从线程便退出
并发的无连接的服务器的算法 • 最简单的算法: • 主1、创建套接字并将其绑定到所提供服务的熟知地址上。让该套接字保持为未连接的 • 主2、反复调用recvfrom接收来自客户的下一个请求,创建一个新的从线程来处理响应 • 从1、从来自主进程的特定请求以及到该套接字的访问开始 • 从2、根据应用协议构造应答,并用sendto将该应答发回给客户 • 从3、退出(即:从线程处理完一个请求后就终止) • 由于创建进程或者线程是昂贵的,因此只有很少的无连接服务器采用并发实现
pthread_t pid; serverfd=socket(AF_INET,SOCK_DGRAM,0); bzero(&servaddr,sizeof(struct sockaddr_in)); servaddr.sin_family=AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(SERVER_PORT); bind(serverfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr_in)); while(1) { recvfrom(serverfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&clientaddr,&addrlen); if(pthread_create(&pid, NULL,(void*)sub_task, (void*)&serverfd) != OK) perror("pthread_create"); } } void * sub_task(void * fd) { sendto }
并发的面向连接服务器算法 • 面向连接的服务器在多个连接之间实现并发(不是在各个请求之间) • 主1、创建套接字并将其绑定到所提供服务的熟知地址上。让该套接字保持为面向连接 • 主2、将该端口设置为被动模式 • 主3、反复调用accept以便接收来自客户的下一个连接请求,并创建新的从线程或者进程来处理响应 • 从1、由主线程传递来的连接请求开始 • 从2、用该连接与客户进行交互;读取请求并发回响应 • 从3、关闭连接并退出
pthread_t pid; int listenfd, connfd; listenfd = Socket( ... ); /* fill in sockaddr_in{} with server's well-known port */ Bind(listenfd, ... ); Listen(listenfd, LISTENQ); for ( ; ; ) { connfd = Accept (listenfd, ... ); /* probably blocks */ if(pthread_create(&pid, NULL,(void*)sub_task, (void*)&listenfd) != OK) perror("pthread_create"); } Close(connfd); /* parent closes connected socket */ } void * sub_task(void * listenfd) { doit(listenfd); /* process the request */ Close(listenfd); /* done with this client */ }
各个服务器使用的场合 • 循环的和并发的: • 如果循环方案产生的响应时间对应用来说足够,就可以使用循环;否则需要并发 • 真正的和表面上的并发性: • 线程或切换环境的开销大,服务器需要在多个连接之间共享或者交换数据,用单线程; • 使用线程开销不大或者要得到最大并发性,使用多进程 • 面向连接的和无连接的: • 应用协议处理了可靠性问题,或者应用在局域网环境内,使用无连接的传输。
服务器死锁 • 循环的面向连接的服务器: • 某个客户和服务器建立一个连接,客户不再发送请求,服务器无法使用recv得到请求,服务器将在这里阻塞 • 客户不能正常工作,不处理服务器的响应,将导致服务器的外发存储数据区填满阻塞 • 可能阻塞的系统调用会产生死锁 • 单线程的服务器会被阻塞死锁
服务器类型小结 • 循环无连接服务器 • 请求要求处理少,无状态的,最常见的 • 循环的面向连接服务器 • 要求可靠传输的,对请求要求处理少的服务,较常见 • 并发的,无连接的服务器 • 不常见,为每个请求创建一个新线程或进程 • 并发的面向连接的服务器 • 最一般的。可靠传输,并发处理多个请求 • 多线程(进程)