Multi-threading | Multi-process | High-concurrency network programming

Insert image description here

1. Multi-process concurrent server

Multi-process concurrent server is a classic server architecture that achieves the ability to handle multiple client requests concurrently by creating multiple sub-processes to handle client connections.

concept:

  1. When the server starts, the main process is created and the listening port is bound.
  2. When there is a client connection request, the main process accepts the connection and creates a child process to handle the client connection.
  3. The child process communicates with the client, processing requests and sending responses.
  4. The main process continues to listen for new connection requests.
  5. After the child process completes the task, it can choose to terminate or continue processing other connections, and choose whether to repeat the cycle according to needs.

advantage:

  1. High concurrent processing capabilities: Each child process can handle a client connection independently, taking advantage of multi-core processors to achieve high concurrent processing capabilities and improve server throughput.
  2. Stability: Each child process is independent. Problems with one process will not affect other processes, which improves the stability and fault tolerance of the server.
  3. Simple and intuitive: It is relatively simple to implement concurrent servers using the multi-process model, and the code is highly readable and easy to understand and maintain.
  4. Cross-platform: The multi-process concurrent server concept is applicable to various operating systems, not just limited to Linux platforms.

shortcoming:

  1. High resource consumption: Each child process requires independent resources, including memory, file descriptors, etc. When the number of concurrent connections is high, a large amount of system resources will be consumed.
  2. Inter-process communication: Communication between sub-processes requires additional mechanisms, such as pipes, shared memory, etc., adding complexity.
  3. Context switching overhead: The overhead of switching between processes is relatively large and will affect performance.
  4. Debugging difficulties: Since each child process executes independently, debugging and locating problems can be complicated.

To sum up, the multi-process concurrent server can meet the needs of high concurrent processing and has the advantages of stability and simplicity. However, it also has shortcomings such as high resource consumption and difficulty in debugging. It is important to choose an appropriate server architecture based on actual needs and application scenarios.

Case

Using multi-process, based on TCP communication, the server can connect to multiple clients at the same time. Implement case conversion.

service.c

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

void handler(int sig)
{
    if (sig == SIGCHLD)
    {
        while(waitpid(0,NULL,WNOHANG)>0);
    }
}
int main(int argc, char const *argv[])
{
    //1.建立socket套接字
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket is err");
        return -1;
    }

    //2.bind绑定服务器ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (rev < 0)
    {
        perror("bind is err");
        return 0;
    }

    //3.listen设置最大同时链接数量
    rev = listen(socked, 32);
    if (rev < 0)
    {
        perror("rev is err");
        return 0;
    }

    //4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束
    char buf[1024] = {0};
    int size=0;
    while (1)
    {
        int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
        if (acceptfd < 0)
        {
            perror("accept is err");
            return 0;
        }
        pid_t pid = fork();
        if (pid < 0)
        {
            perror("pid is err");
            return 0;
        }
        else if (pid == 0)
        {
            //子进程,每个子进程维护一个客户端
            //不许要监听,直接关掉
            close(socked);
            while (1)
            {

                int flage = recv(acceptfd, buf, sizeof(buf), 0);
                if (flage < 0)
                {
                    perror("recv is err");
                }
                else if (flage == 0)
                {
                    printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
                    close(acceptfd);
                    break;
                }
                else
                {
                    size = strlen(buf);

                    for (int i = 0; i < size; ++i)
                    {
                        if (buf[i] >= 'a' && buf[i] <= 'z')
                            buf[i] = buf[i] + ('A' - 'a');
                        else
                            buf[i] = buf[i] + ('a' - 'A');
                    }
                    printf("%d %s\n",getpid(),buf);
                    send(acceptfd, buf, sizeof(buf), 0);
                }
            }
        }
        else
        {
            //主进程回收子线程资源
            close(acceptfd);
            //异步节约资源
            signal(SIGCHLD, handler);
        }
    }
    return 0;
}

client.c

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

