zoukankan      html  css  js  c++  java
  • 基于自定义协议的服务器高并发处理之:多进程模型

    https://www.cnblogs.com/lan0725/p/11634267.html 只是简单的处理,服务器返回客户端一个时间,然后关闭了socket。

    如果要进行双向通信,服务器势必要调用read函数,而read默认阻塞,那么如果客户端不向服务器发送数据,则主线程一直阻塞,其它客户端无法连接成功。这就需要处理高并发问题。

    服务器高并发处理的三种方式

    1. 多进程 https://www.cnblogs.com/lan0725/p/11634709.html
    2. 多线程 https://www.cnblogs.com/lan0725/p/11639142.html
    3.  I/O多路复用

    本篇:多进程模型

    主线程只负责accept操作,接收来自客户端的连接。

    收到一个客户端连接后,就fork出来一个子进程,子进程负责具体的I/O操作。

    收到第二个客户端连接后,再fork出来另一个子进程,子进程负责具体的I/O操作。

    以此类推。

    注意:

    1. 每个子进程只服务于单个连接。
    2. 注意子进程回收

    弊端:启动进程占用系统开销大。

    由于自己封装了消息,故有协议读写代码msg.h 和 msg.c。我做了个简单的make,当然可以不用,gcc的时候注意指定msg.c即可。

    Makefile

    app:obj/time_tcp_server.o obj/time_tcp_client.o obj/msg.o obj/echo_tcp_server_process.o obj/echo_tcp_server_thread.o  obj/echo_tcp_client.o
        gcc obj/time_tcp_server.o -o bin/time_tcp_server
        gcc obj/time_tcp_client.o -o bin/time_tcp_client
        gcc -Iinclude obj/echo_tcp_client.o obj/msg.o -o bin/echo_tcp_client
        gcc -Iinclude obj/echo_tcp_server_process.o obj/msg.o -o bin/echo_tcp_server_process
        gcc -Iinclude obj/echo_tcp_server_thread.o obj/msg.o -o bin/echo_tcp_server_thread
    
    obj/time_tcp_server.o:src/time_tcp_server.c
        gcc  -Iinclude -c $< -o $@
    
    obj/time_tcp_client.o:src/time_tcp_client.c
        gcc  -Iinclude -c $< -o $@
    
    obj/msg.o:src/msg.c
        gcc  -Iinclude -c $< -o $@
    
    obj/echo_tcp_server_process.o:src/echo_tcp_server_process.c
        gcc  -Iinclude -c $< -o $@
    
    obj/echo_tcp_server_thread.o:src/echo_tcp_server_thread.c
        gcc  -Iinclude -c $< -o $@
    
    obj/echo_tcp_client.o:src/echo_tcp_client.c
        gcc  -Iinclude -c $< -o $@
    
    clean:
        rm bin/*
        rm obj/*

    目录结构:

    msg.h

    /*
    *封装自定义的协议
    *
    */
    
    #ifndef _MSG_H_
    #define _MSG_H_
    
    #include <sys/types.h>
    
    typedef struct msg
    {
        //协议头
        char head[9];
        //校验码
        char checkcode;
        //实际的存入数据
        char data[1024];
    }Msg;
    
    //发送一个基于自定义协议的Msg
    //从fd中读取内容,写入到buff
    extern int read_msg(int sockfd, char* buff, size_t buffsize);
    
    ////读取一个基于自定义协议的Msg
    //将buff中的内容写入到fd
    extern int write_msg(int sockfd, char* buff, size_t buffsize);
    
    #endif
    View Code

    msg.c代码

    /*
    *封装自定义的协议
    *
    */
    #include "msg.h"
    #include <memory.h>
    #include <unistd.h>
    
    #define HEAD_DESC "elan2019"
    
    //获得校验码
    static unsigned char msg_checkcode(const Msg* message)
    {
        unsigned char ret = 0;
        for (int i = 0; i < sizeof(message->head); ++i)
        {
            ret += message->head[i];
        }
        for (int i = 0; i < sizeof(message->data); ++i)
        {
            ret += message->data[i];
        }
        return ret;
    }
    
    
    // 从fd中读取内容,读取的数据存放到buff
    int read_msg(int fd, char* buff, size_t buffsize)
    {
        Msg message;
        memset(&message, 0, sizeof(message));
    
        size_t size = read(fd, &message, sizeof(message));
        if( size < 0 )
        {
            return -1;
        }
        else if (size == 0)
        {
            return 0;
        }
    
        //校验比对,判断接收到的数据完整性
        unsigned char cc = msg_checkcode(&message);
        if ((unsigned char)message.checkcode ==  cc
            && (strcmp(HEAD_DESC, message.head) == 0))
        {
            memcpy(buff, &message.data, buffsize);
            return sizeof(message);
        }
        return -1;
    }
    
    //将buff中的内容写入到fd
    int write_msg(int fd, char* buff, size_t buffsize)
    {
        // 构建message
        Msg message;
        memset(&message, 0, sizeof(message));
        //填充头
        strcpy(message.head, HEAD_DESC);
        //填充数据
        memcpy(&message.data, buff, buffsize);
        //填充校验码
        message.checkcode = msg_checkcode(&message);
        //开始写入
        size_t size;
        if((size = write(fd, &message, sizeof(message))) < 0)
        {
            return -1;
        }
        else if(size != sizeof(message))
        {
            return -1;
        }
        return sizeof(message);
    }
    View Code

    服务端代码 echo_tcp_server_process.c

    #include <sys/socket.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <time.h>
    #include <string.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include "msg.h"
    
    #define SERVER_PORT 8888
    #define LISTEN_QUEUE_SISE 10
    
    int socketfd;
    
    void signal_handler(int signo)
    {
        if (signo == SIGINT)
        {
            printf("server close by ctrl c
    ");
            close(socketfd);
            exit(1);
        }
        else if (signo == SIGCHLD)
        {
            //子进程结束 向父进程发送 SIGCHLD 信号
            //父进程区 回收僵尸子进程
            printf("child process dead....
    ");
            wait(NULL);//阻塞回收一个子进程
        }
        
    }
    
    void out_clientinfo(const struct sockaddr_in* outsockaddr)
    {
        char ipstr[16];
        memset(ipstr, 0, sizeof(ipstr));
        // 将地址从网络字节序转换为点分十进制
        inet_ntop(AF_INET, &outsockaddr->sin_addr.s_addr, ipstr, sizeof(ipstr));
    
        printf("Connected by %s(%d)
    ", ipstr, ntohs(outsockaddr->sin_port));
    }
    
    // 和客户端双向通信 收到客户端发来的数据马上返回
    void do_something(int fd)
    {
        char buff[1024];
        while(1)
        {
            //读取客户端发送过来的内容
            memset(buff, 0, sizeof(buff));
            size_t size = read_msg(fd, buff, sizeof(buff));
            if (size < 0)
            {
                // 协议出错 跳出while 回到main 结束子进程 
                perror("read_msg error");
                break;
            }
            else if (size == 0)
            {
                /* 客户端断开了连接 挂掉了 跳出while 回到main往下走,会结束子进程
                 * 结束子进程 向父进程发送SIGCHLD信号
                 **/
                break;
            }
            
            printf("recev from client:%s
    ",buff);
    
            //将数据写回
            if (write_msg(fd, buff, sizeof(buff)) < 0)
            {
                if(errno == EPIPE)
                {
                    /* 客户端断开了连接  
                     * 产生SIGPIPE信号 
                     * 将error设置为EPIPE
                     * 所以可在此判断errno 也可捕捉SIGPIPE信号
                     * 俗称管道爆裂
                     * 跳出while 回到main往下走,会结束子进程,结束子进程 向父进程发送SIGCHLD信号
                     **/
                    break;
                }
                
    
                perror("write_msg error");
            }
        }
    }
    
    
    int main(int argc, char const *argv[])
    {
        //Ctrl+C
        if (signal(SIGINT, signal_handler) == SIG_ERR)
        {
            perror("signal SIGINT error");
            exit(1);
        }
    
        //子进程结束 向父进程发送SIGCHLD
        if (signal(SIGCHLD, signal_handler) == SIG_ERR)
        {
            perror("signal SIGCHLD error");
            exit(1);
        }
    
        /* 1 sokect
         * AF_INET ipv4
         * SOCK_STREAM tcp
         **/
        if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("socket error");
            exit(1);
        }
    
        // 2 bind 绑定本地地址和端口
        struct sockaddr_in serveraddr;
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;//ipv4
        serveraddr.sin_port = htons(SERVER_PORT); //端口
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//响应任意网卡的请求
        if(bind(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
        {
            perror("bind error");
            exit(1);
        }
    
        // 3 listen 启动监听 通知系统接受来自客户端的连接 准备好连接队列
        if(listen(socketfd, LISTEN_QUEUE_SISE) < 0)
        {
            perror("listen error");
            exit(1);
        }
    
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len = sizeof(clientaddr);
        while(1)
        {    
            // 4 accept 从队列拿出第一个
            // clientaddr获取客户端的地址信息,是传出参数
            int clientfd = accept(socketfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
            if (clientfd  < 0)
            {
                perror("accept error");
                continue;
            }
            // 5 创建子进程,在子进程中做读写操作 所以注意进程回收
            pid_t pid = fork();
            if(pid < 0)
            {
                //创建进程失败 继续下一次循环
                perror("fork error");
                close(clientfd);
                continue;
            }
            else if(pid == 0)
            {
                //子进程
                /* 父进程中监听的套接字不再使需要 要关闭
                 * 子进程退出后 父进程在运行 那么子进程会变成僵尸进程 父进程需要监听SIGCHLD
                 * 子进程不再fork 必须break
                 **/
                close(socketfd);
                out_clientinfo(&clientaddr);
                do_something(clientfd);
                close(clientfd);
                break;
            }
            else
            {
                //父进程
                close(clientfd);//不使用 关闭
            }
        }
        
        // 6 close
        return 0;
    }
    View Code

    客户端代码echo_tcp_client.c

    #include <sys/socket.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netdb.h>
    #include "msg.h"
    
    #define SERVER_PORT 8888
    #define SERVER_IP 127.0.0.1
    
    int main(int argc, char const *argv[])
    {
        //step 1 创建socket
    
        int socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if (socketfd < 0)
        {
            perror("socket error");
            exit(1);
        }
    
        //step 2 connect
        struct sockaddr_in serveraddr;
        memset(&serveraddr, 0, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(SERVER_PORT);
    
        if(connect(socketfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0 )
        {
            perror("connect error");
            exit(1);
        }
    
        //step 3 read write
        char buf[512];
        char* promt = ">";
        size_t size;
        while(1)
        {
            memset(buf, 0, sizeof(buf));
            //控制台显示提示符
            write(STDOUT_FILENO, promt, 1);
            //读取控制台输入 read是阻塞函数
            size = read(STDIN_FILENO, buf, sizeof(buf));
            if(size < 0) continue;
            buf[size - 1] = '';
    
            if (write_msg(socketfd, buf, size) < 0)
            {
                //写入错误 则忽略 进行下一次写入
                perror("write_msg error");
                continue;
            }
    
            //等待服务器返回
            memset(buf, 0, sizeof(buf));
            //read是阻塞函数 如果服务器没有下发消息,会一直阻塞在这里,直到收到消息。
            size = read_msg(socketfd, buf,sizeof(buf));
            if( size > 0)
            {
                printf("recev from server:%s
    ",buf);
            }
            else if(size == 0)
            {
                //可能服务器关闭socket了
                printf("socket close from server
    ");
                break;
            }
            else
            {
                perror("read_msg error");
                continue;
            }
        }
    
        //step 4 close
        close(socketfd);
        return 0;
    }
    View Code
  • 相关阅读:
    DataGrid数据格式设置表达式
    删除确认按钮
    获取CpuID
    R0~R31寄存器
    动态改变asp.net网页的标题
    使用"Infragistics"问题集
    Read Cpu Id
    操作DataRow记录
    用Javascript创建"后退"按钮
    日历控件的“星期几”变为“几”
  • 原文地址:https://www.cnblogs.com/lan0725/p/11634709.html
Copyright © 2011-2022 走看看