Linux 网络编程——多进程,多线程模型

上一章节"Linux Socket编程基础"已经介绍了单客户端-单服务器的一对一模型,但在实际应用中,服务器要同时处理成千上万个客户端的请求,一对一模型没法对其他客户端响应。

这一章节,我们介绍服务器模型的多进程模型(多线程回收子进程)和多进程模型

多进程模型:

多进程模型服务器demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>  // socket套接字
#include <unistd.h>
#include <arpa/inet.h>  // 主机字节序和网络字节序转化
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>

#define _SERVER_PORT 8080
#define BACK_LOG 128
#define IP_SIZE 16
#define BUFFER_SIZE 1600

void sig_wait(int n) {
    pid_t zpid;
    int status;
    while((zpid = waitpid(-1, &status, WNOHANG)) > 0) {
        if(WIFEXITED(status))
            printf("thread 0x%x wait success, zpid %d exitcode or reval %d\n", (unsigned int)pthread_self(), zpid, WEXITSTATUS(status));
        if(WIFSIGNALED(status))
            printf("thread 0x%x wait success, zpid %d signal NO.%d\n", (unsigned int)pthread_self(), zpid, WTERMSIG(status));
    }
}

void *thread_wait(void *arg) { // 回收线程
    // 被创建后屏蔽了SIGCHLD信号
    pthread_detach(pthread_self()); // 设置分离态线程
    struct sigaction act, oact;
    act.sa_handler = sig_wait;
    act.sa_flags = 0; // 如果信号行为置为handler,flags置0
    sigemptyset(&act.sa_mask);

    sigaction(SIGCHLD, &act, &oact);
    sigprocmask(SIG_SETMASK, &act.sa_mask, NULL);

    printf("wait thread 0x%x waiting ...\n", (unsigned int)pthread_self());

    while(1)
        sleep(1); // 睡眠等待信号
}

int server_net_init(void) {
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(_SERVER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //设置本机任意IP
 
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error");
        exit(-1);
    }
    listen(server_fd, BACK_LOG); // 将默认主动的socket变为被动类型的
    printf("TCP Server Waiting for Connect ...\n");

    return server_fd;
}

int server_recv_response(int sockfd) {
    char recv_buffer[BUFFER_SIZE];
    bzero(recv_buffer, BUFFER_SIZE);
    ssize_t recvlen;

    while ((recvlen = recv(sockfd, recv_buffer, sizeof(recv_buffer), 0)) > 0) {
        printf("%s", recv_buffer);
        send(sockfd, recv_buffer, recvlen, 0);// 回传
        bzero(recv_buffer, BUFFER_SIZE);
    }
    if (recvlen == 0) {
        printf("client exit. child %d exiting..\n", getpid());
	    exit(0); // return表示函数返回,exit()代表程序退出
    }
    if (recvlen == -1) {
        printf("recv error");
        exit(-1);
    }
    return 0;
}

int server_accepting(int sockfd) {  // 阻塞模型,阻塞等待客户端连接
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    int client_fd;
    pid_t pid;
    char client_ip[IP_SIZE];
    bzero(client_ip, IP_SIZE);

    if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) > 0) { // accept 阻塞函数
        pid = fork();
        if (pid > 0) {
            printf("TCP Server Accept Success:client ip[%s] client prot[%d] \n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, IP_SIZE),ntohs(client_addr.sin_port));
        } else if (pid == 0) {
            server_recv_response(client_fd);
        } else {
            perror("Process fail");
            exit(-1);
        }
    } else {
        perror("Accept Call Failed");
        exit(-1);
    }
    return client_fd;
}

int main() {
    int sfd;
    int cfd;
    // 主线程设置屏蔽 --> 继承给普通线程
    sigset_t set, oset;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigprocmask(SIG_SETMASK, &set, &oset);
    // 回收线程创建 
    pthread_t tid;
    pthread_create(&tid, NULL, thread_wait, NULL);
    
    sfd = server_net_init();
    while(1) {
        cfd = server_accepting(sfd);
    }
    close(cfd);
    close(sfd);
    return 0;
}

注意!!!!!

return和recv的区别:

1.return 是关键字,exit(0)和_exit(0)是函数。 2.return表示函数返回,而exit()和_exit()代表程序的退出。 return和exit在main函数里是一样的,退出程序并返回值给操作系统。 在普通函数里,exit会退出程序返回到操作系统,return则返回值给上层调用函数。

 

多线程模型

多线程模型服务器demo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>  // socket套接字
#include <unistd.h>
#include <arpa/inet.h>  // 主机字节序和网络字节序转化
#include <pthread.h>
#include <sys/wait.h>

#define _SERVER_PORT 8080
#define BACK_LOG 128
#define IP_SIZE 16
#define BUFFER_SIZE 1600

int server_recv_response(int sockfd);

void *thread_jobs(void *arg) {
    pthread_detach(pthread_self());
    printf("thread 0x%x ... \n", (unsigned int)pthread_self());
    // 线程内部使用void *arg参数时, 最好不要直接使用地址
    server_recv_response(*(int *)arg);
    while(1)
        sleep(1);
}

int server_net_init(void) {
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(_SERVER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //设置本机任意IP
 
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error");
        exit(-1);
    }
    listen(server_fd, BACK_LOG); // 将默认主动的socket变为被动类型的
    printf("TCP Server Waiting for Connect ...\n");

    return server_fd;
}

int server_recv_response(int sockfd) {
    char recv_buffer[BUFFER_SIZE];
    bzero(recv_buffer, BUFFER_SIZE);
    ssize_t recvlen;

    while ((recvlen = recv(sockfd, recv_buffer, sizeof(recv_buffer), 0)) > 0) {
        printf("%s", recv_buffer);
        send(sockfd, recv_buffer, recvlen, 0);// 回传
        bzero(recv_buffer, BUFFER_SIZE);
    }
    if (recvlen == 0) {
        printf("client exit. thread 0x%x exiting..\n", (unsigned int)pthread_self());
    }
    return 0;
}

int server_accepting(int sockfd) {  // 阻塞模型,阻塞等待客户端连接
    struct sockaddr_in client_addr;
    socklen_t addrlen = sizeof(client_addr);
    int client_fd;
    pthread_t tid;
    char client_ip[IP_SIZE];
    bzero(client_ip, IP_SIZE);

    if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) > 0) { // accept 阻塞函数 
        printf("TCP Server Main thread 0x%x Accept Success:client ip[%s] client prot[%d] \n", (unsigned int)pthread_self(), inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, IP_SIZE),ntohs(client_addr.sin_port));
        pthread_create(&tid, NULL, thread_jobs, (void *)&client_fd);
    } else {
        perror("Accept Call Failed");
        exit(-1);
    }
    return client_fd;
}

int main() {
    int sfd;
    int cfd;
    sfd = server_net_init();
    while(1) {
        cfd = server_accepting(sfd);
    }
    close(cfd);
    close(sfd);
    return 0;
}

多进程/多线程 模型的问题:

  • 多进程和多线程这类模型都能在一定程度上帮助我们解决一定的并发问题,但,进程/线程是根据客户端的连接动态创建的,服务器的并发量取决于进程/线程数量(数量有限)。
  • 频繁的创建或销毁进程/线程,系统开销较大,进程/线程可用性不高(一对一绑定关系)
  • 进程/线程缺乏管理和控制

 

下一章节,我们将介绍IO多路复用,使单进程服务器实现高并发

posted on 2022-04-29 23:41  SocialistYouth  阅读(128)  评论(0编辑  收藏  举报