首页 > 其他分享 >多线程服务器

多线程服务器

时间:2023-10-25 16:37:51浏览次数:27  
标签:include addr int fd 线程 服务器 多线程 客户端

目录
在真实的服务器客户端模型中,服务器和客户端的关系应为1:n

单线程

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 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;  // 这个宏的值为0 == 0.0.0.0
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    // 3. 设置监听
    ret = listen(lfd, 128);
    // 4. 阻塞等待并接受客户端连接
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);
    // 5. 和客户端通信
    while(1)
    {
        // 接收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        int len = read(cfd, buf, sizeof(buf));
        if(len > 0)
        {
            printf("客户端say: %s\n", buf);
            write(cfd, buf, len);
        }
        else if(len  == 0)
        {
            printf("客户端断开了连接...\n");
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }
    close(cfd);
    close(lfd);
    return 0;
}

在上面的代码中用到了三个会引起程序阻塞的函数,分别是:

  1. accept():如果服务器端没有新客户端连接,阻塞当前进程/线程,如果检测到新连接解除阻塞,建立连接
  2. read():如果通信的套接字对应的读缓冲区没有数据,阻塞当前进程/线程,检测到数据解除阻塞,接收数据
  3. write():如果通信的套接字写缓冲区被写满了,阻塞当前进程/线程(这种情况比较少见)

如果需要和发起新的连接请求的客户端建立连接,那么就必须在服务器端通过一个循环调用accept()函数,
另外已经和服务器建立连接的客户端需要和服务器通信,发送数据时的阻塞可以忽略,当接收不到数据时程序也会被阻塞,
这时候就会非常矛盾,被accept()阻塞就无法通信,被read()阻塞就无法和客户端建立新连接。
因此得出一个结论,基于上述处理方式,在单线程/单进程场景下,服务器是无法处理多连接的,解决方案也有很多,常用的有三种:

  1. 使用多线程实现
  2. 使用多进程实现
  3. 使用IO多路转接(复用)实现
  4. 使用IO多路转接 + 多线程实现

多线程

accept()函数,是一个线程,用于处理与客户端的连接
而每次建立连接,就需要多分配出一个线程来处理和客户端的通信

  • 主线程:
    • 负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
    • 创建子线程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
    • 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。
  • 子线程:负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。
    • 发送数据:send() / write()
    • 接收数据:recv() / read()

      注意:
  1. 同一地址空间中的多个线程的栈空间是独占的
  2. 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。
#include <cstdio>
#include<arpa/inet.h>
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>

using namespace std;

void* working(void* arg);

#define MAXCLIENT 512


struct SockInfo
{
    int fd;                      // 通信
    pthread_t tid;               // 线程ID
    struct sockaddr_in addr;     // 地址信息
};

struct SockInfo infos[MAXCLIENT];

int main()
{
    //初始化
    for (int i = 0; i < MAXCLIENT; i++)
    {
        bzero(infos, 0);
        infos[i].fd = -1;
    }
    printf("%s 向你问好!\n", "SocketLinnx");
    //1.创建监听的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("socket");
        return -1;
    }
    //2.绑定本地端口
    //2.1   addr结构体的初始化和地址
    sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;//指定为0后,自动读取实际ip
    int ret = bind(fd, (sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }
    //3.设置监听
    ret = listen(fd, 128);
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }
    //4.阻塞并等待客户端连接
    int addrlen = sizeof(sockaddr_in);
    while (1)
    {
        struct SockInfo* pinfo;
        for (int i = 0; i < MAXCLIENT; i++)
        {
            if (infos[i].fd == -1)
            {
                pinfo = &infos[i];
                break;
            }
        }
        pinfo->fd = accept(fd, (struct sockaddr*)&pinfo->addr, (socklen_t*)&addrlen);
        if (pinfo->fd == -1)
        {
            perror("accept");
            //连接失败了就进入下一次循环
            continue;
        }
        //创建对应子线程
        pthread_t tid;
        pthread_create(&tid, NULL, working, (void *)pinfo);
        pthread_detach(tid);
    }
    close(fd);
    return 0;
}
void * working(void* arg)
{
    //建立连接成功,打印客户端IP和端口
    //caddr是大端信息,需要转成小端输出
    struct SockInfo* pinfo = (struct SockInfo*)arg;

    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 (true)
    {
        //接收数据
        char buff[1024];
        int len = recv(pinfo->fd, buff, sizeof(buff), 0);
        if (len > 0)
        {
            cout << buff << endl;
            send(pinfo->fd, buff, len, 0);
        }
        else if (len == 0)
        {
            cout << "客户端断开连接" << endl;
            break;
        }
        else {
            perror("recv");
            break;
        }
    }
    //释放文件描述符和其他的东西
    close(pinfo->fd);
    pinfo->fd = -1;
    return NULL;

}

