利用信号处理技术消灭僵尸进程
子进程终止时产生SIGCHLD信号。就可以捕捉信号终止子进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void read_childproc(int sig){//子进程终止调用该函数
int status;
pid_t id=waitpid(-1, &status, WNOHANG);//将子进程正常终止
if(WIFEXITED(status)){
printf("Removed proc id: %d \n", id);
printf("Child send: %d \n", WEXITSTATUS(status));
}
}
int main(int argc, char *argv[])
{
pid_t pid;
struct sigaction act;
act.sa_handler=read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD, &act, 0);
pid=fork();
if(pid==0){
puts("Hi! I'm child process");
sleep(10);//等待10s终止
return 12;
}
else
{ printf("Child proc id: %d \n", pid);
pid=fork();
if(pid==0){
puts("Hi! I'm child process");
sleep(10);
exit(24);
}else{
int i;
printf("Child proc id: %d \n", pid);
for(i=0; i<5; i++){
puts("wait...");
sleep(5);
}
}
}
return 0;
}
基于多任务的并发服务器
每当有客户端请求服务时,服务器都创建子进程以提供服务。
- 服务器端(父进程)通过调用 accept画数受理连接请求 。
- 此时获取的套接字文件描述符创建并传递给子进程 。(因为子进程会复制父进程拥有的所有资源 。实际上根本不用另外经过传递文件描述符的过程 。)
- 子进程利用传递来 的文件描述符提供服务 。
while(1){
adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
if(clnt_sock==-1)
continue;
else
puts("new client connected...");
pid=fork();
if(pid==-1){
close(clnt_sock);
continue;
}
if(pid==0){
close(serv_sock);
while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0)
write(clnt_sock, buf, str_len);
close(clnt_sock);
puts("client disconnected...");
return 0;
}
else
close(clnt_sock);
}
通过fork函数复制文件描述符
套接字属于操作系统,fork子进程只是将父进程的文件描述符拿了过去。进程拥有代表相应套接字的文件描述符。父进程将2个套接字(一个是服务器端套接字,另一个是与客户端连接的套接字)文件描述符复制给子进程 。
1个套接字中存在2个文件描述符时,只有2个文件描述符都终止 (销毁)后,才能销毁套接字 。如果维持阁中的连接状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法完全销毁套接字(服务器端套接字同样如此)。 因此,调用fork函数后,要将无关的套接字文件描述符关掉,
一次
accept
就会创建一个套接字。
分割TCP的I/O程序
传输数据后需要等待服务器端返回的数据,因为程序代码中重复调用了 read和write函数 。 只能能这么写的原因之一是,程序在 1 个进程中运行 。 但现在可以创建多个进程,因此可以分割数据收发过程 。 客户端的父进程负责接收数据,额外创建的子进程负责发送数据 。 分割后,不同进程分别负责输入和输出,这样无论客户端是否从服务器端接收完数据都可以进行传输 。
之前的回声客户端要接收到服务器的数据才发数据,现在可以连发数据。
分割I/O后的客户端发送数据时不必考虑接收数据的情况,因此可以连续发送数据,由此提高同一时间内传输的数据量 。 这种差异在网速较慢时尤为明显 。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
int main(int argc, char *argv[])
{
int sock;
pid_t pid;
char buf[BUF_SIZE];
struct sockaddr_in serv_adr;
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("connect() error!");
pid=fork();
if(pid==0)
write_routine(sock, buf);//子进程程用来写
else
read_routine(sock, buf);//父进程用来读
close(sock);
return 0;
}
void read_routine(int sock, char *buf)
{
while(1)
{
int str_len=read(sock, buf, BUF_SIZE);
if(str_len==0)
return;
buf[str_len]=0;
printf("Message from server: %s", buf);
}
}
void write_routine(int sock, char *buf)
{
while(1)
{
fgets(buf, BUF_SIZE, stdin);
if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
{
shutdown(sock, SHUT_WR);
return;
}
write(sock, buf, strlen(buf));
}
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}