基於自定義協議的服務器高併發處理之:多進程模型

http://www.javashuo.com/article/p-zolorgfe-r.html 只是簡單的處理,服務器返回客戶端一個時間,而後關閉了socket。html

若是要進行雙向通訊,服務器勢必要調用read函數,而read默認阻塞,那麼若是客戶端不向服務器發送數據,則主線程一直阻塞,其它客戶端沒法鏈接成功。這就須要處理高併發問題。服務器

服務器高併發處理的三種方式網絡

  1. 多進程 http://www.javashuo.com/article/p-rsygfewi-e.html
  2. 多線程 http://www.javashuo.com/article/p-vvrmydhx-x.html
  3.  I/O多路複用

 

本篇:多進程模型多線程

主線程只負責accept操做,接收來自客戶端的鏈接。併發

收到一個客戶端鏈接後,就fork出來一個子進程,子進程負責具體的I/O操做。app

收到第二個客戶端鏈接後,再fork出來另外一個子進程,子進程負責具體的I/O操做。socket

以此類推。tcp

注意:ide

  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\n");
        close(socketfd);
        exit(1);
    }
    else if (signo == SIGCHLD)
    {
        //子進程結束 向父進程發送 SIGCHLD 信號
        //父進程區 回收殭屍子進程
        printf("child process dead....\n");
        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)\n", 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\n",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] = '\0';

        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\n",buf);
        }
        else if(size == 0)
        {
            //可能服務器關閉socket了
            printf("socket close from server\n");
            break;
        }
        else
        {
            perror("read_msg error");
            continue;
        }
    }

    //step 4 close
    close(socketfd);
    return 0;
}
View Code
相關文章
相關標籤/搜索