标签:include,addr,int,fd,线程,服务器,多线程,客户端
From: https://www.cnblogs.com/liviayu/p/17787392.html

相关文章

  • 迅雷如何设置代理服务器
    在网络使用中,代理服务器作为一个中介站点,可以帮助我们解决许多网络问题,比如访问一些受限的网站,保护网络安全等。迅雷作为一款广受欢迎的下载软件,也提供了代理服务器的设置选项。下面就详细介绍如何在迅雷中设置代理服务器。首先,我们需要打开迅雷软件。在迅雷的主界面,右上角有一个“......
  • 如果 jumpserver 堡垒机中连不上之前保存能连接的服务器了怎么办
    如果这期间曾经修改过密码,请删除该服务器主机已关联的用户信息,重新添加用户,里面的用户凭据不会通过用户自动同步另外如果主机有其它安全服务保护,请注意是否因为堡垒机尝试错误次数过多导致ip被封,需要手动解封ip!参考:https://blog.csdn.net/weixin_42672685/article/details/11......
  • RTMP流媒体服务器LiteCVR支持在iOS播放WebRTC低延时视频流
    视频监控设备是安防行业的细分专业领域,近年来,视频监控业务正在向其他领域加速渗透。众所周知,iOS系统支持HLS流,但是HLS流延时高,无法满足实时流的要求;而WebRTC播放延时低,因此,很多用户希望能在iOS系统上播放Webrtc视频流。针对用户的这一需求,LiteCVR平台灵活的视频能力,可以完全满足。......
  • 将nginx的access.log访问日志发送到rsyslog服务器并写入数据库
    nginx.conf(将原日志路径改为rsyslog服务器地址)access_logsyslog:server=10.10.14.64:514,facility=local6main;如果需要入库需要安装相应数据库的依赖包;mysql依赖:yuminstall-y rsyslog-mysql   pgsql依赖:yuminstall-y rsyslog-pgsql  还有很多其他依赖可以用......
  • 多线程指南:探究多线程在Node.js中的广泛应用
    前言最初,JavaScript是用于设计执行简单的web任务的,比如表单验证。直到2009年,Node.js的创建者RyanDahl让开发人员认识到了通过JavaScript进行后端开发已成为可能,在后端开发中,用到最多的就是多线程以及线程之间的同步功能,今天小编就为大家介绍一下如何使用Node.js实现多线程的应......
  • 华为云耀云服务器L实例:初级篇-conda与python环境配置
     华为云耀云服务器L实例是一款可快速部署且易于运维的轻量级云服务器,专为中小企业和入门级开发者打造。它不仅拥有华为云擎天架构的强大性能,还具有多项用户体验优化方案,让用户轻松上手,享受简单上云的乐趣。本产品网址为:https://www.huaweicloud.com/product/hecs-light.html......
  • 【记录10】华为云耀云服务器L实例-使用Docker拉取nginx镜像的部署示例
     Docker 是一个开源平台,用于开发、运输和运行应用程序。它使用容器化技术来包装应用程序及其依赖项,以便在任何环境中都能一致地运行。简单来说,Docker 可以将应用程序及其所有依赖项打包成一个容器,这样就可以确保它无论在哪里运行都表现一致。 ### Docker 的主要组件:1.......
  • 华为云耀云服务器L实例:初级篇-mysql安装与配置
     华为云耀云服务器L实例是一款可快速部署且易于运维的轻量级云服务器,专为中小企业和入门级开发者打造。它不仅拥有华为云擎天架构的强大性能,还具有多项用户体验优化方案,让用户轻松上手,享受简单上云的乐趣。本产品网址为:https://www.huaweicloud.com/product/hecs-light.html......
  • 华为云耀云服务器L实例:初级篇-tomcat配置部署
     华为云耀云服务器L实例是一款可快速部署且易于运维的轻量级云服务器,专为中小企业和入门级开发者打造。它不仅拥有华为云擎天架构的强大性能,还具有多项用户体验优化方案,让用户轻松上手,享受简单上云的乐趣。本产品网址为:https://www.huaweicloud.com/product/hecs-light.html......
  • 华为云耀云服务器L实例:初级篇-购买并使用宝塔面板管理自己的云服务器
     在当今数字化时代,云服务器已经成为许多企业和个人不可或缺的一部分。通过将数据和应用程序存储在云端,云服务器为用户提供了灵活、可靠和高效的计算资源。为了帮助用户解决使用云服务器时的问题和困惑,华为云推出了一款专为中小企业和开发者打造的轻量级云服务器产品——云耀云......