TCP协议下的并发服务器

1. TCP协议下的并发服务器存在的意义

accept函数和read/write函数会导致服务器阻塞,只能实现1对1的服务,鉴于此,为了实现1对多的服务,采用了多线程和多进程的技术。

fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen);  //造成阻塞

2. 多线程并发服务器

2.1 server端:

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

#define SERV_PORT 5050
#define SERV_IP_ADDR "192.168.0.158"

void* func_recall(void* arg);
char botton[64];

int main()
{
    int fd = -1;
    int result = 0,i=0;
    struct sockaddr_in mysockaddr;
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen;
    
    static pthread_t nptd;

    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    /*优化4: 允许绑定地址快速重用 */
    int b_reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));

    /*2. 绑定 */
    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 1    
    //mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
    mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif
    /*2.2 绑定 */
    result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("bind");
        return -1;
    }

    /*3. 调用listen()把主动套接字变成被动套接字 */
    result = listen(fd, 5);
    if (result != 0) {
        perror("listen");
        return -1;
    }

    /*4. 阻塞等待客户端连接请求 */

    /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/
    int fd_client;
    while (1) {       
        bzero((void*)&clientscokaddr, sizeof(clientscokaddr));
        fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        if (-1 == result) {
            perror("accpet");
            return 0;
        }
        char ipv4_addr[16];
        if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) {
            perror("inet_ntop");
            return -1;
        }

        printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port));

        result = pthread_create(&nptd, 0, func_recall, (void*)&fd_client);
        if (result != 0) {
            perror("pthread_create");
            return -1;
        }
        i++;
    }

    close(fd);
    close(fd_client);
    return 0;
}

void* func_recall(void* arg) {
    int result = 0;
    int newfd = *(int*)arg;

    printf("handler thread: newfd =%d\n", newfd);
    //..和newfd进行数据读写
    char buf[BUFSIZ];
    

    while (1) {
        bzero(buf, BUFSIZ);

        do {
            result = read(newfd, buf, BUFSIZ - 1);
        } while (result < 0 && EINTR == errno);
        
        if (result < 0) {
            perror("read");
            break;
        }

        if (!result)
            break;

        printf("message is:%s\n", buf);
    }
    close(newfd);
    return 0;
}

--------------------------------------------分割线---------------------------------------------------------

2.2 client端:

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define SERV_PORT 5050
#define SERV_IP_ADDR "192.168.0.158"

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    char MESSAGE[] = "hELLo wOLd!!!\n";
    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }
    /*2.连接服务器 */

	/*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0    
    mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif
    /*2.2  connect 链接服务端*/
    result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("connect");
        return -1;
    }
    printf("Client staring...OK!\n");

    /*3. 读写数据 */
    while (1) {
        do {
            result = write(fd, (void*)MESSAGE, sizeof(MESSAGE));
        } while (result < 0 && EINTR == errno);   //判断是否阻塞了     
        sleep(10);
    }

    close(fd);
    return 0;
}

3. 多进程并发服务器

3.1 server端:

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#define SERV_PORT 5050  //端口号 根据需要可自行修改
#define SERV_IP_ADDR "192.168.0.158"    //IP地址 根据需要可自行修改

void* func_recall(void* arg);
void handler(int signo) {   //自动回收子进程 防止僵尸进程
    
    if (signo == SIGCHLD)
    {
        printf("Recall SIGINT\n");
        waitpid(-1, NULL, WNOHANG);
    }
}

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    struct sockaddr_in clientscokaddr;
    socklen_t clientaddrlen;

    static pthread_t nptd;

    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }

    /*优化4: 允许绑定地址快速重用 */
    int b_reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));

    /*2. 绑定 */
    /*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号
    mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*2.2 绑定 */
    result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("bind");
        return -1;
    }

    /*3. 调用listen()把主动套接字变成被动套接字 */
    result = listen(fd, 5);
    if (result != 0) {
        perror("listen");
        return -1;
    }

    /*4. 阻塞等待客户端连接请求 */

    /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/
    int fd_client;

    pid_t pid;
    signal(SIGCHLD, handler);
    while (1) {
        bzero((void*)&clientscokaddr, sizeof(clientscokaddr));
        fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen);
        if (-1 == result) {
            perror("accpet");
            return 0;
        }
        char ipv4_addr[16];
        if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) {
            perror("inet_ntop");
            return -1;
        }

        printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port));
        pid = fork();
        if (pid < 0) {
            perror("fork");
            return -1;
        }
        else if (pid > 0) { //父进程
            close(fd_client);
        }
        else {  //子进程
            close(fd);
            func_recall((void*)&fd_client); //调用读写函数
            return 0;
        }
    }

    close(fd);
    close(fd_client);
    return 0;
}

void* func_recall(void* arg) {
    int result = 0;
    int newfd = *(int*)arg;

    printf("handler thread: newfd =%d\n", newfd);
    //..和newfd进行数据读写
    char buf[BUFSIZ];
    while (1) {
        bzero(buf, BUFSIZ);

        do {
            result = read(newfd, buf, BUFSIZ - 1);
        } while (result < 0 && EINTR == errno);     //如果遇到阻塞,则重新执行一次 

        if (result < 0) {
            perror("read");
            break;
        }

        if (!result)
            break;

        printf("message is:%s\n", buf);
    }
    close(newfd);
    return 0;
}

--------------------------------------------分割线---------------------------------------------------------

3.2 client端:

#include <cstdio>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define SERV_PORT 5050
#define SERV_IP_ADDR "192.168.0.158"

int main()
{
    int fd = -1;
    int result = 0;
    struct sockaddr_in mysockaddr;
    char MESSAGE[] = "hELLo wOLd!!!\n";
    /* 1. 创建socket fd */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("socket");
        return -1;
    }
    /*2.连接服务器 */

	/*2.1 填充struct sockaddr_in结构体变量 */
    bzero((void*)&mysockaddr, sizeof(mysockaddr));
    mysockaddr.sin_family = AF_INET;
    mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号
#if 0    
    mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr);
    if (result != 1) {
        perror("inet_pton");
        return -1;
    }
#endif
    /*2.2  connect 链接服务端*/
    result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr));
    if (result != 0) {
        perror("connect");
        return -1;
    }
    printf("Client staring...OK!\n");

    /*3. 读写数据 */
    while (1) {
        do {
            result = write(fd, (void*)MESSAGE, sizeof(MESSAGE));
        } while (result < 0 && EINTR == errno);   //判断是否阻塞了     
        sleep(10);
    }

    close(fd);
    return 0;
}