TCP 并发学习笔记(进程 fork / 线程 pthread)

目录

一、核心思路

二、多进程并发(fork)

流程

关键代码

优点

缺点

三、多线程并发(pthread)

流程

关键代码

优点

缺点

四、简单对比

一、核心思路

服务器 accept 到一个客户端连接后,不自己处理数据,而是创建子进程 / 子线程去处理,主线程 / 主进程继续监听,实现同时处理多个客户端。


二、多进程并发(fork)

流程

  1. 主进程:socket → bind → listen → 循环 accept
  2. 来一个客户端就 fork ()
  3. 子进程:关闭 listenfd,和客户端读写
  4. 主进程:关闭 connfd,继续 accept
  5. 用 waitpid 回收子进程,避免僵尸进程

关键代码

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <sys/wait.h>
#include <signal.h>

typedef struct sockaddr *(SA);

void handle(int num)
{
    wait(NULL);
}

int	main(int argc, char **argv)
{
    signal(SIGCHLD, handle);

    //监听套接字
    int listfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listfd == -1)
    {
        perror("listfd");
        return 1;
    }

    //创建服务器,客户端地址结构体
    struct sockaddr_in ser,cli;
    bzero(&ser, sizeof(ser));
    bzero(&cli, sizeof(cli));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = INADDR_ANY;

    //给套接字绑定ip+port
    int ret = bind(listfd,(SA)&ser,sizeof(ser));
    if(ret == -1)
    {
        perror("bind");
        return 1;
    }

    //进入监听状态
    listen(listfd, 3);
    socklen_t len = sizeof(cli);

    //多个客户端三次握手
    while(1)
    {
        //建立连接通信套接字
        int conn = accept(listfd, (SA)&cli, &len);
        if(conn == -1)
        {
            perror("accept");
            close(conn);
            continue;
        }
        //创建进程
        pid_t pid = fork();
        if(pid > 0)
        {
            //主进程只负责建立连接-关闭通信套接字
            close(conn);
        }
        else if(pid == 0)
        {
            //关闭监听套接字
            close(listfd);
            while(1)//与客户端的多次收发
            {
                char buf[512] = {0};
                //接收数据
                int rd_ret = recv(conn, buf, sizeof(buf), 0);
                if(rd_ret <= 0)
                {
                    printf("cli offline\n");
                    exit(0);
                }
                //处理数据
                printf("cli:%s\n",buf);
                time_t tm;
                time(&tm);
                sprintf(buf,"%s %s",buf,ctime(&tm));
                //发送数据
                send(conn,buf,strlen(buf),0);
            }
        }
        else
        {
            perror("fork");
            continue;
        }
    }   
    close(listfd);
    return 0;
}

优点

  • 进程独立,一个挂了不影响其他
  • 稳定、安全

缺点

  • 开销大,占内存多
  • 进程间通信麻烦

三、多线程并发(pthread)

流程

  1. 主线程:socket → bind → listen → 循环 accept
  2. 来一个客户端就 pthread_create
  3. 线程函数处理读写
  4. 设置线程分离,自动释放资源

关键代码

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_arg;
typedef struct sockaddr*(SA);

void* th(void* arg)
{
    int conn = *(int*)arg;
    //设置信号量,确保conn不被下一个conn号覆盖
    sem_post(&sem_arg);
    //分离属性,当线程结束后,栈区自动由系统回收
    pthread_detach(pthread_self());
    
    //服务器收发客户端数据过程
    while(1)
    {
        char buf[512] = {0};
        // 5 接收数据
        // 返回值  >0 实际收到的字节数  ==0  对方断开 -1 错误
        int rd_ret = recv(conn, buf, sizeof(buf), 0);
        if (rd_ret <= 0)
        {
            printf("cli offline\n");
            close(conn);
            break;
        }
        // 5.5  处理数据
        printf("cli:%s\n", buf);
        time_t tm;
        time(&tm);
        sprintf(buf, "%s %s", buf, ctime(&tm));
        // 6 发送数据
        send(conn, buf, strlen(buf), 0);
    }
    return NULL;
}

int	main(int argc, char **argv)
{
    //监听套接字
    int listfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == listfd)
    {
        perror("socket");
        return 1;
    }

    //创建服务器,客户端地址结构体
    struct sockaddr_in ser, cli;
    //清空 结构体
    bzero(&ser, sizeof(ser));
    bzero(&cli, sizeof(cli));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(50000);
    ser.sin_addr.s_addr = INADDR_ANY;

    //给套接字绑定ip+port
    int ret = bind(listfd, (SA)&ser, sizeof(ser));
    if (-1 == ret)
    {
        perror("bind");
        return 1;
    }

    //进入监听状态
    listen(listfd, 3);

    socklen_t len = sizeof(cli);
    //设置信号量
    sem_init(&sem_arg,0,0);

    while(1)
    {
        //通信套接字
        int conn = accept(listfd, (SA)&cli, &len);
        if(conn == -1)
        {
            perror("accept");
            close(conn);
            continue;
        }

        //创建线程
        pthread_t tid;
        pthread_create(&tid, NULL, th, &conn);
        //确保conn参数一定在th线程中被保存下来
        sem_wait(&sem_arg);
    }
    sem_destroy(&sem_arg);
    close(listfd);
    return 0;
}

优点

  • 资源开销小
  • 共享全局变量,通信方便

缺点

  • 线程共享资源,要加锁
  • 一个线程崩溃整个进程挂掉

四、简单对比

  • fork:稳定、耗资源、独立
  • pthread:轻量、高效、要处理同步
相关推荐
nuoxin1145 分钟前
HR4988替代A4988-富利威
网络·人工智能·嵌入式硬件·fpga开发·dsp开发
EnCi Zheng37 分钟前
N3A-一个端口只能给一个程序使用吗?[特殊字符]
网络·nginx·docker
技术硬汉2 小时前
Ai帮我找问题-路由器拨号问题-SIM卡不识别
网络·智能路由器
帅次2 小时前
Android 高级工程师面试:网络与数据层 近1年高频追问 18 题
android·网络·okhttp·面试·retrofit·gilde
网络小白不怕黑2 小时前
11.虚拟机模拟路由器实验
linux·运维·服务器·网络
ylscode10 小时前
OpenSSH 10.4 正式发布:后量子加密落地,多项高危漏洞得到修复
网络·安全·网络安全
DianSan_ERP11 小时前
电商架构演进:如何在高并发场景下实现多平台API的标准化履约?
运维·前端·网络·安全·架构·自动化
阿成学长_Cain11 小时前
Linux ipcs 命令超全详解:查看共享内存 / 消息队列 / 信号量,IPC 运维必备
linux·运维·服务器·网络·数据库
青瓦梦滋12 小时前
协议定制/序列化-反序列化(Linux视角)
linux·服务器·网络·c++
驭渊的小故事16 小时前
网络原理01(两千字解析)
网络