linux网络编程学习笔记之三 -----多进程并发服务端

首先是fork()函数。移步APUE 8.3.  比較清晰的解释能够參考http://blog.csdn.net/lingdxuyan/article/details/4993883http://www.oschina.net/question/195301_62902

补充一点是:fork返回后,原进程中的每一个文件或套接口描写叙述符的引用计数加1(相当于被多打开了一次),每调用一次close,引用计数减1,仅仅有当引用计数减到0时才会真正关闭该套接字。

可运行文件被linux运行的唯一方式就是调用exec,把当前进程映像替换成新的程序文件。从该程序的main開始运行。

linux典型做法是,fork之后開始exec。通经常使用COW技术。

关于僵尸进程

进程在exit()结束后。进程表中任然会保留如进程号、退出状态、执行时间等信息。尽管它已经放弃了内存空间,不能被调度。因为linux对进程数量有限制,过多的僵尸进程将会占用可用进程号。导致新的进程无法生成,所以必须及时地清除。当时我就纳闷了。。。那僵尸进程存在的意义何在?答:保留子进程退出的状态,等待父进程收尸时确定死因。(在父进程中调用各种宏:WEXITSTATUS(status)等)

处理方式:

1、父进程调用wait()或waitpid()来等待子进程结束。当然这会导致父进程挂起

2、假设父进程并不关心子进程何时结束,能够调用signal(SIGCHLD,SIG_IGN)来通知内核。内核会在子进程结束后自己主动回收。

注意:此方法不是可移植的。Stevent在UNP5.9中提到。这个处理并非POSIX标准。仅仅是在某些系统上可用

3、相同使用异步处理的方式。信号处理函数signal,安装handler来处理SIGCHLD信号。这样。父进程能够在handler中调用wait()进行回收。可是因为相同的信号不排队的原因,要注意处理同一时候提交的信号。即对同一时候终止的子进程做处理。UNP5.10具体地讨论了该问题。并给出了安全的方法。

4、通过给信号处理函数设置SA_NOCLDWAIT标志。使调用进程的子进程终止时不创建僵尸进程(APUE10.14)

signala.sa_handler = SIG_IGN;
signala.sa_flags = SA_NOCLDWAIT;
sigemptyset(&signala.sa_mask);
sigaction(SIGCHLD, &signala, NULL);

5、由于进程的特性。在其父进程退出后,子进程将被过继给init进程。假设子进程已是僵尸进程。init直接将其回收,由于它的管理者父进程已经不存在了,init不关心其死因。

因此能够採取连续两次fork()的方式,子进程在fork之后直接退出。由孙进程来进行处理,孙进程结束后,资源被init回收。但注意子进程的清理还得由父进程来完毕。

信号处理:

在此小结下。每一个内核的信号,操作系统都有默认的处理方式,比方前面的SIGCHLD,默认是忽略,因此不处理会留下僵尸。我们能够自定义函数来处理信号。注意SIGKILL和SIGSTOP是不能被捕获和忽略的。

信号处理函数是进程相关的,为全进程全部线程公用。

信号处理函数中。尽量不要使用printf这种不可重入函数(UNP11.18)

一个信号被信号处理函数响应,在处理过程中,该信号被屏蔽。标准的信号实现没有排队的功能,所以信号可能会被丢失,多个连续的信号来不及处理。假设通过sa_mask设置了信号集,集合中的信号也会被堵塞。

建立信号处理函数的POSIX方法是调用sigaction(),简单些直接调用signal()。尽管它不是POSIX函数,但多数平台用signal()来实现sigaction(),以实现向后兼容。

以下是一个简单的多进程服务端程序:

#include"simon_socket.h"

#define SERV_PORT 12345

extern pid_t waitpid(pid_t, int *, int);

void handler(int signo)
{
	pid_t pid;
	int status;
	while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
	{
		printf("Child process %d terminated\nThe WEXITSTATUS return code is %d \nThe WIFEXITED return code is %d\n", pid, WEXITSTATUS(status), WIFEXITED(status));
	}
}

int main()
{
	int sockfd, acfd;
	struct sockaddr_in client_addr;

	size_t sin_len = sizeof(client_addr);

	sockfd = init_tcp_psock(SERV_PORT);

	signal(SIGCHLD, handler);

/*   another method :
 *
	struct sigaction signala;
	signala.sa_handler = SIG_IGN;
	signala.sa_flags = SA_NOCLDWAIT;
	sigemptyset(&signala.sa_mask);
	sigaction(SIGCHLD, &signala, NULL);
*/
	while (1)
	{
		if ((acfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_len)) == -1)
		{
			perror("Accept request failed: ");
			return 1;
		}
		else
			printf("Get a connection from %s:%d !\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

		pid_t pid;
		if ((pid = fork()) > 0)
		{
			close(acfd);
			continue;
		}
		else if(pid == 0)
		{
			close(sockfd);
			process_client(acfd, &client_addr);
			close(acfd);
			exit(0);
		}
		else
		{
			perror("Fork error");
			exit(0);
		}
	}
	close(sockfd);
	return 0;
}

