基于多线程实现服务器并发

看大丙老师的B站视频总结的笔记19-基于多线程实现服务器并发分析_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1F64y1U7A2/?p=19&spm_id_from=pageDriver&vd_source=a934d7fc6f47698a29dac90a922ba5a3

思路:首先accept是有一个线程的,另外只要这个accept成功的和一个客户端建立了连接,那么我们就需要创建一个对应的线程,用这个线程和客户端进行网络通信。每建立一个连接,通信的线程就需要创建出来一个。这样的话,能够保证通信的线程和客户端是一个一一对应的关系,也就是说用于通信的线程一共是有n个,用于建立连接的线程只有一个。在线程里边一共分为两类,一类是主线程,一类是子线程,只要是建立了新连接,主线程创建一个子线程,让子线程和对应建立连接的那个客户端去通信就行了。

这个图的思路和分析:我们需要在主线程里面不停的进行accept操作,如果说有新的客户端连接就建立连接。如果说没有新的客户端连接,主线程就阻塞在accept这个函数上。在主线程里边每创建一个新连接,就需要调用pthread_create创建一个子线程让这个子线程和对应的那个客户端进行网络通信。

考虑细节:多线程之间有哪些资源是共享的?哪些资源是不共享的?

全局和堆区是共享的,他们可以共同访问全局数据区里面的某一块内存或者说堆区里边的某一块内存。如果说有三个线程,那么这个栈区会被分成三份,每个线程都有一块属于自己的独立的栈空间,因此对于多个线程来说,他们并不是共享的。

注意细节:

cpp 复制代码
// 信息结构体
struct SockInfo {
    struct sockaddr_in addr;
    int fd;
};
struct SockInfo infos[512];

把结构体数组里边的每一个元素中的文件描述符设置为-1,这样的话,可以通过这个服务器来判断当前的数组元素是不是被占用的。如果这个数组元素被占用了,它的文件描述符的值应该是一个有效值。如果是-1,是无效值。也就意味着这个元素是空闲的,是可用的

pthread_server.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>

// 信息结构体
struct SockInfo {
    struct sockaddr_in addr;
    int fd;
};
struct SockInfo infos[512];

void* working(void* arg);

int main() {
    // 1.创建监听的套接字
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    // 2.绑定本地的IP port
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 对于0来说,大端和小端是没有区别的的,因此不需要转换
    saddr.sin_port = htons(9999);//主机字节序转换成网络字节序
    
    int ret = bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        return -1;
    }

    // 3.设置监听
    ret = listen(fd,128);
    if(ret == -1) {
        perror("listen");
        return -1;
    }

    //初始化结构体数组
    int max = sizeof(infos) / sizeof(infos[0]);
    for(int i = 0;i < max; i++) {
        bzero(&infos[i],sizeof(infos[i]));
        infos[i].fd = -1;
        /*
            把结构体数组里边的每一个元素中的文件描述符设置为-1
            这样的话,可以通过这个服务器来判断当前的数组元素是不是被占用的
            如果这个数组元素被占用了,它的文件描述符的值应该是一个有效值
            如果是-1,是无效值。也就意味着这个元素是空闲的,是可用的
        */
    }

    // 4.阻塞并等待客户端的连接
    int addrlen = sizeof(struct sockaddr_in);
    while(1) {
        struct SockInfo* pinfo;
        for(int i = 0;i < max; i++) {
            if(infos[i].fd == -1) {
                pinfo = &infos[i];
                break;
            }
        }

        int cfd = accept(fd,(struct sockaddr*)&pinfo->addr,&addrlen);
        pinfo->fd = cfd;
        if(cfd == -1) {
            perror("accept");
            break;
        }
        // 创建子线程
        pthread_t tid;
        pthread_create(&tid,NULL,working,pinfo);
        pthread_detach(tid);
    }
    // 关闭监听描述符
    close(fd);
    return 0;
}

void* working(void* arg) {
    struct SockInfo* pinfo = (struct SockInfo*)arg;
    // 连接建立成功,打印客户端的IP和端口信息
    char ip[32];
    
    printf("客户端的IP: %s,端口: %d\n",
            inet_ntop(AF_INET,&pinfo->addr.sin_addr.s_addr,ip,sizeof(ip)),
            ntohs(pinfo->addr.sin_port));
    
    // 5.通信
    while(1) {
        // 接收数据
        char buff[1024];
        int len = recv(pinfo->fd,buff,sizeof(buff),0);
        if(len > 0) {
            printf("client say: %s\n",buff);
            send(pinfo->fd,buff,len,0);
        }else if(len == 0) {
            printf("客户端已经断开了连接...\n");
            break;
        }else{
            perror("recv");
            break;
        }
    }
    
    // 关掉文件描述符
    close(pinfo->fd);
    pinfo->fd = -1;
    return NULL;
}

client.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main() {
    // 1.创建套接字
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    // 2.连接服务器IP port
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    inet_pton(AF_INET,"192.168.88.129",&saddr.sin_addr.s_addr);
    int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(ret == -1) {
        perror("connect");
        return -1;
    }

    int number = 0;
    // 3.通信
    while(1) {
        // 发送数据
        char buff[1024];
        sprintf(buff,"你好,呵呵哒,%d...\n",number++);
        send(fd,buff,strlen(buff) + 1,0);

        //接收数据
        memset(buff,0,sizeof(buff));
        int len = recv(fd,buff,sizeof(buff),0);
        if(len > 0) {
            printf("server say: %s\n",buff);
        }else if(len == 0) {
            printf("服务器已经断开了连接...\n");
            break;
        }else{
            perror("recv");
        }
        sleep(1);
    }
    // 关闭文件描述符
    close(fd);
    return 0;
}
相关推荐
寻星探路5 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
曹牧8 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
爬山算法8 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
七夜zippoe8 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥9 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
kfyty7259 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎9 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄9 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿9 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds9 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化