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:轻量、高效、要处理同步
相关推荐
Johnstons10 小时前
DNS解析慢不是网络差:一次应用卡顿排查实战
网络
爱学习的小囧10 小时前
VMware NSX-T Data Center 3.2.3.0 部署后账号密码获取及登录配置教程
linux·运维·服务器·网络·数据库·esxi
2501_9467862011 小时前
如何高效查找同时持有CCRC和CMA双认证的信息安全服务商?
服务器·网络·安全
W.A委员会11 小时前
地址栏输入url到显示画面
前端·网络
pengyi87101511 小时前
共享IP使用基础注意事项,从源头降低关联风险
网络·网络协议·tcp/ip·安全·http
lcreek11 小时前
epoll 指南:Linux 高并发服务器开发的核心技术
网络·epoll
X7x512 小时前
网工核心:直连 / 静态 / 动态路由全解,附华为 / 华三 / 思科配置 + 高级应用
运维·网络·网络协议·信息与通信
我也不曾来过112 小时前
网络基础概念
网络
Dontla12 小时前
VPC(Virtual Private Cloud虚拟私有云)介绍(内部网络隔离、逻辑私有网络、子网隔离Subnet、公有子网、私有子网、路由表控制、安全组)
网络·安全
思麟呀12 小时前
HTTP的Cookie和Session
linux·网络·c++·网络协议·http