Linux网络编程 多进程多线程并发服务器

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

进程的状态



就绪:进程准备运行,但还没有得到CPU。根 据调度算法决定将CPU分配给某个进程。 运行:进程正在运行(即占用CPU) 等待:进程正在等待某个事件的发生,如:输 入\输出的完成、子进程的退出、进程睡眠期 的结束。 被交换:进程准备运行,但可能由于需要更多 的内存而当前却没有足够的可用内存,因此被 暂时存放在硬盘(交换空间)上。 僵死:一个已经终止,但是其父进程尚未对其 进行善后处理的进程。


如果没有终止的子进程,但是有一个或多个正在执 行的子进程,则该函数将堵塞,直到有一个子进程 终止或者wait被信号中断时,wait返回。 当调用该系统调用时,如果有一个子进程已经终止, 则该系统调用立即返回,并释放子进程所有资源。
获取子进程终止信息
使用wait()函数可能会出现一个问题
SIGCHLD SIGCHLD SIGCHLD
多进程并发服务器模板
…… int main(void) { int listenfd, connfd; pid_t pid; int BACKLOG = 5; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror(“Create socket failed.”); exit(1); } bind(listenfd, …); listen(listenfd, BACKLOG); while(1) { if ((connfd = accept(sockfd, NULL, NULL)) == -1) { perror(“Accept error.”); exit(1); }
#include<stdlib.h> #include<stdio.h> main() { int i, sum; sum = 0; for(i =0;i<=2;i++) { sum = sum +i; printf(“i=%d\n",i); } printf("sum = %d\n",sum); }
运行的结果:
终止进程

进程的终止存在两个可能:

父进程先于子进程终止(init进程领养) 子进程先于主进程终止


对于后者,系统内核为子进程保留一定的状态 信息:进程ID、终止状态、CPU时间等;当 父进程调用wait或waitpid函数时,获取这些 信息;(什么叫“僵尸进程”?) 当子进程正常或异常终止时,系统内核向其父 进程发送SIGCHLD信号;缺省情况下,父进 程忽略该信号,或者提供一个该信号发生时即 被调用的函数。
int main(void) { pid_t pid; int status; if ((pid = fork()) == 0) { //sleep(2); printf("child running.\n"); printf("child sleeping.\n"); // sleep(2); printf("child dead.\n"); exit(0); }else if ( pid > 0) { printf("parent running .\n"); printf("parent exit\n"); exit(0); } else { printf("fork error.\n"); exit(1); } }
终止进程(续)
#include <stdlib.h> void exit(int status);

本函数终止调用进程。关闭所有子进程打开的描述 符,向父进程发送SIGCHLD信号,并返回状态。
获取子进程终止信息
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc); 返回:终止子进程的ID-成功;-1-出错;stat_loc存储 子进程的终止状态(一个整数);
int main(void) { pid_t pid; int status; if ((pid = vfork()) == 0){ sleep(2); printf("child running.\n"); printf("child sleeping.\n"); sleep(2); printf("child dead.\n"); exit(0); }else if ( pid > 0) { printf("parent running .\n"); printf("parent exit\n"); exit(0); } else { printf("fork error.\n"); exit(1); } }

当pid=-1,option=0时,该函数等同于wait,否则 由参数pid和option共同决定函数行为,其中pid参 数意义如下:


-1:要求知道任何一个子进程的返回状态(等待第一 个终止的子进程); >0:要求知道进程号为pid的子进程的状态; <-1:要求知道进程号为pid的绝对值的子进程的终止 状态
服务器父进程
服务器子进程
FIN
服务器子进程
FIN
服务器子进程
FIN
客户 由于linux信号不排队,在SIGCHLD信号同时到来后,信 号处理程序中调用了wait函数,其只执行一次,这样将留 下2个僵尸进程。可以使用waitpid函数解决这个问题。
获取子进程终止信息
pid_t waitpid(pid_t pid, int *stat_loc, int options); 返回:终止子进程的ID-成功;-1-出错;stat_loc存储 子进程的终止状态;
创建进程(cont.)
#include <sys/types.h> #include <unistd.h> pid_t vfork(void);


该系统调用基本上与fork相同,在BSD3.0中开始出现,主 要为了解决fork昂贵的开销。 两者的基本区别在于当使用vfork()创建新进程时,父进程 将被暂时阻塞,而子进程则可以借用父进程的地址空间。这 个奇特状态将持续直到子进程退出,至此父进程才继续执行。 因此,子进程需小心处理共享变量。
pid_t pid; int stat; while((pid=waitpid(-1,&stat,WNOHANG))>0) printf(“child %d terminated\n”,pid);
waitpid函数用法
pid_t pid; if ((pid=fork()) > 0) /* parent process */ { int child_status; waitpid(pid, &child_status, 0); }else if ( pid == 0 ) { /* child process */ exit(0); }else { /* fork error */ printf(“fork error.\n”); exit(1); }
并发服务器
并发服务器
目录 服务器分类技术 进程与线程 多进程服务器 多线程服务器
服务器分类

按连接类型分类

面向连接的服务器(如tcp) 面向无连接的服务器(如udp) 迭代服务器 并发服务器

按处理方式分类

Βιβλιοθήκη Baidu
迭代服务器 vs. 并发服务器
绑定地址
监听连接 服务器主进程 绑定地址 服务器子进程 监听连接 接收连接 创建子进程 关闭监听套接字
else if ( pid > 0) { printf("parent running .\n"); waitpid(pid, &status,0); printf("parent is running\n"); printf("parent exit\n"); } else { printf("fork error.\n"); exit(1); }

Options最常用的选项是WNOHANG,它通 知内核在没有已终止进程时不要堵塞。
获取子进程终止信息(cont.)

调用wait或waitpid函数时,正常情况下, 可能会有以下几种情况: 阻塞(如果其所有子进程都还在运行); 获得子进程的终止状态并立即返回(如果 一个子进程已终止,正等待父进程存取其 终止状态); 出错立即返回(如果它没有任何子进程)
一点说明

从以上模板看出,产生新的子进程后,父进 程要关闭连接套接字,而子进程要关闭监听 套接字,主要原因是:


关闭不需要的套接字可节省系统资源,同时可避 免父子进程共享这些套接字可能带来的不可预计 的后果; 另一个更重要的原因,是为了正确地关闭连接。 和文件描述符一样,每个套接字描述符都有一个 “引用计数”。当fork函数返回后,listenfd和 connfd的引用计数变为2,而系统只有在某描述符 的“引用计数”为0时,才真正关闭该描述符。
程序例子: #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(void) { pid_t pid; int status; if ((pid = fork()) == 0) { sleep(1); printf("child running.\n"); printf("child sleeping.\n"); printf("child dead.\n"); exit(0); }
listenfd connfd
fork()函数
connect()函数
服务器(子进程)
listenfd connfd
客户/服务器状态图(调用fork函数后)
多进程并发服务器状态图 (cont.) 客户 服务器(父进程)
listenfd
connect()函数
服务器(子进程) connfd 客户/服务器状态图(父进程关闭连接套接字, 子进程关闭监听套接字)
接收连接
处理连接
接收请求
处理连接
关闭连接套接字 终止子进程
处理请求
返回响应
断开连接
关闭连接套接字
TCP迭代服务器
TCP并发服务器
“进程”基本概念



进程定义了一个计算的基本单元,可以认为 是一个程序的一次运行。它是一个动态实体, 是独立的任务。它拥有独立的地址空间、执 行堆栈、文件描述符等。 每个进程拥有独立的地址空间,进程间正常 情况下,互不影响,一个进程的崩溃不会造 成其他进程的崩溃。 当进程间共享某一资源时,需注意两个问题: 同步问题和通信问题。
多进程并发服务器模板
if((pid = fork() ) > 0){ /*parent process */ close(connfd); ……. continue; } else if (pid == 0){ /*child process */ close(lisetenfd); ……. exit(0); } else{ printf(“fork error\n”); exit(1); } } }
创建进程
#include <sys/types.h> #include <unistd.h> pid_t fork(void) 返回:父进程中返回子进程的进程ID, 子进程返回0, -1-出错 fork后,子进程和父进程继续执行fork()函数后的指令。 子进程是父进程的副本。子进程拥有父进程的数据空间、 堆栈的副本。但父、子进程并不共享这些存储空间部分。 如果代码段是只读的,则父子进程共享代码段。如果父子 进程同时对同一文件描述字操作,而又没有任何形式的同 步,则会出现混乱的状况; 父进程中调用fork之前打开的所有描述字在函数fork返回 之后子进程会得到一个副本。fork后,父子进程均需要将 自己不使用的描述字关闭,有两方面的原因:(1)以免 出现不同步的情况;(2)最后能正常关闭描述字
}
多进程并发服务器状态图
服务器 listenfd 客户
连接请求
connect()函数
客户/服务器状态图(调用accept函数时)
多进程并发服务器状态图 (cont.)
服务器 listenfd connfd 客户 connect()函数
客户/服务器状态图(调用accept函数后)
多进程并发服务器状态图 (cont.) 客户 服务器(父进程)
相关文档
最新文档