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:轻量、高效、要处理同步
相关推荐
小捏哩3 小时前
死锁检测组件的设计
linux·网络·数据结构·c++·后端
信看3 小时前
SIM7600 MQTT TCP UDP 等常用网络功能测试
网络·tcp/ip·udp
添砖java‘’3 小时前
应用层协议HTTP
网络·网络协议·http
深蓝轨迹3 小时前
TCP/IP 网络模型面试核心考点总结01(基础篇)
网络·tcp/ip·面试
YJlio3 小时前
《Windows 11 从入门到精通》读书笔记 1.4.9:全新的微软应用商店——“库 + 多设备同步”把它从鸡肋变成刚需入口
c语言·网络·python·数码相机·microsoft·ios·iphone
YJlio3 小时前
《Windows 11 从入门到精通》读书笔记 1.4.10:集成的微软 Teams——办公与社交的无缝衔接
c语言·网络·python·数码相机·ios·django·iphone
安科士andxe4 小时前
深度解析安科士1X9-1.25G-60Km光模块,为何能成为长距低速通信首选?
网络·5g
路由侠内网穿透4 小时前
本地部署开源网络书签与内容管理工具 Karakeep 并实现外部访问
运维·服务器·网络·数据库·开源
Fairy要carry4 小时前
项目05-手搓Agent之任务通信+任务编排的实现
服务器·前端·网络