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

引言

本文会写一个并发服务器(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:[email protected]
  > 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好儿子,下篇博文会讲进程池。

时间: 2024-10-12 11:04:35

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

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

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.传统并发服务器调用 fork 派生一个子进程来处理每个客户 2.传统并发服务器的问题在于为每个客户现场 fork 一个子进程比较耗费 CPU 时间. /* include serv01 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; pid_t childpid; void

Linux客户/服务器程序设计范式2&mdash;&mdash;并发服务器(进程池)

引言 让服务器在启动阶段调用fork创建一个子进程池,通过子进程来处理客户端请求.子进程与父进程之间使用socketpair进行通信(为了方便使用sendmsg与recvmsg,如果使用匿名管道,则无法使用以上两个函数).以下针对TCP进行分析. server端使用select轮询用于监听客户端请求的被动套接字fd_listen以及用于父子之间通信的socketpair.每当客户端有请求时,server端会将由accept返回的用于与客户端通信的socket描述符通过socketpair发送给一

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

引言 让服务器在启动阶段调用fork创建一个子进程池,通过子进程来处理客户端请求.子进程与父进程之间使用socketpair进行通信(为了方便使用sendmsg与recvmsg,如果使用匿名管道,则无法使用以上两个函数).以下针对TCP进行分析. server端使用select轮询用于监听客户端请求的被动套接字fd_listen以及用于父子之间通信的socketpair.每当客户端有请求时,server端会将由accept返回的用于与客户端通信的socket描述符通过socketpair发送给一

UNIX网络编程卷1 服务器程序设计范式6 并发服务器,为每个客户请求创建一个线程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.为每个客户请求创建一个线程,以取代为每个客户派生一个子进程 /* include serv06 */ #include "unpthread.h" int main(int argc, char **argv) { int listenfd, connfd; void sig_int(int); void *doit(void *); pthread_t tid; sockl

UNIX网络编程卷1 服务器程序设计范式0 迭代服务器

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.迭代 TCP 服务器总是在完全处理某个客户的请求后才转向下一个客户. 2.从进程控制角度看迭代服务器是最快的,因为它不执行进程控制. /* include serv00 */ #include "unp.h" int main(int argc, char **argv) { int listenfd, connfd; void sig_int(int), web_child

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

TCP测试用客户程序 1 #include "unp.h" 2 3 #define MAXN 16384 /* max # bytes to request from server */ 4 5 int 6 main(int argc, char **argv) 7 { 8 int i, j, fd, nchildren, nloops, nbytes; 9 pid_t pid; 10 ssize_t n; 11 char request[MAXLINE], reply[MAXN];

Linux客户/服务器程序设计范式——阿帕奇服务器(多进程)

引言 本文会写一个并发服务器(concurrent server)程序,它为每个客户请求fork出一个子进程. 注意 1. 信号处理问题 对于相同信号,按信号的先后顺序依次处理.可能会产生的问题是,正在处理sig1信号时,又来了2个或更多的sig1信号,此sig1时只会在处理完原来的sig1信号后,再处理1个sig1信号.因此对于相同信号,会产生信号掉包的问题. 一个儿子退了之后,程序在处理handler(),如果此时又退了两个儿子,那么必然有一个儿子的资源回收不到,称为僵尸进程. 对于不同信号

UNIX网络编程卷1 回射服务器程序 TCP服务器程序设计范式 四个版本

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 这是一个简单的回射服务器程序.它将客户发送的数据读入缓冲区并回射其中内容 下面我会介绍同一个使用 TCP 协议的回射服务器程序的几个不同版本,分别是 fork 版本.select 版本.poll 版本.多线程版本 fork 版本:为每一个客户连接派生(fork) 一个子进程用来处理客户请求 /** * TCP/IPv4 协议相关 * **/ #include "unp.h" in

UNIX网络编程第30章客户服务器程序设计范式 草稿