基於tcp的小型服務器(多線程多進程)

在深入理解計算機系統第11章中,講到一個echo服務器,基於tcp字節流的小型服務器,利用socket套接字接口,完成了一個簡易的服務器。

具體細節可參考網絡編程(編寫一個小型服務器)–csapp11章

不過這個小型服務器有一個難以接受的缺點在於:它是單進程的服務器,也就是它只能服務一個客戶,我們先不去討論這個功能強弱,我們知道在現實生活中,一個網站,一個服務器必須要能夠服務多個用戶,如果只能服務一個用戶,那簡直不可想象。

之所以只能服務一個用戶,是由於設計時,只有一個執行流(也就是單進程),串行化完成任務,說白也就是當一個客戶發起請求,該服務器去相應然後服務客戶,客戶退出才能服務其他人。

因此,想要實現併發性,服務多個用戶,那需要實現多進程或者多線程服務器,即一個主線程(主進程)去執行等待客戶,另一個去服務客戶。

思路很簡單,那麼問題來了:如何選擇多線程還是多進程服務器?這時候,我們就要看看它們利弊了。

線程是CPU調度的基本單位,它是一個執行流的不同分支,運行在進程的地址空間之中,共享進程的資源,它強調共享性,因此無論是創建線程,切換線程,終止線程,退出線程代價要小得多,效率相對來說要高很多,在併發問題中,多線程確實是一個提高效率的好辦法。

而對於進程,由於進程是分配資源的基本單位,每個進程都具有自己獨立的進程地址空間,創建,切換,終止,退出等等效率不如線程快,那麼是不是就用多線程好呢?未必,進程強調獨立性,就帶來一個好處,穩定性,一個進程如果崩潰不會影響其他進程,而線程就不同了,多個線程運行在同一個進程的地址空間之中,因此一旦有一個線程崩潰了,其他線程也會崩潰,事實上,對於一個公司來說,服務器線程是絕對不能掛的(掛了也要立即重啓),因此選擇多進程也是可以考慮的。

綜上考慮,我們各實現一份多進程,多線程的服務器,其實代碼主邏輯有很多類似之處。具體如下:



圖示如下:

這裏寫圖片描述

多進程服務器

tcp_server

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/wait.h>

void usage(const char* arg)
{
    printf("correct usage : %s [ip] [port]\n", arg);
}

int start_up(const char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
    {
        fprintf(stderr, "socket failure\n");
        exit(-1);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    inet_aton(ip, &local.sin_addr);
    //local.sin_addr.s_addr = inet_addr(ip);

    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
    {
        fprintf(stderr, "setsockopt failure\n");
        exit(-1);
    }

    int flag = bind(sock, (struct sockaddr*)&local, sizeof(local));
    if (flag == -1)
    {
        fprintf(stderr, "bind failure\n");
        exit(-1);
    }
    flag = listen(sock, 6);
    if (flag == -1)
    {
        fprintf(stderr, "listen failure\n");
        exit(-1);
    }
    return sock;
}


int main(int argc, char* argv[])
{
    // ./tcp_server ip port
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    int listen_sock = start_up(argv[1], atoi(argv[2]));

    while (1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        int new_fd = accept(listen_sock, (struct sockaddr*)&client, &len);
        if (new_fd == -1)
        {
            //fprintf(stderr, "accept failure\n");
            continue;
        }

        pid_t pid = fork();
        if (pid < 0)
        {
            fprintf(stderr, "fork failure\n");
            close(new_fd);
        }
        else if (pid == 0)//child
        {
            close(listen_sock);
            if (fork() > 0)
            {
                exit(0);
            }
            else
            {
                printf("%s:%d connects the server\n", inet_ntoa(client.sin_addr), client.sin_port);
                while (1)
                {
                    char buf[1024];
                    ssize_t read_size = read(new_fd, buf, sizeof(buf)-1);
                    if (read_size > 0)
                    {
                        buf[read_size] = '\0';
                        printf("client : %s\n", buf);
                        write(new_fd, buf, strlen(buf));
                    }
                    else
                    {
                        printf("client quits...\n");
                        break;
                    }
                }
                close(new_fd);
            }
        }
        else //father
        {
            close(new_fd);
            waitpid(-1, NULL, WNOHANG);
        }   
    }

    return 0;
}

tcp_client

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>

void usage(const char* arg)
{
    printf("correct usage: %s [remote_ip] [remote_port]\n", arg);
}

int open_clientfd(const char* ip, int port)
{
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1)
    {
        fprintf(stderr, "socket failure\n");
        exit(-1);
    }

    struct sockaddr_in remote;
    remote.sin_family = AF_INET;
    remote.sin_port = htons(port);
    inet_aton(ip, &remote.sin_addr);

    int flag = connect(clientfd, (struct sockaddr*)&remote, sizeof(remote));
    if (flag == -1)
    {
        fprintf(stderr, "connect failure\n");
        exit(-1);
    }
    return clientfd;

}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    int sock_fd = open_clientfd(argv[1], atoi(argv[2])); 

    while (1)
    {
        char buf[1024];
        printf("please enter # ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if (s > 0)
        {
            buf[s-1] = '\0';
            write(sock_fd, buf, strlen(buf));
            s = read(sock_fd, buf, strlen(buf));
            if (s > 0)
            {
                printf("server echo # %s\n", buf);
            }

            //printf("server # %s\n", buf);
        }
    }

    return 0;
}

