引言

本文会写一个并发服务器(concurrent server)程序,它为每个客户请求fork出一个子进程。

注意

1. 信号处理问题

对于相同信号,按信号的先后顺序依次处理。可能会产生的问题是,正在处理sig1信号时,又来了2个或更多的sig1信号,此sig1时只会在处理完原来的sig1信号后,再处理1个sig1信号。因此对于相同信号,会产生信号掉包的问题。 一个儿子退了之后,程序在处理handler(),如果此时又退了两个儿子,那么必然有一个儿子的资源回收不到,称为僵尸进程。

对于不同信号,优先处理后者,处理完后者在回头处理上一个。例如正在处理sig1时,来了sig2,则会先处理sig2,等处理完sig2后,回过头继续处理sig1。

2. 解决方案

在注册的信号处理函数中,使用循环,并在循环中用waitpid来回收子进程资源。只要进入信号处理函数,那么该信号处理函数就可以把所有fork出的儿子的资源都回收掉。注意:waitpid要设置成非阻塞模式,不然当进入循环后,如果没有子进程退出时,会阻塞在信号处理函数中。

3. wait与waitpid

wait一定是阻塞模式。因此在信号处理函数中,用while1{wait(NULL)}有问题,如果进入信号处理函数后,只要有儿子不退,就会一直阻塞在这里。

waitpid可以是阻塞模式,也可以是非阻塞模式。

4. select与accept

两者在收到信号时,均会返回-1。

对于系统默认不处理的信号,程序收不到,两者当然也不会返回-1。换句话说,SIGCHLD是系统默认不处理的信号,如果不对其注册信号处理函数,当子进程退出时,select与accept也是不会返回-1的。

5. 本代码用到了Linux网络编程9——对TCP与UDP的简易封装2.0中的动态库。

代码

server.c

/*************************************************************************
> File Name: server.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 05 Sep 2014 03:31:12 PM CST
************************************************************************/
#include "my_socket.h"
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#define MY_IP "192.168.1.100"
#define MY_PORT 8888
#define SIZE 192
#define MSG_SIZE (SIZE - 4)
extern int errno ;
typedef struct tag_mag
{
int msg_len;//记录msg_buf的真实大小
char msg_buf[MSG_SIZE];//msg_buf所占空间为188byte
}MSG, *pMSG; void my_handle(int num)
{
/*waitpid参数:
* -1表示回收每一个儿子
* NULL表示不关心子进程的exit的返回值
* WNOHANG:wait no hang 非阻塞模式
*waitpid返回值:
* -1表示没有创建任何儿子
* 0表示没有儿子退出
*大于0表示有儿子退出
* */
while(waitpid(-1, NULL, WNOHANG ) > 0) ;
} int main(int argc, char* argv[])
{
int fd_listen , fd_client ;
signal(SIGCHLD, my_handle);
my_socket(&fd_listen, MY_TCP, MY_IP ,MY_PORT);
my_listen(fd_listen, 10); while( fd_client = accept(fd_listen, NULL, NULL))
{
/* 只要不是程序默认忽略的信号,accept都能收到,并返回-1 */
if(fd_client == -1)
{
if(errno == EINTR)
{
continue ;
}else
{
break ; //break退出后,父亲就退了。下来儿子会由init接管。
}
}else
{
if(fork() == 0) //fork儿子用于与客户端通信
{
MSG recv_msg ;
int recvn;
while(1 )
{
memset(&recv_msg, 0, sizeof(MSG));
/*在my_socket.c中,my_recv接收的长度 与 my_send 发送的长度必须是精确值
* my_recv中填的长度小于等于实际要收的,是可以的,大于的话就永远退不出循环了*/
my_recv(&recvn, fd_client, &recv_msg, 4);
if(recvn == 0) //当对面客户端退出(关闭socket),系统调用recv的返回值为0
{
break ;
}else
{
my_recv(NULL,fd_client, &recv_msg.msg_buf, recv_msg.msg_len);
my_send(NULL, fd_client, &recv_msg, 4 + recv_msg.msg_len); }
}
close(fd_client);
exit(0);
}
close(fd_client);
}
}
return 0 ;
}

client.c

#include "my_socket.h"
#define MY_IP "192.168.1.100"
#define MY_PORT 6666
#define SER_IP "192.168.1.100"
#define SER_PORT 8888
#define SIZE 192
#define MSG_SIZE (SIZE - 4)
typedef struct tag_mag
{
int msg_len ;
char msg_buf[MSG_SIZE];//188
}MSG, *pMSG;
int main(int argc, char* argv[])
{
int sfd ;
my_socket(&sfd, MY_TCP, MY_IP, MY_PORT);
my_connect(sfd, SER_IP, SER_PORT);
MSG my_msg ;
while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, MSG_SIZE, stdin)!= NULL)
{
my_msg.msg_len = strlen(my_msg.msg_buf);
my_send(NULL, sfd, &my_msg, 4 + my_msg.msg_len );
memset(&my_msg, 0, sizeof(MSG));
my_recv(NULL, sfd, &my_msg, 4);
my_recv(NULL, sfd, &my_msg.msg_buf, my_msg.msg_len);
printf("recv from server : %s \n", my_msg.msg_buf); }
close(sfd); }

注意:本代码由于client.c中绑定了端口,因此只能连一个客户端,读者可以自己从命令行中输入端口号或者让系统自行分配。