代码中一些调用函数的实现,移步我的github:https://github.com/simon-xia/lnp

时间: 2024-10-13 22:28:14

linux网络编程学习笔记之三 -----多进程并发服务端的相关文章

linux网络编程学习笔记之四 -----多线程并发服务端

相对于使用进程实现并发,用线程的实现更加轻量.每个线程都是独立的逻辑流.线程是CPU上独立调度运行的最小单位,而进程是资源分配的单位.当然这是在微内核的操作系统上说的,简言之这种操作系统的内核是只提供最基本的OS服务,更多参看点击打开链接 每个线程有它自己的线程上下文,包括一个唯一的线程ID(linux上实现为unsigned long),栈,栈指针,程序计数器.通用目的寄存器和条件码,还有自己的信号掩码和优先级.同一个进程里的线程共享这个进程的整个虚拟地址空间,包括可执行的程序文本.程序的全局

linux网络编程学习笔记之五 -----并发机制与线程?

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我仅仅是举几个样例作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.能够在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理须要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也非常easy,在主线程中定时,定

linux网络编程学习笔记之五 -----并发机制与线程池

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我只是举几个例子作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.可以在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理需要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也很简单,在主线程中定时,定时到期,

linux网络编程学习笔记之六 -----I/O多路复用服务端

多进程和多线程的目的是在于最大限度地利用CPU资源,当某个进程不需要占用太多CPU资源,而是需要I/O资源时,可以采用I/O多路复用,基本思路是让内核把进程挂起,直到有I/O事件发生时,再把控制返回给程序.这种事件驱动模型的高效之处在于,省去了进程和线程上下文切换的开销.整个程序运行在单一的进程上下文中,所有的逻辑流共享整个进程的地址空间.缺点是,编码复杂,而且随着每个逻辑流并发粒度的减小,编码复杂度会继续上升. I/O多路复用典型应用场合(摘自UNP6.1) select的模型就是这样一个实现

linux网络编程学习笔记之二 -----错误异常处理和各种碎碎(更新中)

errno 在unix系统中对大部分系统调用非正常返回时,通常返回值为-1,并设置全局变量errno(errno.h),如socket(), bind(), accept(), listen().erron存放一个正整数来保存上次出错的错误值. 对线程而言,每个线程都有专用的errno变量,不必考虑同步问题. strerror converts to English (Note: use strerror_r for thread safety) perror is simplified str

TCP/IP网络编程 学习笔记_11 --多进程服务器端

并发服务器 首先,我们来假设有下面这样两种类型的服务器:第一种,第一个连接请求的受理时间为1s,第50个连接请求的受理时间为50s,第100个连接请求的受理时间为100s.即同时很多客服端连接,需要依次排队受理,但只要受理了,他们的服务时间平均只需1s.第二种,所有连接请求的受理时间不超过1s,但平均服务时间要2~3s. 即使有可能延长服务时间,我们实际网络编程中也一般选择第二种方式,使其可以同时向所有发起请求的客服端提供服务,以提高平均满意度.而且,网络程序中数据通信时间比CPU运算时间占比更

linux网络编程学习笔记之四 -----多-threaded服务器

对于使用过程中并发.通过实现更轻量级线程. 每个线程都是一个独立的逻辑流. 主题是CPU在执行调度的最小独立单位,这个过程是资源分配单元.当然,这是在微内核操作系统说.总之,这是唯一的一个操作系统内核提供了最重要的OS服务,许多人看点击打开链接 每一个线程有它自己的线程上下文.包含一个唯一的线程ID(linux上实现为unsigned long),栈,栈指针.程序计数器.通用目的寄存器和条件码,还有自己的信号掩码和优先级.同一个进程里的线程共享这个进程的整个虚拟地址空间,包含可运行的程序文本.程

转 网络编程学习笔记一:Socket编程

网络编程学习笔记一:Socket编程 “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. ——有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的.本文的主要内容如下:

黑马程序员_JAVA UDP网络编程学习笔记

一.UDP网络编程概述 采用TCP协议通信时,客户端的Socket必须先与服务器建立连接,连接建立成功后,服务器端也会持有客户端连接的Socket,客户端的Socket与服务器端的Socket是对应的,它们构成了两个端点之间的虚拟通信链路.与TCP通信不同,UDP是面向无连接的.不可靠的基于数据包的传输协议.即应用进程(或程序)在使用UDP协议之前,不必先建立连接.自然,发送数据结束时也没有连接需要释放.因此,减少了开销和发送数据之前的延时.UDP也采用端口来区分进程. 在java中,java.