基于多线程的套接字通信

发布时间:2024年01月22日

基于多线程的套接字通信

之前的套接字通信, 一个服务器只能和一个客户端进行通信, 不能实现并发的效果, 主要原因是:

解决这种问题有多种方法, 我们先来看多线程解决

多线程中的线程有两大类:主线程(父线程)和子线程,他们分别要在服务器端处理监听和通信流程

  • 主线程: while循环处理监听, 有新的连接就创建一个新的子线程, 让这个子线程和服务器通信
  • 子线程: 负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。

在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的

  • 同一地址空间中的多个线程的栈空间是独占的
  • 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。

在这里插入图片描述

多线程版TCP通信的客户端代码不变, 服务器端代码如下:

//
// Created by 47468 on 2024/1/19.
//
#include <iostream>
#include "arpa/inet.h"
using namespace std;
#include "cstring"
#include "unistd.h"
#include "thread"

struct SockInfo{
    int fd;
    pthread_t tid;
    struct sockaddr_in addr;
};

struct SockInfo infos[128];

// 任务函数
void* worker(void* arg){
    struct SockInfo* info = static_cast<SockInfo*>(arg);
    // 打印子线程id
    cout << "子线程id: " << info->tid << endl;
    // 打印客户端的地址信息
    char ip[24] = {0};
    cout << "客户端的ip地址: " << inet_ntop(AF_INET, &info->addr.sin_addr.s_addr, ip, sizeof(ip))
         << ", 端口: " << ntohs(info->addr.sin_port) << endl;

    // 5. 和客户端通信
    while(1){
        // 接收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        int len = read(info->fd, buf, sizeof(buf));
        if(len > 0){
            cout << "客户端say: " << buf << endl;
            write(info->fd, buf, len);
        } else if (len == 0){
            cout << "客户端断开了连接" << endl;
            break;
        } else {
            perror("read");
            break;
        }
    }

    close(info->fd);
}

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

    // 2. 将socket()返回值和本地的IP端口绑定到一起
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10000);

    // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
    // 这个宏可以代表任意一个IP地址
    // 这个宏一般用于本地的绑定操作
    addr.sin_addr.s_addr = INADDR_ANY;

    // inet_pton(AF_INET, "192.168.237.131", &addr.sin_addr.s_addr);

    int ret = bind(lfd, (struct sockaddr *) &addr, sizeof(addr));
    if (ret == -1) {
        perror("bind");
        exit(0);
    }

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

    // 4. 阻塞等待并接受客户端连接
    int len = sizeof(sockaddr);
    // 数据初始化
    int max = sizeof(infos) / sizeof(infos[0]); // 同时最大连接数
    for (int i = 0; i < max; ++i) {
        memset(&infos[i], 0, sizeof(infos[i]));
        infos[i].fd = -1;
        infos[i].tid = -1;
    }

    while (1) {
        struct SockInfo* pinfo;
        for (int i = 0; i < max; ++i) {
            if(infos[i].fd == -1){
                pinfo = &infos[i];
                break;
            }
            if(i == max - 1){
                sleep(1);
                --i;
            }
        }
        int cfd = accept(lfd, (struct sockaddr *) &pinfo->addr, (socklen_t *) (&len));
        if (cfd == -1) {
            perror("accept");
//            exit(0);
            break;
        }
        pinfo->fd = cfd;
        pthread_create(&pinfo->tid, nullptr, worker, pinfo);
        // 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。
        // 这里用join就不行, 因为用join的话, 子线程不退出, 主线程就会一直阻塞在这里, 无法监听新的连接
        pthread_detach(pinfo->tid);
    }
    // 释放资源
    close(lfd);
    return 0;
}

测试情况:

在这里插入图片描述

在这里插入图片描述

就是三个服务器和客户端在通信

文章来源:https://blog.csdn.net/f593256/article/details/135744218
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。