makefile

.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.c
    gcc -o tcp_server tcp_server.c -static
tcp_client:tcp_client.c
    gcc -o tcp_client tcp_client.c
.PHONY:clean
clean:
    rm -f tcp_client tcp_server



多線程服務器

tcp_server

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>

void usage(const char* arg)
{
    printf("correct usage : %s [ip] [port]\n", arg);
}

int start_up(const char* ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
    {
        fprintf(stderr, "socket failure\n");
        exit(-1);
    }

    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    // sockaddr* 相當於 void*
    //
    // 利用grep -ER 'struct sockaddr_in {' /usr/include 查找
    //
    // struct sockaddr_in {                                                                                                                                                                     
    //    __kernel_sa_family_t  sin_family; /* Address family       */
    //    __be16        sin_port;   /* Port number          */
    //    struct in_addr    sin_addr;   /* Internet address     */
    //   
    //    /* Pad to size of `struct sockaddr'. */
    //    unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
    //              sizeof(unsigned short int) - sizeof(struct in_addr)];
    // };
    //
    // struct in_addr {                                                                                                                                                                         
    //     __be32  s_addr;
    // };

    int opt = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
    {
        fprintf(stderr, "setsockopt failure\n");
        exit(-1);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    //local.sin_addr.s_addr = inet_addr(ip);
    inet_aton(ip, &local.sin_addr);

    int flag = bind(sock, (struct sockaddr*)&local, sizeof(local));
    if (flag == -1)
    {
        fprintf(stderr, "bind failure\n");
        exit(-1);
    }
    flag = listen(sock, 6);
    if (flag == -1)
    {
        fprintf(stderr, "listen failure\n");
        exit(-1);
    }

    return sock;
}


void* thread_run(void* arg)
{
    pthread_detach(pthread_self());
    int new_fd = (int)arg;
    char buf[1024];
    memset(buf, 0, sizeof(buf));
    while (1)
    {
        ssize_t s = read(new_fd, buf, sizeof(buf)-1);
        if (s > 0)
        {
            buf[s] = '\0';
            printf("client : %s\n", buf);
            write(new_fd, buf, strlen(buf));
        }
        else
        {
            printf("client quits...\n");
            break;
        }
    }
}


int main(int argc, char* argv[])
{
    // ./tcp_server ip port
    if (argc != 3)
    {
        usage(argv[0]);
        exit(-1);
    }

    int listen_sock = start_up(argv[1], atoi(argv[2]));

    while (1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        // int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        int new_fd = accept(listen_sock, (struct sockaddr*)&client, &len);
        if (new_fd == -1)
        {
            //fprintf(stderr, "accept failure\n");
            continue;
        }

        pthread_t tid;
        pthread_create(&tid, NULL, thread_run, (void*)new_fd);
        //pthread_detach(tid);
    }
    return 0;
}

tcp_client

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>

void usage(const char* arg)
{
    printf("correct usage: %s [remote_ip] [remote_port]\n", arg);
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
    {
        fprintf(stderr, "socket failure\n");
        exit(-1);
    }

    // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    //

    // struct sockaddr_in {                                                                                                                                                                     
    //    __kernel_sa_family_t  sin_family; /* Address family       */
    //    __be16        sin_port;   /* Port number          */
    //    struct in_addr    sin_addr;   /* Internet address     */
    //   
    //    /* Pad to size of `struct sockaddr'. */
    //    unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
    //              sizeof(unsigned short int) - sizeof(struct in_addr)];
    // };
    //
    // struct in_addr {                                                                                                                                                                         
    //     __be32  s_addr;
    // };
    struct sockaddr_in remote;
    remote.sin_family = AF_INET;
    remote.sin_port = htons(atoi(argv[2])); 
    //remote.sin_addr.s_addr = inet_addr(argv[1]);
    inet_aton(argv[1], &remote.sin_addr);

    int flag = connect(sock, (struct sockaddr*)&remote, sizeof(remote));
    if (flag == -1)
    {
        fprintf(stderr, "connect failure\n");
        exit(-1);
    }

    while (1)
    {
        char buf[1024];
        printf("please enter # ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf)-1);
        if (s > 0)
        {
            buf[s-1] = '\0';
            write(sock, buf, strlen(buf));

            s = read(sock, buf, strlen(buf));
            if (s > 0)
            {
                printf("server echo # %s\n", buf);
            }
        }
    }

    return 0;
}

makefile

.PHONY:all
all:tcp_server tcp_client
tcp_server:tcp_server.c
    gcc -o $@ $^ -static -lpthread
tcp_client:tcp_client.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f tcp_client tcp_server
發佈了96 篇原創文章 · 獲贊 119 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章