int main(int argc, char const *argv[])
{
    //1.socket建立文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket is err");
    }

    //2.connect连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (flage < 0)
    {
        perror("connect is err");
    }

    //3.服务器端不断发送数据,接受服务器转化后的数据
    char buf[1024] = {0};
    while (1)
    {
        //memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp(buf,"quit#",5)==0)
        {
            break;
        }
        
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(fd, buf, sizeof(buf), 0);
        flage = recv(fd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else
        {
            fprintf(stdout, "%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

2. Multi-threaded concurrent server

Multi-threaded concurrent server is a server architecture that achieves the ability to handle multiple client requests concurrently by creating multiple threads to handle client connections. Here are the concepts and some advantages of multi-threaded concurrent servers:

concept:

  1. When the server starts, the main thread is created and the listening port is bound.
  2. When there is a client connection request, the main thread accepts the connection and creates one or more threads to handle the client connection.
  3. Threads communicate with clients, handle requests and send responses.
  4. The main thread continues to listen for new connection requests.
  5. After the thread completes its task, it chooses to terminate or continue processing other connections, and choose whether to repeat the cycle according to needs.

advantage:

  1. Resource efficiency: Compared with processes, threads have less creation and destruction overhead and occupy fewer resources. In particular, shared resources, such as file descriptors, can be shared among multiple connections, improving resource utilization efficiency.
  2. Response speed: The switching overhead between threads is small, the memory space is shared, the client request can be responded to quickly, and the connection waiting time is reduced.
  3. Simple and intuitive: Compared with multi-process, multi-threaded concurrent server is simpler and more intuitive, the code is highly readable and easy to understand and maintain.
  4. Scalability: Threads can be easily added and removed to adapt to changing connection requirements and provide better scalability.
  5. Sharing data is easy: the data structures shared between threads can be accessed directly without using additional mechanisms like inter-process communication.

shortcoming:

  1. Data synchronization issues: Sharing data between multiple threads may lead to race conditions and data consistency issues, requiring the use of locks or other synchronization mechanisms to ensure thread safety.
  2. Debugging difficulties: Debugging and locating problems can be complicated by the fact that the same process's memory space is shared between threads.
  3. Parallelism limitations: In some cases, the parallelism of a multi-threaded server may be limited by the number of CPU cores available in the system.
  4. Thread safety needs to be considered: Writing thread-safe code may require more development effort and consideration to avoid issues such as data races and deadlocks.

To sum up, multi-threaded concurrent servers can meet high concurrent processing needs and have the advantages of resource efficiency, response speed, simplicity and scalability. However, it also has shortcomings such as data synchronization problems and debugging difficulties. It is important to choose an appropriate server architecture based on actual needs and application scenarios.

Case

Using multi-threading, based on TCP communication, the server can connect to multiple clients at the same time. Implement case conversion.

service.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

struct client
{
    int acceptfd;
    struct sockaddr_in caddr;
};

void *my_pthread(void *p)
{
    char buf[1024] = {0};
    int size = 0;
    struct client *cl = (struct client *)p;
    int acceptfd = cl->acceptfd;
    struct sockaddr_in caddr = cl->caddr;
    while (1)
    {
        int flage = recv(acceptfd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else if (flage == 0)
        {
            printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
            close(acceptfd);
            break;
        }
        else
        {
            size = strlen(buf);

            for (int i = 0; i < size; ++i)
            {
                if (buf[i] >= 'a' && buf[i] <= 'z')
                    buf[i] = buf[i] + ('A' - 'a');
                else
                    buf[i] = buf[i] + ('a' - 'A');
            }
            send(acceptfd, buf, sizeof(buf), 0);
        }
    }

    close(acceptfd);
    return 0;
}

int main(int argc, char const *argv[])
{

    //1.建立socket套接字
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket is err");
        return -1;
    }

    //2.bind绑定服务器ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (rev < 0)
    {
        perror("bind is err");
        return 0;
    }

    //3.listen设置最大同时链接数量
    rev = listen(socked, 32);
    if (rev < 0)
    {
        perror("rev is err");
        return 0;
    }

    //4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束

    int i = 0;
    pthread_t tid;
    struct client ti[1024] = {0};
    while (1)
    {
        int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
        if (acceptfd < 0)
        {
            perror("accept is err");
            return 0;
        }
        ti[i].acceptfd = acceptfd;
        ti[i].caddr = caddr;
        pthread_create(&tid, NULL, my_pthread, (void *)&ti[i]);
        pthread_detach(tid);
        ++i;
    }
    return 0;
}

client.c

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

int main(int argc, char const *argv[])
{
    //1.socket建立文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket is err");
    }

    //2.connect连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (flage < 0)
    {
        perror("connect is err");
    }

    //3.服务器端不断发送数据,接受服务器转化后的数据
    char buf[1024] = {0};
    while (1)
    {
        //memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp(buf,"quit#",5)==0)
        {
            break;
        }
        
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(fd, buf, sizeof(buf), 0);
        flage = recv(fd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else
        {
            fprintf(stdout, "%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

Guess you like

Origin blog.csdn.net/m0_73731708/article/details/132910150