本范式的缺陷在于,服务端在每次收到一个客户端连接请求后,才会fork儿子进行处理,fork有时间开销。更好的方法是,服务端提前fork好儿子,下篇博文会讲进程池。

Linux客户/服务器程序设计范式1——并发服务器(多进程)的更多相关文章

  1. Linux客户/服务器程序设计范式2——并发服务器(进程池)

    引言 让服务器在启动阶段调用fork创建一个子进程池,通过子进程来处理客户端请求.子进程与父进程之间使用socketpair进行通信(为了方便使用sendmsg与recvmsg,如果使用匿名管道,则无 ...

  2. UNP学习笔记(第三十章 客户/服务器程序设计范式)

    TCP测试用客户程序 #include "unp.h" #define MAXN 16384 /* max # bytes to request from server */ in ...

  3. Linux网络编程服务器模型选择之并发服务器(下)

    前面两篇文章(参见)分别介绍了循环服务器和简单的并发服务器网络模型,我们已经知道循环服务器模型效率较低,同一时刻只能为一个客户端提供服务,而且对于TCP模型来说,还存在单客户端长久独占与服务器的连接, ...

  4. Linux C++服务器程序设计范式

    <Unix网络编程>30章详细介绍了几种服务器设计范式.总结了其中的几种,记录一下: 多进程的做法: 1.每次创建一个新的请求,fork一个子进程,处理该连接的数据传输. 2.预先派生一定 ...

  5. Linux网络编程服务器模型选择之并发服务器(上)

    与循环服务器的串行处理不同,并发服务器对服务请求并发处理.循环服务器只能够一个一个的处理客户端的请求,显然效率很低.并发服务器通过建立多个子进程来实现对请求的并发处理.并发服务器的一个难点是如何确定子 ...

  6. UNIX网络编程卷1 server程序设计范式1 并发server,为每一个客户请求fork一个进程

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发server调用 fork 派生一个子进程来处理每一个客户 2.传统并发serv ...

  7. Linux网络编程客户\服务器设计范式

    1.前言 网络编程分为客户端和服务端,服务器通常分为迭代服务器和并发服务器.并发服务器可以根据多进程或多线程进行细分,给每个连接创建一个独立的进程或线程,或者预先分配好多个进程或线程等待连接的请求.今 ...

  8. LINUX环境并发服务器的三种实现模型

    服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器.按处理方式来分有循环服务器和并发服务器. 1  循环服务器与并发服务器模型 在网络程序里面,一般来说都是许多客户对应一个服务器,为了 ...

  9. linux学习之高并发服务器篇(二)

    高并发服务器 1.线程池并发服务器 两种模型: 预先创建阻塞于accept多线程,使用互斥锁上锁保护accept(减少了每次创建线程的开销) 预先创建多线程,由主线程调用accept 线程池 3.多路 ...

随机推荐

  1. web relase

    http://wenku.baidu.com/link?url=uOAV9QwXGGLjeqt6M1KTqwp0Jbhhguvz9IxExCHNiUlrYMX584Io3ByNJJIkAVzEqzv9 ...

  2. OSGi之Bundle

    OSGi提出的根源是什么?在我看来就是对JVM的类加载机制进行了扩展,添加了一系列的规则,使得原有的类包(Class Package)扩展到类域(Class Domain).然后是建立在类域上的一系列 ...

  3. MongoDB学习笔记-创建、更新、删除文档

    创建     MongoDB中使用insert方法来向集合插入文档,然后保存到MongoDB中.     db.foo.insert({"hehe":"呵呵"} ...

  4. Entity Framework 泛型使用

    因为增删改查是我们常用到的方法,我们不可能每个数据模型都去完成增删改查,这样的办法太笨拙了.我们可以写个父类(包含增删改查),然后让所有的数据模型类继承该父类.那就要求我们的父类必须使用泛型来实现. ...

  5. Python - DICT 字典排序 - OrderedDict

    官方地址: https://docs.python.org/2/library/collections.html#collections.OrderedDict >>> # regu ...

  6. python实现树莓派生成并识别二维码

    python实现树莓派生成并识别二维码 参考来源:http://blog.csdn.net/Burgess_Liu/article/details/40397803 设备及环境 树莓派2代 官方系统R ...

  7. Mono for Android (1) 之布局

    最近和同事交接工作,首次接触mono for android, 结果画view时少了layout,页面没办法出来,各种冥思,各种找问题,最后把关于布局的一些共享出来(同事写的,哈哈):   Andro ...

  8. [网络配置相关]——netstat命令

    netstat:显示网络状态信息 -a   显示所有连接状态的网络的所有选项-l    仅显示LISTEN状态的连接-n   直接显示IP地址,而不通过域名服务器-p   把进程名和进程PID也显示出 ...

  9. JNA开发中的问题积累

    [Qboy原创] 2013年12月28日 在开发一个项目过程中需要调用第三方的C的dll.由于是第一次在项目中使用JNA,很多都安装开发文档来做,但是出现了很多的问题. 由于很多接口还没调完,还不知道 ...

  10. Daily Scrum4

    今天我们小组开会内容分为以下部分: part1:与负责这个项目的其他组进行会晤; part2:组内成员召开了简短会议,进行工作安排; part3:总结今日工作,对项目遇到的问题商讨解决办法; ◆Par ...