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:轻量、高效、要处理同步
相关推荐
QXWZ_IA4 小时前
定位数据怎么推给 IoT 平台?消息协议选型与接入准备
网络·人工智能·物联网·安全
wdeok5 小时前
Socket编程Tcp
服务器·网络·tcp
xiaoye-duck20 小时前
《C++ 项目:手写大模型接入 SDK》Ollama接口设计(上):初始化、全量响应实现和测试
开发语言·网络·c++·语言模型
无足鸟ICT1 天前
【RHCA+】iptraf-ng(网络监控分析)
linux·网络
坤坤子吖1 天前
Linux网络编程:TCP服务器的多进程与多线程实现
linux·服务器·开发语言·网络·c++·笔记·学习
xiaoye-duck1 天前
《C++ 项目:手写大模型接入 SDK》LLMManager架构设计与实现
开发语言·网络·c++·语言模型
andxe1 天前
工业级 PoE 交换机选型指南:破解边缘部署四大核心难题
运维·网络·安全·光通信·交换机
say_fall1 天前
【Linux网络编程】计算机网络基础:局域网与广域网、OSI七层模型与TCP/IP五层模型、封装解包与MAC/IP地址、端口号与socket编程接口
linux·网络·tcp/ip·计算机网络·信息与通信
Joy T1 天前
Gzip 压缩原理与实践:HTTP 响应压缩、性能收益与工程配置
网络·网络协议·http
叼烟扛炮1 天前
Linux 学习笔记(五十四):内网穿透实战——用 frp 让外网访问你的私人电脑
linux·服务器·网络·笔记·学习