我前两天实现了一个简单的服务器和一个对应的客户端,也简单的解决了一些错误检查和常用的函数的封装,但是那个服务器的一次只能连接一个客户端,鸡肋,太鸡肋了,今天我来实现可以连接多个客户端的服务器实例:多进程并发服务器。

使用多进程并发服务器时要考虑以下几点:

  1. 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
  2. 系统内创建进程个数(与内存大小相关)
  3. 进程创建过多是否降低整体服务性能(进程调度)

    其实也不难,就是将以往的知识应用就是:循环创建多个子进程,循环回收子进程,信号捕捉函数。主要就增加这三个知识。父进程专注于揽业务(连接客户端),子进程专注于处理客户端的数据,信号捕捉函数专门来回收子进程。说到这里,大体的思路应该是有了吧。上菜:

    客户端是没有变化的。。。还是贴上来,占篇幅吧!

    client.c:

    #include
    <string.h>

    #include
    "my_error.h"

    #include
    <sys/types.h>

    #include
    <sys/socket.h>

    #include
    <stdio.h>

    #include
    <stdlib.h>

    #include
    <unistd.h>

    #include
    <sys/stat.h>

    #include
    <arpa/inet.h>

    #include
    <netinet/in.h>

    #define
    ADDR_POST 8888

    int main(void)

    {

        int c_fd;

        int len;

        char buf[BUFSIZ];

        struct
    sockaddr_in clie_addr;

        socklen_t addr_len;

     

        if ((c_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

        {

            perror("socket error");

            exit(1);

        }

     

     

        if (-1 == (inet_pton(AF_INET, "127.0.0.1", &clie_addr.sin_addr.s_addr)))

        {

            perror("inet_pton error");

            exit(1);

        }

        clie_addr.sin_family = AF_INET;

        clie_addr.sin_port = htons(ADDR_POST);

     

        if (-1 == (connect(c_fd, (struct
    sockaddr*)&clie_addr, sizeof(clie_addr))))

        {

            perror("connect error");

            exit(1);

        }

     

        while (1)

        {

            fgets(buf, sizeof(buf), stdin);

            write(c_fd, buf, strlen(buf));

            len = read(c_fd, buf, sizeof(buf));

            write(STDOUT_FILENO, buf, len);

        }

        close(c_fd);

     

        return 0;

    }

    然后是服务器的代码:变得挺多的,所以注释也有,并且比较详细。

    #include<stdio.h>

    #include
    <ctype.h>

    #include
    <netinet/in.h>

    #include
    <sys/wait.h>

    #include<string.h>

    #include
    <signal.h>

    #include<unistd.h>

    #include<stdlib.h>

    #include
    <arpa/inet.h>

    #include
    "my_error.h"

    #define
    SERV_PORT 8888

    #define
    MY_IP
    "127.0.0.1"

     

    void func_wait(int
    signal)

    {

        while (waitpid(0, NULL, WNOHANG))//循环不阻塞的回收子进程

        {

            ;

        }

    }

     

    int main(void)

    {

        int lfd, cfd;

        pid_t pid;

        struct
    sockaddr_in serv_addr, clie_addr;

        socklen_t clie_addr_len;

        char buf[BUFSIZ];

        struct
    sigaction act;

     

        lfd = Socket(AF_INET, SOCK_STREAM, 0);

     

        bzero(&serv_addr, sizeof(serv_addr));

        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        //inet_pton(AF_INET, MY_IP, &serv_addr.sin_addr.s_addr);

        serv_addr.sin_family = AF_INET;

        serv_addr.sin_port = htons(SERV_PORT);

     

        Bind(lfd, (struct
    sockaddr*)&serv_addr, sizeof(serv_addr));

     

        Listen(lfd, 32);

     

     

        while (1)//父进程专门揽业务

        {

            clie_addr_len = sizeof(serv_addr);

            cfd = Accept(lfd, (struct
    sockaddr*)&clie_addr, &clie_addr_len);

     

            pid = fork();

            if (pid > 0)

            {

                Close(cfd);//父进程出去揽业务,那么他就不需要子进程的需要的与客户端建立连接之后产生的新的socket文件秒数符了

     

     

                         /*

                         act.sa_flags = 0;

                         act.sa_handler = func_wait;

                         sigemptyset(&act.sa_mask);

                         sigaddset(&act.sa_mask, SIGCHLD);

                         if ((sigaction(SIGCHLD, &act, NULL)) < 0)//注册一个信号捕捉函数,回收子进程只能由父进程完成

                         {

                         perror("sigaction error");

                         exit(1);

                         }

                         */

                signal(SIGCHLD, func_wait);//注册一个信号捕捉函数,回收子进程只能由父进程完成

            }

            else
    if (pid < 0)//出错

            {

                perror("fork error");

                exit(1);

            }

            else
    if (0 == pid)

            {

                Close(lfd);//子进程不再需要刚开始建立的那个socket文件描述符了,这个描述符是用来和客户端建立连接的,连接已经建立,那么子进程也不需要了

                break;

            }

        }

     

        if (pid == 0)

        {

            while (1)//子进程循环读取/处理/发送客户端发来的数据。直到read返回0(表示读到末尾,即客户端已被关闭);

            {

                int len = Read(cfd, buf, sizeof(buf));

                if (len > 0)

                {

                    for (int i = 0; i != len; i++)

                    {

                        buf[i] = toupper(buf[i]);

                    }

                }

                else
    if (len == 0)//读到结尾,客户端关闭了

                {

                    Close(cfd);

                    return 0;

                }

                else

                {

                    perror("read error");

                    exit(1);

                }

     

                Write(cfd, buf, len);//写数据回去

            }

        }

     

        return 0;

    }

    另外还有一个my_error.h,虽然上一篇博文也有,但是为了不麻烦,贴在这儿吧:

    #pragma
    once

    #include
    <string.h>

    #include
    <sys/types.h>

    #include
    <sys/socket.h>

    #include
    <stdio.h>

    #include
    <stdlib.h>

    #include
    <unistd.h>

    #include
    <sys/stat.h>

    #include
    <arpa/inet.h>

    #include
    <netinet/in.h>

    #include
    <errno.h>

    void perr_exit(const
    char *s)

    {

        perror(s);

        exit(1);

    }

    int Accept(int
    fd, struct
    sockaddr *sa, socklen_t *salenptr)

    {

        int n;

    again: //accrpt是慢速系统调用,在阻塞期间可能会被信号杀死

        if ((n = accept(fd, sa, salenptr)) < 0) {

            if ((errno == ECONNABORTED) || (errno == EINTR))//进一步判断返回值,EINTR代表函数被信号中断;ECONNABORTED代表连接中断,这两种情况不算是异常,所以重启

                goto again;

            else

                perr_exit("accept error");

        }

        return n;

    }

    int Bind(int
    fd, const
    struct
    sockaddr *sa, socklen_t
    salen)

    {

        int n;

        if ((n = bind(fd, sa, salen)) < 0)

            perr_exit("bind error");

        return n;

    }

    int Connect(int
    fd, const
    struct
    sockaddr *sa, socklen_t
    salen)

    {

        int n;

        if ((n = connect(fd, sa, salen)) < 0)

            perr_exit("connect error");

        return n;

    }

    int Listen(int
    fd, int
    backlog)

    {

        int n;

        if ((n = listen(fd, backlog)) < 0)

            perr_exit("listen error");

        return n;

    }

    int Socket(int
    family, int
    type, int
    protocol)

    {

        int n;

        if ((n = socket(family, type, protocol)) < 0)

            perr_exit("socket error");

        return n;

    }

    ssize_t Read(int
    fd, void *ptr, size_t
    nbytes)

    {

        ssize_t n;

    again: //read也是慢速系统调用。

        if ((n = read(fd, ptr, nbytes)) == -1) {

            if (errno == EINTR)

                goto again;

            else

                return -1;

        }

        return n;

    }

    ssize_t Write(int
    fd, const
    void *ptr, size_t
    nbytes)

    {

        ssize_t n;

    again:

        if ((n = write(fd, ptr, nbytes)) == -1) {

            if (errno == EINTR)

                goto again;

            else

                return -1;

        }

        return n;

    }

    int Close(int
    fd)

    {

        int n;

        if ((n = close(fd)) == -1)

            perr_exit("close error");

        return n;

    }

     

    /*

    应用场景:以太网帧一次最多发送1500字节的数据,若是我们要读取4096字节的数据,但是4096字节的数 据需要四次才能完全发送过来,如果只调用一次read,那就只能读到1500就返回不读了,所以我们需要让 系统调用多次,必须读够那么多数据。所以这次调用,n要等于4096

     

    //参数三:应该读取到的字节数*/

    ssize_t Readn(int
    fd, void *vptr, size_t
    n)

    {

        size_t nleft; //unsigned int 剩余未读取的字节数

        ssize_t nread; //int 实际读取到的字节数

        char *ptr;

     

        ptr = (char*)vptr;

        nleft = n; //n 未读取到的字节数

     

        while (nleft > 0) {

            if ((nread = read(fd, ptr, nleft)) < 0) {

                if (errno == EINTR)// EINTR(表"被信号中断")

                    nread = 0; // 读到了0个字节

                else

                    return -1;//其他错误

            }

            else
    if (nread == 0)// 文件读取完

                break;

            nleft -= nread;

            ptr += nread;

        }

        return
    n - nleft;//返回实际读取到的字节数

    }

     

    ssize_t Writen(int
    fd, const
    void *vptr, size_t
    n)

    {

        size_t nleft; //剩余未写的字节数

        ssize_t nwritten; //实际写的字节数

        const
    char *ptr;

     

        ptr = (char*)vptr;

        nleft = n; //未写的字节数

     

        while (nleft > 0) {

            if ((nwritten = write(fd, ptr, nleft)) <= 0) {

                if (nwritten < 0 && errno == EINTR)

                    nwritten = 0;

                else

                    return -1;

            }

            nleft -= nwritten;

            ptr += nwritten;

        }

        return
    n;

    }

     

    static
    ssize_t my_read(const
    int
    fd, char *ptr)

    {

        static
    int read_cnt;

        static
    char *read_ptr;

        static
    char read_buf[100];//读一次可以传出100字节,但是不是一次性传出100字节,实际上 是一次传一个字符出去

     

        if (read_cnt <= 0) {

        again:

            if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {

                if (errno == EINTR)

                    goto again;

                return -1;

            }

            else
    if (read_cnt == 0)

                return 0;

            read_ptr = read_buf;

        }

        read_cnt--;

        *ptr = *read_ptr++;

        return 1;

    }

    /*

    应用场景:fgets只能从普通文件或者标准输入输出设备读去数据,他不能从socket中读取数据,所以用readline代替fgets来读取一行

    */

    ssize_t Readline(int
    fd, void *vptr, size_t
    maxlen)

    {

        ssize_t n, rc;

        char c, *ptr;

        ptr = (char*)vptr;

     

        for (n = 1; n < maxlen; n++) {

            if ((rc = my_read(fd, &c)) == 1) {

                *ptr++ = c;

                if (c == '\n')

                    break;

            }

            else
    if (rc == 0) {

                *ptr = 0;

                return n - 1;

            }

            else

                return -1;

        }

        *ptr = 0;

        return n;//返回读到的字节数

    }

     

    这是结果:

    可惜,这东西貌似只能在我的电脑上运行,在我室友的电脑上就不行,郁闷,不知道是哪儿出问题了。不过没事,以后肯定能知道。不过这个就算是能够通用,它也不能用到实际中,因为他的开销很大。当然,应用到那种只需要几十个几百个客户端的情况下还是可以的。比如智能家居。

Linux:简单的并发服务器实现的更多相关文章

  1. Go语言之进阶篇简单版并发服务器

    1.简单版并发服务器 示例1: package main import ( "fmt" "net" "strings" ) //处理用户请求 ...

  2. (二)通过fork编写一个简单的并发服务器

    概述 那么最简单的服务端并发处理客户端请求就是,父进程用监听套接字监听,当有连接过来时那么监听套接字就变成了已连接套接字(源和目的的IP和端口都包含了),这时候就可以和客户端通信,但此时其他客户端无法 ...

  3. linux 简单搭建git服务器

    如果使用git的人数较少,可以使用下面的步骤快速部署一个git服务器环境. 1. 生成 SSH 公钥 每个需要使用git服务器的工程师,自己需要生成一个ssh公钥进入自己的~/.ssh目录,看有没有用 ...

  4. Linux select TCP并发服务器与客户端编程

    介绍:运行在ubuntu linux系统,需要先打开一个终端运行服务端代码,这时,可以打开多个终端同时运行多个客户端代码(注意客户端数目要小于MAX_FD);在客户端输入数据后回车,可以看见服务器收到 ...

  5. 简单的并发服务器(多个线程各自accept)

    基于之前讲述的简单循环服务器,做一个多个线程各自accept的服务器demo 由于多个线程各自accept,容易造成数据错误,需要在accept前后枷锁 先看下客户端 客户端创建socket,初始化服 ...

  6. [GO]简单的并发服务器

    package main import ( "net" "fmt" "strings" ) func HandleConn(conn net ...

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

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

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

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

  9. socket 中午吃的啥 socket 并发服务器 fork

    http://www.cnblogs.com/thinksasa/archive/2013/02/26/2934206.html zh.wikipedia.org/wiki/網路插座 在作業系統中,通 ...

随机推荐

  1. [UE4]GameInstance初始化

    GameInstance的生命周期跟游戏进程一样. 每一次进入游戏都会初始化一个GameInstance,直到退出游戏才会被销毁. 不会随着场景的变化而被销毁.

  2. typescript可索引接口 类类型接口

    /* 接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用.接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据, ...

  3. UE4中多种颜色轮廓线的后期处理

    转自:http://blog.csdn.net/pizi0475/article/details/50396277 随着近来虚幻引擎4的一些变化,渲染多种颜色的轮廓线已经可以实现了!通过自定义模板,类 ...

  4. U3D学习08-异步、协程

    1.调用 invoke不能传参, 2.协程(不是线程,拥有自己独立的执行序列) Coroutine(尽可能减少计算,提高运行效率) 需要迭代器IEnumerate,迭代器中有返回方法yield 协程的 ...

  5. centos7 安装后静态ip的配置

    centos7 想到于centos6.5来说界面上看起来更加炫一点,但是在配置静态ip上来说是差不多的 首先看一下centos7的安装界面,相对来说简洁好看一些 先打开终端 可以看到centos7默认 ...

  6. oi造数据

    #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> #includ ...

  7. jquery双击事件会触发单击事件

    实际工作中,我们经常会遇到在同一个元素上,绑定多种事件类型,比较常见的是单击事件和一些鼠标事件,一般而言影响不大.但是如果同时绑定单击事件和双击事件呢? 其实,只要能够想明白的话,解决方案也比较简单, ...

  8. Postman用法,了解一下

    一.Postman的基础功能 二.接口请求流程 1. GET 请求 GET请求:点击Params,输入参数及value,可输入多个,即时显示在URL链接上, 所以,GET请求的请求头与请求参数如在接口 ...

  9. 占cpu 100%的脚本

    #! /bin/sh # filename killcpu.sh if [ $# -ne 1 ] ; then echo "USAGE: $0 <CPUs>|stop" ...

  10. C#找出第n到m个素数之间所有之和

    static void Main(string[] args) { int n = int.Parse(Console.ReadLine()); //开始的数 int m = int.Parse(Co ...