最新资讯

  • 【linux】网络套接字编程(四)TCP服务器与客户端的实现(单进程/单线程的TCP服务器),setsockopt,listen,accept,telnet,connect,inet_pton

【linux】网络套接字编程(四)TCP服务器与客户端的实现(单进程/单线程的TCP服务器),setsockopt,listen,accept,telnet,connect,inet_pton

2026-01-29 01:41:05 栏目:最新资讯 3 阅读

小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
linux系统编程专栏<—请点击
linux网络编程专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!


目录

    • 前言
    • 一、TCP服务器TcpServer.hpp(版本一:单进程/单线程版)
      • 基本框架
      • InitServer
        • setsockopt
        • listen
      • StartServer
        • accept
      • Service
    • 二、Main.cc
    • 三、telnet作为客户端进行测试
    • 四、TCP客户端TcpClient.cc
      • 基本框架
      • connect连接
        • inet_pton
      • 开始进行通信
      • 测试
    • 五、源代码
      • makefile
      • TcpServer.hpp
      • TcpClient.cc
      • Main.cc
      • Log.hpp
    • 总结


前言

【linux】网络套接字编程(三)UDP服务器与客户端实现:跨主机执行命令程序,windows与linux通信执行命令程序,多人在线聊天程序,inet_ntop——书接上文 详情请点击<——,本文会在上文的基础上进行讲解,所以对上文不了解的读者友友请点击前方的蓝字链接进行学习
本文由小编为大家介绍——【linux】网络套接字编程(四)TCP服务器与客户端的实现(单进程/单线程的TCP服务器),setsockopt,listen,accept,telnet,connect,inet_pton


一、TCP服务器TcpServer.hpp(版本一:单进程/单线程版)

  1. 关于TCP协议的介绍,以及socket,bind,htons,ntohs等接口的使用,端口号bind的介绍,小编已经在后方蓝字链接文章中进行了讲解 详情请点击<——
  2. 关于IP地址的讲解 详情请点击<——
  3. 关于日志的实现与基本使用讲解 详情请点击<——
  4. 上述的文章与TCP服务器的编写具有一定的铺垫作用,所以希望读者友友先学习上述文章之后再来进入本文的学习更加轻松
  5. 那么对于TCP的服务器,我们希望进行封装为TcpServer.hpp,并且在包含main函数中的文件中包TCP服务器的头文件TcpServer.hpp进行调用,所以下面我们就来先实现一下TCP服务器
  6. 其实TCP服务器的编写套路和UDP服务器的编写套路十分的类似,尤其是在初始化InitServer部分,几乎就是UDP服务器一样,所以本文会基于UDP服务器的实现的基础上直接进行简要的讲解,UDP服务器与客户端的实现,详情请点击<——

基本框架

  1. 首先小编在日志中直接定义了一个日志的全局变量Log lg,这样其它的文件如果想要使用日志,那么仅需要包含日志的头文件#include “Log.hpp”,然后extern声明外部变量即可使用日志

  2. 那么接下来就是定义IP地址,端口号port,网络文件描述符fd的缺省参数,这里小编还定义了一个backlog为10,这个backlog用于给listen进行传参,关于listen是什么,小编后面会进行讲解

  3. 接下来使用枚举enum定义错误码,然后就可以开始TcpServer服务器的编写了

  4. 首先我们需要了解一下一个TCP服务器的类TcpServer中应该包含什么成员变量,那么TCP服务器的成员变量类似于UDP服务器的成员变量,即TCP服务器的成员变量需要包含网络文件描述符,端口号,IP地址,但是注意,这里小编关于类中定义的网络文件描述符的命名不是和UDP服务器写的都是socket_了,在TCP服务器这里,我们将这个网络文件描述符命名为listensock_,所以为什么呢?

  5. 因为TCP协议是面向连接的,服务器需要先和客户端建立起连接才能进行通信,所以服务器如何和客户端建立连接呢?所以服务器在进行初始化部分的时候,要先创建套接字,绑定,接下来和UDP不一样的是,TCP服务器需要额外使用listen接口,将网络文件描述符listensock_设置为监听状态,即TCP服务器初始化创建的套接字返回的网络文件描述符listensock_仅仅用于监听的作用,即表示当前服务器可以接收外部客户端的连接请求

  6. 那么在构造函数我们就进行对应字段的设置,在析构函数,如果创建了网络文件描述符listensock_大于0,那么我们就close关闭这个网络文件描述符对应的文件对象

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Log.hpp"

const int defaultfd = -1;
const uint16_t defaultport = 8080;
const std::string defaultip = "0.0.0.0";
const int backlog = 10;
extern Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        : listensock_(defaultfd), port_(port), ip_(ip)
    {}

    ~TcpServer()
    {
        if (listensock_ > 0)
            close(listensock_);
    }

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

InitServer

  1. 那么在初始化InitServer部分,我们要完成socket创建套接字,struct sockaddre_in本地属性初始化,bind套接字绑定,listen将网络文件描述符listensock_设置为监听状态,下面小编将依次进行讲解
  2. 创建套接字要使用socket,并且我们创建的是TCP网络套接字,TCP是一个使用IPv4地址的网络通信协议,所以socket的第一个参数选择协议家族(域)AF_INET,TCP是面向字节流进行通信的,所以选择协议类型为SOCK_STREAM,最后一个参数默认为0即可,socket,bind的使用,详情请点击<——,如果套接字创建失败,那么我们使用日志打印信息,然后终止服务器,如果成功了那么我们打印日志信息即可
setsockopt

  1. 其实服务器如果中断,然后立即重启,会触发偶发性的重启失败,端口号无法绑定的问题,所以这里我们在服务器的初始化的时候,使用setsockopt这个接口就可以有效避免这种情况,setsockopt是作用是设置网络套接字的选项,所以依次传入网络文件描述符listensock_,然后level就是选择协议层,这里我们选择套接字层SOL_SOCKET,接下来就需要传入要设置的选项,那么这里我们设置重新使用IP地址,重新使用端口号即可SO_REUSEADDR|SO_REUSEPORT,optval即要设计选项的值,那么我们在本地定义一个值为1的opt然后取地址传入即可&opt,紧接着是传入这个变量的大小,即sizeof(opt)
  2. 接下来就是进行struct sockaddre_in本地属性初始化,那么我们和UDP一样,进行对应字段的设置,然后将端口号和IP地址的主机字节序列转换为网络字节序列进行传入即可
  3. 接下来就是进行套接字的绑定bind,那么和UDP的套路一样,依次传入网络文件描述符listensock_,要绑定的字段,字段的大小即可,如果绑定失败bind则会返回一个小于0的数,所以我们判断一下,如果绑定失败,那么打印日志信息,然后终止服务器,如果绑定成功,那么打印日志信息即可
listen

  1. 那么接下来就是使用listen将一个网络文件描述符对应的网络套接字设置为监听状态,那么依次传入网络文件描述符listensock_,然后再传入backlog,其中关于这个backlog我们在基本框架已经定义出来了,默认我们设置为10,表示允许这个服务器同时可以监听连接请求的最大数量,一般我们将其设置为10,并且不会将其设置为很大
  2. 如果listen失败bind则会返回一个小于0的数,所以我们判断一下,如果listen监听失败,那么打印日志信息,然后终止服务器,如果listen监听成功,那么打印日志信息即可
void InitServer()
{
    listensock_ = socket(AF_INET, SOCK_STREAM, 0);
    if (listensock_ < 0)
    {
        lg(Fatal, "create socket error, errno: %d, errstring: %s", errno, strerror(errno));
        exit(SocketError);
    }
    lg(Info, "create socket success, listensock_: %d", listensock_);

    //防止偶发性服务器无法立即重启
    int opt = 1;
    setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port_);
    inet_aton(ip_.c_str(), &(server.sin_addr));
    socklen_t len = sizeof(server);

    if (bind(listensock_, (struct sockaddr *)&server, len) < 0)
    {
        lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
        exit(BindError);
    }
    lg(Info, "bind socket success, listensock_: %d", listensock_);

    if (listen(listensock_, backlog) < 0)
    {
        lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
        exit(ListenError);
    }
    lg(Info, "listen socket success, listensock_: %d", listensock_);
}

StartServer

  1. 这个版本是单进程/单线程版本的启动服务器,那么首先我们打印一个日志消息表示服务器已经启动
  2. 既然是服务器,那么服务器就要24小时的为客户端提供服务,即服务器要一直运行下去,所以这里服务器启动的核心逻辑就要基于死循环for(; ; )进行编写
  3. 之前再服务器初始化的时候我们使用listen将网络文件描述符listensock_对应的网络套接字设置为监听状态,可以监听服务器收到的连接请求,所以此时服务器和客户端建立连接了吗?没有,仅仅是服务器知道了有几个客户端想要连接自己这个服务器,那么如何进行连接呢?accept
accept

  1. 使用accept即可,那么依次传入网络文件描述符listensock_,并且注意这个accept函数和我们在UDP阶段使用的recvfrom的后两个参数是一样的,所以对于我们来讲上手使用这个accept就没难度了
  2. 那么在本地定义一个sockaddr_in类型的对象client,然后取地址进行类型转换为struct sockaddr*再传入,然后计算出大小传入即可,后续连接上的客户端的字段信息,例如IP地址,端口号等就都在这个client中了,所以此时就可以进行连接客户端和服务端了
  3. 那么接下来重点来了,那么就是accept的返回值,如果连接失败了,那么就会返回一个小于0的数,那么我们进行判断即可,如果返回值小于0,那么就日志打印即可,然后再continue重新执行连接(毕竟着只是一个连接失败,但是不能因为仅仅一个连接失败就将服务器终止吧,所以服务器连接失败的时候,不能终止,而是应该继续进行下一个连接),如果连接成功,那么就会返回一个网络文件描述符sockfd,那么我们就可以使用这个网络文件描述符进行读写式的进行通信了,如何读,如何写?
  4. 那么就和文件操作一样,由于文件是基于字节流的,而TCP恰好也是基于字节流的,所以这里完全可以使用文件的read和write向文件描述符中进行读取或写入,大大降低了我们的学习成本,所以后续服务端想要和客户端进行通信,那么就需要使用到accept的返回值,即使用网络文件描述符sockfd
  5. 那么问题来了,TCP服务器初始化然后socket创建的套接字的返回值listensock_和这里accept连接的返回值sockfd都是网络文件描述符,那么两者的作用分别是什么呢?
    (1)listensock_是网络文件描述符,用于监听服务器收到的连接请求,一般一个TCP服务器只有一个listensock_
    (2)sockfd同样是网络文件描述符,它的作用是用于服务端想要和已经连接上的客户端进行通信,进行读写操作的桥梁,用于服务器和客户端进行网络通信的网络文件描述符,服务器可以和多个客户端进行连接,所以也就意味着sockfd可以有多个
  6. 接下来走到下面accept服务器连接客户端成功,那么客户端的信息,例如IP地址,端口号port的信息就都在类型为struct sockaddr_in的这个client对象中了,但是此时对应的字段信息仍然为网络字节序列,小编想要将IP地址以及端口号提取出来在本主机上进行使用,那么就要网络字节序列转主机字节序列
  7. 那么对于端口号,我们可以使用ntohs进行转换为主机字节序列,那么对于为整数的网络字节序列的IP地址, 我们首先要将网络字节序列转换为为整数的主机字节序列,然后再将其转换为点分十进制的字符串风格的IP地址,这里我们使用inet_ntop
  8. 这里小编将主机序列转换为网络字节序列,并且转换为点分十进制的字符串风格IP地址的时候,则使用inet_ntop进行转换 关于inet_ntop如何使用,在第三点的测试的第二点中进行的讲解,详情请点击<——,那么我们依次传入协议家族(IPv4对应的是AF_INET),struct sockaddr_in中的sin_addr的地址(由于sin_addr的成员就是整数类型的IP地址,所以本质上就是传入IP地址字段),自己维护的缓冲区serverip,缓冲区的大小即可
  9. 所以转化端口号和IP地址完成后,那么接下来我们就可以打印一下日志信息了,将网络文件描述符sockfd进行打印然后打印IP地址和端口号信息,并且这里我们可以猜测一下这里的网络文件描述符sockfd应该是4,因为3是listensock_,2是标准错误,1是标准输出,0是标准输出
  10. 然后我们接下来执行Server函数,Server函数是用于服务端处理数据,然后服务端和客户端进行通信的函数,这个小编后面会进行实现,那么让Server函数执行完毕之后,此时当前服务器和客户端的通信已经结束了,所以我们在服务端将这个网络文件描述符sockfd使用close关闭即可
  11. 这个版本是单进程/单线程版本的启动服务器,因为这里执行了Server之后,当前进程就去进行服务器和客户端的通信了,在处理通信的期间无法继续接收其它客户端的连接请求,只有在处理完成了通信之后,关闭文件描述符sockfd,才可以继续接收其它客户端的连接请求,才可以和其它客户端进行通信,所以我们编写的当前版本是单进程/单线程的TCP服务器
void StartServer()
{
    lg(Info, "tcpserver is running...");

    for (;;)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        
        int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
        if (sockfd < 0)
        {
            lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));
            continue;
        }
        uint16_t clientport = ntohs(client.sin_port);
        char clientip[32];
        inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

        lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
            sockfd, clientip, clientport);
        
        Service(sockfd, clientip, clientport);
        close(sockfd);
    }
}

Service

  1. 现在我们设想的场景是客户端一旦连接上服务器,那么客户端期望和服务器源源不断的进行通信,即由服务器提供长服务,所以这里我们就基于while(true)死循环基础上进行编码
  2. 有了网络文件描述符,并且TCP是基于字节流的,文件也是基于字节流的,所以可以使用read读接收数据,使用write进行写发送数据
  3. 那么此时服务端就要读取数据,首先定义一个缓冲区用于将读取上来的数据放到这个缓冲区中,服务端和客户端约定,相互之间传输的数据是字符串,所以此时当read结束,就将这个缓冲区当作字符串来处理,那么使用n来接收read的返回值,n是read读取的字节的个数
  4. 如果n等于0,那么说明此时客户端已经退出,客户端已经将写端关闭了,所以此时我们我们再进行读取已经没有意义了,因为根本没有数据了,所以此时我们先使用日志打印信息,然后break退出死循环,那么结束服务,之后Service执行完毕,就会close关闭sockfd网络文件描述符
  5. 如果n小于0,说明读取失败,此时有可能sockfd有误或者其它原因无法进行读取,所以此时我们日志打印信息,同样break退出死循环,那么结束服务,之后Service执行完毕,就会close关闭sockfd网络文件描述符
  6. 如果n大于0,那么说明此时读取数据成功,所以此时我们就可以对缓冲区进行处理了,由于文件中的字符串的结尾没有’’,所以这里我们c/c++语言中规定,字符串要以’’结尾,所以我们就在n位置处放一个0即可,即在字符串的结尾添加’’,接下来那么我服务端回显打印一下收到了客户端的什么消息即可,然后定义一个字符串echo_string,使用服务端的信息"tcpserver echo# "初始化,将服务端收到的字符串也进行添加上
  7. 即服务端将收到的字符串进行简单的处理之后,然后再write通过sockfd写回给客户端即可
  8. 注意,这里必须要对n == 0或者n < 0的情况进行处理,因为一旦服务器或者客户端,一方作为读端,另一方作为写端进行网络通信,如果有一端突然退出,不想通信了,那么此时也就意味着信道的读端或者写端关闭了一个
  9. 那么此时我们就以客户端作为写端,服务器作为读端为例进行讲解,写端关闭了,那么此时读端就无法读取到数据了,所以也就意味着读端没有意义存在了,所以如果操作系统检测到了对方的写端关闭,而自己这边的读端还在读取,所以操作系统认为自己这边的读端进行读取没有意义,是在浪费资源的,而操作系统绝对不允许在系统内任何一件浪费时间和空间的事情的,而服务器本质上也是一个进程,既然是进程就统一归操作系统管理,操作系统有终止进程的权限,那么操作系统就会直接终止该服务器进程
  10. 那么这一终止可不得了,你试想一下,直接将服务器给终止了,那么所有的客户端都无法连接上服务器了,如果今天的服务器是微信服务器呢?所以所有的手机电脑上的微信客户端都无法通信,支付了,这本质上是非常严重的,所以我们呢不期望服务器被终止,所以这里我们必须要对read的返回值n进行获取,并且操作,一旦我们接收了read的返回值,并且及时进行了操作,我们服务器及时主动的关闭这边的读端,那么此时操作系统就不会终止这个进程了,所以也就保障了服务器可以一直运行
void Service(int sockfd, const std::string& clientip, const uint16_t& clientport)
{
    while (true)
    {
        char inbuffer[4096];
        ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << "client say# " << inbuffer << std::endl;

            std::string echo_string = "tcpserver echo# ";
            echo_string += inbuffer;

            write(sockfd, echo_string.c_str(), echo_string.size());                
        }
        else if(n == 0)
        {
            lg(Info, "%s:%d quit, server colse sockfd: %d", clientip.c_str(), clientport, sockfd);
            break;
        }
        else  
        {
            //异常
            lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", 
                sockfd, clientip.c_str(), clientport);
            break;
        }
    }
}

二、Main.cc

  1. main函数的编写以及调用逻辑和UDP协议的main函数类似,即获取命令行参数,提取port,然后使用智能指针管理new出来的TCP服务器对象,给这个TCP服务器对象传参port,接下来初始化TCP服务器,然后运行TCP服务器即可
#include 
#include 
#include "TcpServer.hpp"


void Usage(const std::string str)
{
    std::cout << "
	Usage: " << str << " port[1024+]
" << std::endl; 
}


int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> server(new TcpServer(port));
    server->InitServer();
    server->StartServer();


    return 0;
}

三、telnet作为客户端进行测试

  1. 其实我们的linux中还有一个工具叫做telnet,这个telnet的底层是使用的是TCP协议,telnet用于在本地通过本地环回地址127.0.0.1然后以及服务端的端口号连接上TCP服务器,可以让telnet作为客户端和我们编写的TCP服务端进行通信
  2. 但是有的读者友友的linux上可能没有安装这个telnet,所以我们使用如下执行安装一下telnet这个工具,然后我们会观察到如下字样complete即为安装telnet工具成功
sudo yum install -y telnet

  1. 所以此时我们将我们的服务器进行编译,然后运行起来,然后使用telnet进行连接,telent是在本地进行连接的服务器,所以使用本地环回的IP地址127.0.0.1,然后再输入服务器绑定的端口号即可建立连接,如下,左侧服务器已经运行起来了
  2. 然后我们在右侧使用telnet作为客户端试着和服务端进行连接,那么启动telnet即输入127.0.0.1 服务器绑定的端口号即可,那么telnet在连接成功之后可以使用ctrl + ]然后再按下回车即可进行输入
  3. 所以此时小编在右侧的使用telnet充当的客户端就可以输入数据和左侧的服务端进行通信了,无误
  4. 那么如何退出右侧telnet的服务端呢?有很多读者友友心中会想,很简单,无脑ctrl+c呀,多简单,那么下面小编尝试一下无脑ctrl+c是否可以退出右侧的telnet服务端,如下很明显,无脑ctrl+c不可以右侧的telnet服务端
  5. 那么正确的退出方式是先按住 ctrl 不松手 然后再按 ] 松开手 此时输入quit即可正常退出telnet客户端
  6. 同样的,如下,如果服务端将信道对应的网络文件描述符socket关闭,或者服务端直接终止(服务端终止,代表着网络文件描述符socket也被关闭了),所以这里小编就ctrl+c在左侧直接终止服务器,同样也会导致telnet的退出

四、TCP客户端TcpClient.cc

  1. 虽然使用telnet工具充当TCP客户端可以让我们观察到服务端和客户端的通信现象,这代表服务端测试无误,可是我们写客户端了吗?没有,仅仅是借助的telnet工具充当客户端,但是关于客户端的原理以及实现,我们还不清楚,所以下面我们来编写一下TCP客户端TcpClient.cc
  2. 其实TCP客户端的编写套路和UDP客户端的编写套路在最初的时候十分的类似,所以小编这里就基于UDP客户端的基础上进行简要讲解,UDP客户端的实现,详情请点击<——

基本框架

  1. 我们期望用户使用 ./udpclient 124.220.4.187 8080 的方式在命令行运行客户端,所以客户端的main函数同样需要参数
  2. 那么如果进行判断argc的个数即可,如果不等于3,那么说明用户传参错误,所以打印提示,然后终止进程即可
  3. 走到下一步说明此时用户传参正确,那么我们提取服务器的IP地址和端口号port即可
  4. 接下来socket创建套接字即可,如果socket的返回值网络文件描述符sockfd小于0,那么说明创建套接字失败,所以我们打印信息,然后终止进程即可
  5. 当最后使用完成了网络文件描述符sockfd之后,使用close关闭网络文件描述符sockfd即可
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Usage(const std::string& str)
{
    std::cout << "
	Usage: " << str << " serverip serverport" << std::endl; 
}

// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket create err" << std::endl;
        return 1;
    }

    close(sockfd);


    return 0;
}

connect连接

  1. TCP是面向连接的,所以客户端想要和服务端进行通信,那么客户端需要主动使用connect向服务器发起连接请求
  2. connect的使用方法很简单,先传入网络文件描述符,我们看connect的后两个参数和我们之前使用的sendto的后两个参数完全一样,所以对于我们来讲上手使用connect很简单
  3. 所以我们就在本地定义sockaddr_in的结构体对象server,使用memset将各个字段设置为0,然后进行对应字段的初始化即可,需要注意将端口号port和IP地址的主机序列转换成网络字节序列,端口号port的转换使用hton即可,很简单
inet_pton

  1. 重点是IP地址,这里要将点分十进制的字符串风格的IP地址转换成网络字节序列对应的整数,所以我们使用inet_pton即可,那么依次传入IPv4地址对应的协议家族(域)AF_INET,然后传入要进行转换的点分十进制的字符串风格的IP地址,然后再传入转换后要放入的位置,即server.sin_addr中,那么我们取出server.sin_addr的地址传入即可
  2. 接下来计算出sockaddr_in的结构体对象server的大小len便于进行传参
  3. 所以此时sockaddr_in的结构体对象server的各个字段我们都设置好了,所以接下来就可以进行connect连接了,那么传入网络文件描述符sockfd,然后传入server取地址,接下来强制类型转换为struct sockaddr*,以及传入这个server对象的大小len即可
  4. 所以此时我们就发起了一次连接请求,观察connect的返回值,如果连接并且绑定成功,那么就会返回0,如果连接或者绑定失败就会返回一个小于0的数-1,所以这里我们进行判断,如果返回值小于0,那么代表连接或绑定失败,那么我们就打印信息,然后终止进程
  5. 那么问题来了,客户端要不要bind绑定?客户端要不要显示的bind绑定?客户端是什么时候进行绑定的?
    (1)客户端要bind绑定(2)客户端不需要显示的绑定,而是由操作系统进行自动的随机绑定(3)bind绑定是在客户端发起connect请求的时候,操作系统会自动进行端口号的随机bind绑定
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
socklen_t len = sizeof(server);

//客户端发起connect请求的时候,操作系统会自动进行端口号的随机bind绑定
int n = connect(sockfd, (struct sockaddr*)&server, len);
if(n < 0)
{
    std::cerr << "connect err..." << std::endl;
    return 2;
}

开始进行通信

  1. 那么我们定义一个string类型的字符串,用于接收用户输入,接下来定义一个inbuffer缓冲区用于拷贝服务器发来的在网络文件描述符sockfd对应的套接字对象中缓冲区的数据
  2. 我们期望客户端一旦开始和服务器进行通信,那么就一直进行通信,所以通信的代码逻辑应该基于while死循环的基础上进行编码
  3. 所以首先我们打印消息,提示用户可以开始进行输入,那么使用getline获取一行输入,将输入结果放到message中,所以用户的输入此时就保存在message中
  4. 由于TCP是面向字节流的,并且TPC网络文件描述符fd对应的是一个套接字文件对象,文件同样也是面向字节流的,所以我们就可以使用文件中的读写方式进行通信,即使用read读接收数据,使用write进行写发送数据
  5. 那么作为客户端我们首先要将数据使用write通过网络文件描述符sockfd发送给服务器,并且约定相互之间发送的数据是一个字符串,文件中的字符串不以’’结尾,所以我们可以使用message的size接口求出字符串的大小,符合我们需求
  6. 那么我们使用write依次传入网络文件描述符sockfd,字符串,字符串的大小即可
  7. 但是write写入也有可能会失败,那么就会返回一个小于0的数,所以这里我们接收一下返回值,并且进行判断,如果写入失败,那么打印信息,然后break退出循环即可
  8. 下一步说明数据已经成功的发送给了服务器,那么服务器处理完成之后就要将数据发回给客户端,所以我们客户端此时就收到数据了,数据被放在了网络文件描述符sockfd对应的文件描述符对象的读缓冲区中,所以我们使用read进行读取即可
  9. 那么对于read依次传入网络文件描述符sockfd,然后传入接收缓冲区inbuffer,以及缓冲区的大小 - 1,这个-1是为了防止缓冲区被写满导致最后无法在结尾添加’’所以要预留出防止’’的空间
  10. 使用n接收read的返回值,read返回的是读取的字节数,所以如果read读取成功,那么n应该大于0,所以此时我们将缓冲区的n位置添加上’’,然后将字符串进行打印即可
  11. 如果n等于0,那么代表此时服务器对应的写端已经关闭,所以此时我们read读取已经没有意义了,所以此时直接break,如果n小于0,那么代表网络文件描述符不正确,读取失败等,所以此时我们同样break退出
std::string message;
char inbuffer[4096];
while(true)
{
    std::cout << "Please Enter# ";
    std::getline(std::cin, message);

    int n = write(sockfd, message.c_str(), message.size());
    if(n < 0)
    {
        std::cerr << "write err" << std::endl;
        break;
    }

    n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
    if(n > 0)
    {
        inbuffer[n] = 0;
        std::cout << inbuffer << std::endl;
    }
    else
    {
        break;
    }
}

测试

  1. 由于小编使用的是云服务器,并且云服务器默认不开放端口号,所以我们要先开放云服务器的TCP的端口号8080,才可以使用我们的客户端连接服务器,那么如何开放云服务器的特定端口号呢?如下是开放腾讯云服务器的特定端口号的操作步骤

  2. 首先,进入腾讯云服务器的控制台,找到登录并点击上图红色框框内的任意位置,进入服务器主界面

  3. 接下来点击上方防火墙后,找到下面的添加规则并点击

  4. 然后应用类型默认自定义即可,来源选择全部IPv4地址,由于本文小编使用的是TCP协议进行的网络通信数据传输,所以协议类型选择TCP协议,端口则输入你想要绑定的端口号即可,最后下方点击确定即可添加成功端口号

  5. 所以下面我们就可以开始进行测试了,那么我们编译服务器和客户端即可,然后依次运行服务器,客户端,无误


  6. 然后ctrl+c依次退出客户端,退出服务端即可,无误

五、源代码

makefile

all:tcpserver tcpclient

tcpserver:Main.cc
	g++ -o $@ $^ -std=c++11
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONT:clean
clean:
	rm -f tcpserver tcpclient

TcpServer.hpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Log.hpp"

const int defaultfd = -1;
const uint16_t defaultport = 8080;
const std::string defaultip = "0.0.0.0";
const int backlog = 10;
extern Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        : listensock_(defaultfd), port_(port), ip_(ip)
    {}

    void InitServer()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "create socket error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensock_: %d", listensock_);

        //防止偶发性服务器无法立即重启
        int opt = 1;
        setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));

        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(server.sin_addr));
        socklen_t len = sizeof(server);

        if (bind(listensock_, (struct sockaddr *)&server, len) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "bind socket success, listensock_: %d", listensock_);

        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "listen socket success, listensock_: %d", listensock_);
    }

    void StartServer()
    {
        lg(Info, "tcpserver is running...");

        for (;;)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            
            int sockfd = accept(listensock_, (struct sockaddr*)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", 
                sockfd, clientip, clientport);
            
            Service(sockfd, clientip, clientport);
            close(sockfd);
        }
    }

    void Service(int sockfd, const std::string& clientip, const uint16_t& clientport)
    {
        while (true)
        {
            char inbuffer[4096];
            ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << "client say# " << inbuffer << std::endl;

                std::string echo_string = "tcpserver echo# ";
                echo_string += inbuffer;

                write(sockfd, echo_string.c_str(), echo_string.size());                
            }
            else if(n == 0)
            {
                lg(Info, "%s:%d quit, server colse sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else  
            {
                //异常
                lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", 
                    sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }

    ~TcpServer()
    {
        if (listensock_ > 0)
            close(listensock_);
    }

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

TcpClient.cc

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Usage(const std::string& str)
{
    std::cout << "
	Usage: " << str << " serverip serverport" << std::endl; 
}

// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket create err" << std::endl;
        return 1;
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    socklen_t len = sizeof(server);
    
    //客户端发起connect请求的时候,操作系统会自动进行端口号的随机bind绑定
    int n = connect(sockfd, (struct sockaddr*)&server, len);
    if(n < 0)
    {
        std::cerr << "connect err..." << std::endl;
        return 2;
    }

    std::string message;
    char inbuffer[4096];
    while(true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        int n = write(sockfd, message.c_str(), message.size());
        if(n < 0)
        {
            std::cerr << "write err" << std::endl;
            break;
        }

        n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::cout << inbuffer << std::endl;
        }
        else
        {
            break;
        }
    }

    close(sockfd);


    return 0;
}

Main.cc

#include 
#include 
#include "TcpServer.hpp"


void Usage(const std::string str)
{
    std::cout << "
	Usage: " << str << " port[1024+]
" << std::endl; 
}


int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> server(new TcpServer(port));
    server->InitServer();
    server->StartServer();


    return 0;
}

Log.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SIZE 1024

#define Info   0
#define Debug  1
#define Warning 2
#define Error  3
#define Fatal  4

#define Screen 1     //输出到屏幕上
#define Onefile 2    //输出到一个文件中
#define Classfile 3  //根据事件等级输出到不同的文件中

#define LogFile "log.txt" //日志名称


class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }

    void Enable(int method) //改变日志打印方式
    {
        printMethod = method;
    }

    ~Log()
    {}

    std::string levelToString(int level)
    {
        switch(level)
        {
            case Info:
                return "Info";
            case Debug:
                return "Debug";
            case Warning:
                return "Warning";
            case Error:
                return "Error";
            case Fatal:
                return "Fata";
            default:
                return "";
        }
    }

    void operator()(int level, const char* format, ...)
    {
        //默认部分 = 日志等级 + 日志时间
        time_t t = time(nullptr);
        struct tm* ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(), 
        ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, 
        ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        char logtxt[2 * SIZE];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        printLog(level, logtxt);
    }

    void printLog(int level, const std::string& logtxt)
    {
        switch(printMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
                break;
            case Onefile:
                printOneFile(LogFile, logtxt);
                break;
            case Classfile:
                printClassFile(level, logtxt);
                break;
            default:
                break;
        }
    }

    void printOneFile(const std::string& logname, const std::string& logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if(fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    void printClassFile(int level, const std::string& logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level);

        printOneFile(filename, logtxt);
    }


private:
    int printMethod;
    std::string path;
};

Log lg;


总结

以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!

本文地址:https://www.yitenyun.com/1082.html

搜索文章

Tags

#远程工作 #服务器 #python #pip #conda #ios面试 #ios弱网 #断点续传 #ios开发 #objective-c #ios #ios缓存 香港站群服务器 多IP服务器 香港站群 站群服务器 #kubernetes #笔记 #平面 #容器 #linux #学习方法 #运维 #Trae #IDE #AI 原生集成开发环境 #Trae AI #进程控制 #学习 #开发语言 #云原生 #iventoy #VmWare #OpenEuler #docker #后端 #数据库 #人工智能 #node.js #cpolar #fastapi #html #css #MobaXterm #ubuntu #物联网 #websocket #低代码 #爬虫 #音视频 #Conda # 私有索引 # 包管理 #算法 #大数据 #数信院生信服务器 #Rstudio #生信入门 #生信云服务器 #内网穿透 #网络 #vscode #mobaxterm #深度学习 #计算机视觉 #github #git #安全 #nginx #tcp/ip #开源 #vllm #大模型 #Streamlit #Qwen #本地部署 #AI聊天机器人 #golang #java #redis #RTP over RTSP #RTP over TCP #RTSP服务器 #RTP #TCP发送RTP #web安全 #缓存 #jvm #我的世界 #android #腾讯云 #c# #kylin #分阶段策略 #模型协议 #unity #游戏引擎 #需求分析 #Dell #PowerEdge620 #内存 #硬盘 #RAID5 #架构 #面试 #ssh #ide #银河麒麟 #系统升级 #信创 #国产化 #华为 #ModelEngine #多个客户端访问 #IO多路复用 #回显服务器 #TCP相关API #编辑器 #hadoop #hbase #hive #zookeeper #spark #kafka #flink #qt #C++ #凤希AI伴侣 #云计算 #银河麒麟高级服务器操作系统安装 #银河麒麟高级服务器V11配置 #设置基础软件仓库时出错 #银河麒高级服务器系统的实操教程 #生产级部署银河麒麟服务系统教程 #Linux系统的快速上手教程 #udp #c++ #json #科技 #自然语言处理 #神经网络 #jmeter #功能测试 #软件测试 #自动化测试 #职场和发展 #prometheus #gpu算力 #grafana #ping通服务器 #读不了内网数据库 #bug菌问答团队 #flutter #数码相机 #debian #改行学it #创业创新 #程序员创富 #asp.net #sqlserver #流媒体 #NAS #飞牛NAS #监控 #NVR #EasyNVR #centos #1024程序员节 #php #前端 #claude #http #cpp #项目 #高并发 #arm开发 #企业开发 #ERP #项目实践 #.NET开发 #C#编程 #编程与数学 #fiddler #华为云 #部署上线 #动静分离 #Nginx #新人首发 #gemini #gemini国内访问 #gemini api #gemini中转搭建 #Cloudflare #screen 命令 #mvp #个人开发 #设计模式 #AI编程 #金融 #mcp #金融投资Agent #Agent #elasticsearch #版本控制 #Git入门 #开发工具 #代码托管 #研发管理 #禅道 #禅道云端部署 #个人博客 #windows #n8n #毕设 #RAID #RAID技术 #磁盘 #存储 #嵌入式编译 #ccache #distcc #vue.js #ollama #ai #llm #性能优化 #智能路由器 #oracle #Spring AI #MCP服务器 #STDIO协议 #Streamable-HTTP #McpTool注解 #服务器能力 #Android #Bluedroid #我的世界服务器搭建 #minecraft #AI #大模型学习 #javascript #压力测试 #c语言 #stm32 #openlayers #bmap #tile #server #vue #电脑 #自动化 #网络协议 #todesk #asp.net大文件上传 #asp.net大文件上传下载 #asp.net大文件上传源码 #ASP.NET断点续传 #asp.net上传文件夹 #uni-app #小程序 #notepad++ #jenkins #jar #算力一体机 #ai算力服务器 #ansible #微信小程序 #微信 #健身房预约系统 #健身房管理系统 #健身管理系统 #SSH #X11转发 #Miniconda #mcu #MCP #聊天小程序 #鸭科夫 #逃离鸭科夫 #鸭科夫联机 #鸭科夫异地联机 #游戏 #开服 #北京百思可瑞教育 #百思可瑞教育 #北京百思教育 #apache #tomcat #risc-v #嵌入式硬件 #tdengine #时序数据库 #制造 #涛思数据 #deepseek #交互 #flask #SSH公钥认证 # PyTorch # 安全加固 #Proxmox VE #虚拟化 #VMware #spring #1panel #vmware #spring boot #部署 #GPU服务器 #8U #硬件架构 #NPU #CANN #搜索引擎 #cosmic #macos #mysql #pytorch #阿里云 #远程桌面 #远程控制 #东方仙盟 #游戏机 #JumpServer #堡垒机 #API限流 # 频率限制 # 令牌桶算法 #iBMC #UltraISO #黑群晖 #虚拟机 #无U盘 #纯小白 #振镜 #振镜焊接 #teamviewer #蓝湖 #Axure原型发布 #llama #opencv #语言模型 #单元测试 #集成测试 #YOLO # Triton # 目标检测 #网络安全 #pycharm #lua #语音识别 #测试工具 #京东云 #计算机网络 #DisM++ # GLM-4.6V # 系统维护 #SRS #直播 #数据结构 #DeepSeek #蓝耘智算 #910B #昇腾 #AIGC #ida #web server #请求处理流程 #aws #MQTT协议 #react.js #分布式 #单片机 #深度优先 #DFS #svn #守护进程 #复用 #screen #系统架构 # 双因素认证 # TensorFlow #rustdesk #p2p #unity3d #服务器框架 #Fantasy #源码 #闲置物品交易系统 #毕业设计 #umeditor粘贴word #ueditor粘贴word #ueditor复制word #ueditor上传word图片 #YOLOFuse # Base64编码 # 多模态检测 #IPv6 #DNS #微服务 #进程 #操作系统 #进程创建与终止 #shell #SPA #单页应用 #django #web3.py #麒麟OS #swagger #visual studio code #java-ee #transformer #prompt #RustDesk #IndexTTS 2.0 #本地化部署 #jupyter #计算机 #树莓派4b安装系统 #mamba #车辆排放 #智能手机 #sqlite #Ansible #Playbook #AI服务器 #课程设计 #journalctl #epoll #电气工程 #C# #PLC #openresty #AI论文写作工具 #学术写作辅助 #论文创作效率提升 #AI写论文实测 #wordpress #雨云 #sql #maven #intellij-idea #libosinfo #ssl #TCP #客户端 #嵌入式 #DIY机器人工房 #gitlab #vuejs #eBPF #说话人验证 #声纹识别 #CAM++ #视频去字幕 #高级IO #select #信令服务器 #Janus #MediaSoup #Windows #gitea #FTP服务器 #网站 #截图工具 #批量处理图片 #图片格式转换 #图片裁剪 #万悟 #联通元景 #智能体 #镜像 #scala #测试用例 #webrtc #idm #Android16 #音频性能实战 #音频进阶 #cesium #可视化 # GPU租赁 # 自建服务器 #web服务器 #MinIO服务器启动与配置详解 #H5网页 #网页白屏 #H5页面空白 #资源加载问题 #打包部署后网页打不开 #HBuilderX #postgresql #SSE # AI翻译机 # 实时翻译 #VMWare Tool #microsoft #MC #心理健康服务平台 #心理健康系统 #心理服务平台 #心理健康小程序 #无人机 #Deepoc #具身模型 #开发板 #未来 #经验分享 #agent #ai大模型 #散列表 #哈希算法 #链表 #链表的销毁 #链表的排序 #链表倒置 #判断链表是否有环 #openEuler #GATT服务器 #蓝牙低功耗 #PyTorch #CUDA #Triton #adb #论文笔记 #硬件 #硬件工程 #dify #信号处理 #昇腾300I DUO #PowerBI #企业 #idea #intellij idea #数据挖掘 #5G #数据分析 #vnstat #运维开发 #memcache #文心一言 #AI智能体 #C2000 #TI #实时控制MCU #AI服务器电源 #AutoDL #攻防演练 #Java web #漏洞 #红队 #leetcode #Llama-Factory # 树莓派 # ARM架构 #H5 #跨域 #发布上线后跨域报错 #请求接口跨域问题解决 #跨域请求代理配置 #request浏览器跨域 #ci/cd #ShaderGraph #图形 #WT-2026-0001 #QVD-2026-4572 #smartermail #银河麒麟操作系统 #openssh #华为交换机 #信创终端 #Linux #VMware Workstation16 #服务器操作系统 #UDP的API使用 #处理器 #飞牛nas #fnos #支付 #bash #Modbus-TCP #智能体来了 #智能体对传统行业冲击 #行业转型 #AI赋能 #系统管理 #服务 #Socket网络编程 #管道Pipe #system V #RAG #LLM #chat #能源 #YOLO26 #目标检测 #远程开发 #扩展屏应用开发 #android runtime #SAP #ebs #metaerp #oracle ebs #muduo库 #uv #uvx #uv pip #npx #Ruff #pytest #postman #milvus #springboot #知识库 #机器学习 #html5 #计算几何 #斜率 #方向归一化 #叉积 #chrome #媒体 #Anaconda配置云虚拟环境 #交通物流 #C语言 #学术论文创作 #论文效率提升 #MBA论文写作 #政务 #手机h5网页浏览器 #安卓app #苹果ios APP #手机电脑开启摄像头并排查 #IO #rocketmq #selenium #fabric #密码学 #可信计算技术 #openHiTLS #TLCP #DTLCP #商用密码算法 #harmonyos #ArkUI #ArkTS #鸿蒙开发 #laravel #CPU #测评 #CCE #Dify-LLM #Flexus #系统安全 #Nacos #web #服务器繁忙 #go #Clawdbot #个人助理 #数字员工 #cursor #puppeteer #KMS #slmgr #连接数据库报错 #spine #智能家居 #POC #问答 #交付 #xlwings #Excel #mybatis #翻译 #spring cloud #bootstrap #nfs #iscsi #统信UOS #win10 #qemu #文件IO #输入输出流 #文件管理 #文件服务器 #jetty #信息与通信 #tcpdump #前端框架 #负载均衡 #kmeans #聚类 #YOLOv8 # Docker镜像 #SA-PEKS # 关键词猜测攻击 # 盲签名 # 限速机制 #ms-swift # 大模型 # 模型训练 #win11 #scanf #printf #getchar #putchar #cin #cout #大语言模型 #程序员 #Java #企业级存储 #网络设备 #Smokeping #pve #langchain #LangGraph #CLI #Python #JavaScript #langgraph.json #安卓 #ddos #vps #zotero #WebDAV #同步失败 #代理模式 #工具集 #大模型应用 #API调用 #PyInstaller打包运行 #服务端部署 #实时音视频 #业界资讯 #排序算法 #jdk #排序 # 高并发部署 #欧拉 #aiohttp #asyncio #异步 #数据仓库 #软件 #本地生活 #电商系统 #商城 #.netcore # IndexTTS 2.0 # 自动化运维 #https #LoRA # lora-scripts # 模型微调 #麒麟 #rdp #海外服务器安装宝塔面板 #大模型部署 #mindie #大模型推理 #Go并发 #高并发架构 #Goroutine #系统设计 #Dify #ARM架构 #鲲鹏 #.net #net core #kestrel #web-server #asp.net-core #大模型开发 #SSH反向隧道 # Miniconda # Jupyter远程访问 #集成学习 #chatgpt #EMC存储 #存储维护 #NetApp存储 #简单数论 #埃氏筛法 #codex #Harbor #yum #windows11 #系统修复 #PTP_1588 #gPTP #rtsp #转发 #Termux #Samba #三维 #3D #三维重建 #fpga开发 #CVE-2025-61686 #路径遍历高危漏洞 # 大模型推理 #rust #esp32教程 #鸿蒙PC #进程等待 #wait #waitpid #pdf #大模型教程 #AI大模型 #结构体 #Kylin-Server #国产操作系统 #服务器安装 #diskinfo # 磁盘健康 #LangFlow # 智能运维 # 性能瓶颈分析 #推荐算法 #流量监控 #devops #戴尔服务器 #戴尔730 #装系统 #青少年编程 #SMTP # 内容安全 # Qwen3Guard #junit #渗透测试 #黑客技术 #文件上传漏洞 #ThingsBoard MCP #clickhouse #代理 #平板 #零售 #智能硬件 #vncdotool #链接VNC服务器 #如何隐藏光标 #HeyGem # 服务器IP访问 # 端口映射 #CTF #A2A #GenAI #遛狗 #bug #r-tree #FHSS #arm64 #自动化运维 #插件 #开源软件 #DHCP #服务器解析漏洞 # 自动化部署 # VibeThinker #nodejs #NFC #智能公交 #服务器计费 #FP-增长 #GPU #算力建设 #数据安全 #注入漏洞 #wpf #串口服务器 #Modbus #MOXA #reactjs #web3 # 一锤定音 # 大模型微调 #SSH密钥 # CUDA # ControlMaster #练习 #基础练习 #数组 #循环 #九九乘法表 #计算机实现 #AI技术 #dynadot #域名 #ETL管道 #向量存储 #数据预处理 #DocumentReader #esb接口 #走处理类报异常 #ffmpeg #le audio #蓝牙 #低功耗音频 #通信 #连接 #arm #smtp #smtp服务器 #PHP #银河麒麟部署 #银河麒麟部署文档 #银河麒麟linux #银河麒麟linux部署教程 #serverless #ui #googlecloud #Qwen3-14B # 大模型部署 # 私有化AI #AI 推理 #NV #大剑师 #nodejs面试题 #vp9 #ServBay #SFTP # WebUI # 网络延迟 #ranger #MySQL8.0 #GB28181 #SIP信令 #SpringBoot #视频监控 #SSH跳板机 # Python3.11 #LVDS #高速ADC #DDR #word #驱动开发 #ESP32 # OTA升级 # 黄山派 #ansys #ansys问题解决办法 #screen命令 #MS #Materials #mariadb #Gunicorn #WSGI #Flask #并发模型 #容器化 #性能调优 #Emby #视频 #ambari #门禁 #梯控 #智能一卡通 #门禁一卡通 #消费一卡通 #智能梯控 #一卡通 #源代码管理 #elk #超时设置 #客户端/服务器 #网络编程 #挖矿 #Linux病毒 #sql注入 #ai编程 #机器人 #muduo #TcpServer #accept #高并发服务器 # 服务器配置 # GPU #ue5 # 高并发 #数据恢复 #视频恢复 #视频修复 #RAID5恢复 #流媒体服务器恢复 #AI-native #Gateway #认证服务器集成详解 #框架搭建 #状态模式 #dba #Tokio #国产化OS #react native #bond #服务器链路聚合 #网卡绑定 #WinSCP 下载安装教程 #FTP工具 #服务器文件传输 #excel # CosyVoice3 # 批量部署 #copilot # 批量管理 #ASR #SenseVoice #星图GPU # TTS服务器 # 键鼠锁定 #glibc #中间件 #远程连接 #vivado license #CVE-2025-68143 #CVE-2025-68144 #CVE-2025-68145 #node #zabbix #证书 #scrapy #AI写作 #winscp #ONLYOFFICE #MCP 服务器 #后端框架 #tensorflow #参数估计 #矩估计 #概率论 # 数字人系统 # 远程部署 #LE Audio #BAP #powerbi #agi #信息可视化 #claude code #code cli #ccusage #Node.js # child_process #scikit-learn #随机森林 #安全威胁分析 #仙盟创梦IDE #运维工具 #GLM-4.6V-Flash-WEB # AI视觉 # 本地部署 #网络攻击模型 #动态规划 #pyqt #dlms #dlms协议 #逻辑设备 #逻辑设置间权限 #企业微信 #3d #Minecraft #Minecraft服务器 #PaperMC #我的世界服务器 #ipmitool #BMC #C #STDIO传输 #SSE传输 #WebMVC #WebFlux #ARM服务器 # 多模态推理 #embedding #kong #Kong Audio #Kong Audio3 #KongAudio3 #空音3 #空音 #中国民乐 #人大金仓 #Kingbase #小艺 #鸿蒙 #搜索 #Spring AOP #程序人生 #paddleocr #wsl #产品经理 #就业 #CMake #Make #C/C++ #多进程 #python技巧 #Java程序员 #Java面试 #后端开发 #Spring源码 #Spring #V11 #kylinos #raid #raid阵列 #KMS激活 #numpy #CSDN #论文阅读 #软件工程 #Langchain-Chatchat # 国产化服务器 # 信创 #Host #SSRF #webpack #database #儿童AI #图像生成 #pjsip #LobeChat #vLLM #GPU加速 #分类 #SSH保活 #数字化转型 #实体经济 #商业模式 #软件开发 #数智红包 #商业变革 #创业干货 #人脸识别sdk #视频编解码 #人脸识别 #开源工具 #FASTMCP #ZooKeeper #ZooKeeper面试题 #面试宝典 #深入解析 #n8n解惑 #模型训练 #Tracker 服务器 #响应最快 #torrent 下载 #2026年 #Aria2 可用 #迅雷可用 #BT工具通用 #Zabbix #CosyVoice3 #语音合成 #高斯溅射 # 语音合成 #产品运营 #Puppet # IndexTTS2 # TTS #联机教程 #局域网联机 #局域网联机教程 #局域网游戏 # keep-alive #eureka #云服务器 #个人电脑 #mongodb #广播 #组播 #并发服务器 #x86_64 #数字人系统 #MC群组服务器 #uvicorn #uvloop #asgi #event #yolov12 #研究生life #gpu #nvcc #cuda #nvidia #其他 #unix #编程 #c++高并发 #百万并发 #CS2 #debian13 #信创国产化 #达梦数据库 #RXT4090显卡 #RTX4090 #深度学习服务器 #硬件选型 #SQL注入主机 #neo4j #NoSQL #SQL #uip #k8s #echarts ##租显卡 #树莓派 #温湿度监控 #WhatsApp通知 #IoT #MySQL # 服务器IP # 端口7860 #Reactor #短剧 #短剧小程序 #短剧系统 #微剧 #空间计算 #原型模式 #hibernate #nosql #VibeVoice # 云服务器 #汽车 #可撤销IBE #服务器辅助 #私钥更新 #安全性证明 #双线性Diffie-Hellman # 公钥认证 #CPU利用率 #数据访问 #gateway #Comate #I/O模型 #并发 #水平触发、边缘触发 #多路复用 #eclipse #servlet #CNAS #CMA #程序文件 #SSH复用 # 远程开发 #磁盘配额 #存储管理 #形考作业 #国家开放大学 #系统运维 #wireshark #网络安全大赛 #C++ UA Server #SDK #跨平台开发 #Aluminium #Google #云服务器选购 #Saas #线程 #机器视觉 #6D位姿 #UOS #海光K100 #统信 #outlook #错误代码2603 #无网络连接 #2603 #mssql #实时检测 #卷积神经网络 #lucene #DAG #Ubuntu服务器 #硬盘扩容 #命令行操作 #Docker #b树 #具身智能 #Fun-ASR # 语音识别 #HarmonyOS APP #密码 #firefox #safari # RTX 3090 #windbg分析蓝屏教程 #AI电商客服 #spring ai #oauth2 #memory mcp #Cursor #数据可视化 #网路编程 #nmodbus4类库使用教程 #docker-compose #目标跟踪 #rtmp #声源定位 #MUSIC #fs7TF # 远程访问 #c++20 #Buck #NVIDIA #算力 #交错并联 #DGX #ROS # 局域网访问 # 批量处理 #内存治理 #IFix # 高温监控 # 远程连接 #gerrit #opc ua #opc #npu # 环境迁移 #matplotlib #安全架构 #指针 #anaconda #虚拟环境 #远程软件 # GLM-TTS # 数据安全 #xshell #host key #TTS私有化 # IndexTTS # 音色克隆 #内网 # 跳板机 #blender #设计师 #图像处理 #游戏美术 #技术美术 # ARM服务器 #分布式数据库 #集中式数据库 #业务需求 #选型误 #编程助手 # Connection refused #代理服务器 #rsync # 数据同步 #ip #雨云服务器 #教程 #MCSM面板 #Apple AI #Apple 人工智能 #FoundationModel #Summarize #SwiftUI #ceph #多线程 #claudeCode #content7 #跳槽 #工作 #turn #网安应急响应 #odoo #微PE # GLM # 服务连通性 #HarmonyOS #azure #TLS协议 #HTTPS #漏洞修复 #运维安全 # 串口服务器 # NPort5630 #appche #TTS # GPU集群 #VS Code调试配置 #服务器开启 TLS v1.2 #IISCrypto 使用教程 #TLS 协议配置 #IIS 安全设置 #服务器运维工具 #ftp #sftp #uniapp #合法域名校验出错 #服务器域名配置不生效 #request域名配置 #已经配置好了但还是报错 #uniapp微信小程序 #YOLO识别 #YOLO环境搭建Windows #YOLO环境搭建Ubuntu # 轻量化镜像 # 边缘计算 #华为od #华为机试 #OpenHarmony #策略模式 #matlab #Python办公自动化 #Python办公 #SSH跳转 #Socket #套接字 #I/O多路复用 #字节序 #工程设计 #预混 #扩散 #燃烧知识 #层流 #湍流 #weston #x11 #x11显示服务器 #量子计算 #samba #RSO #机器人操作系统 #硬盘克隆 #DiskGenius #opc模拟服务器 #cpu #RWK35xx #语音流 #实时传输 #超算中心 #PBS #lsf #报表制作 #职场 #用数据讲故事 #语音生成 #AI部署 # ms-swift #PN 结 #服务器线程 # SSL通信 # 动态结构体 #JNI #pxe #log #lvs #adobe #STUN # TURN # NAT穿透 #MCP服务器注解 #异步支持 #方法筛选 #声明式编程 #自动筛选机制 #数据迁移 #MinIO #麦克风权限 #访问麦克风并录制音频 #麦克风录制音频后在线播放 #用户拒绝访问麦克风权限怎么办 #uniapp 安卓 苹果ios #将音频保存本地或上传服务器 #浏览器自动化 #python #express #cherry studio #gmssh #宝塔 #漏洞挖掘 #Exchange #free #vmstat #sar #sentinel #宝塔面板部署RustDesk #RustDesk远程控制手机 #手机远程控制 #系统安装 #铁路桥梁 #DIC技术 #箱梁试验 #裂纹监测 #四点弯曲 #可再生能源 #绿色算力 #风电 #SSH免密登录 #若依 #TRO #TRO侵权 #TRO和解 #Discord机器人 #云部署 #程序那些事 #AI应用编程 #r语言 #Xshell #Finalshell #生信 #生物信息学 #组学 #服务器IO模型 #非阻塞轮询模型 #多任务并发模型 #异步信号模型 #多路复用模型 # 黑屏模式 #前端开发 #EN4FE #领域驱动 #自由表达演说平台 #演说 #移动端h5网页 #调用浏览器摄像头并拍照 #开启摄像头权限 #拍照后查看与上传服务器端 #摄像头黑屏打不开问题 #AI Agent #开发者工具 #流程图 #图论 #WRF #WRFDA #RK3576 #瑞芯微 #硬件设计 #国产开源制品管理工具 #Hadess #一文上手 #蓝桥杯 #工业级串口服务器 #串口转以太网 #串口设备联网通讯模块 #串口服务器选型 #okhttp #IndexTTS2 # 阿里云安骑士 # 木马查杀 #范式 #计算机外设 #入侵 #日志排查 #Karalon #AI Test #视觉检测 #visual studio #remote-ssh #gRPC #注册中心 #健康医疗 #ET模式 #非阻塞 #高考 #iot #多模态 #微调 #超参 #LLamafactory #工程实践 #租显卡 #训练推理 #c #AI应用 #图像识别 #pencil #pencil.dev #设计 #轻量化 #低配服务器 #Beidou #北斗 #SSR #Anything-LLM #IDC服务器 #私有化部署 #gpt #API #taro #java大文件上传 #java大文件秒传 #java大文件上传下载 #java文件传输解决方案 #wps #Linux多线程 #bigtop #hdp #hue #kerberos #PyCharm # 远程调试 # YOLOFuse #simulink #docker安装seata #信息安全 #信息收集 #poll #Autodl私有云 #深度服务器配置 #Keycloak #Quarkus #AI编程需求分析 # 水冷服务器 # 风冷服务器 #nas #VoxCPM-1.5-TTS # 云端GPU # PyCharm宕机 #全链路优化 #实战教程 #传统行业 #Syslog #系统日志 #日志分析 #日志监控 #生产服务器问题查询 #日志过滤 #测速 #iperf #iperf3 # GLM-4.6V-Flash-WEB # AI部署 #everything #材料工程 #智能电视 #AB包 #VMware创建虚拟机 #远程更新 #缓存更新 #多指令适配 #物料关联计划 #AI生成 # outputs目录 # 自动化 #es安装 #挖漏洞 #攻击溯源 #stl #IIS Crypto #warp #HistoryServer #Spark #YARN #jobhistory #DooTask #sglang #ComfyUI # 推理服务器 #防毒面罩 #防尘面罩 #rabbitmq #m3u8 #HLS #移动端H5网页 #APP安卓苹果ios #监控画面 直播视频流 #Prometheus #esp32 arduino #决策树 #Hadoop #SSH Agent Forwarding # 容器化 #UEFI #BIOS #Legacy BIOS # REST API #内存接口 # 澜起科技 # 服务器主板 # 显卡驱动备份 #模拟退火算法 #计算机毕业设计 #程序定制 #毕设代做 #课设 #文件传输 #电脑文件传输 #电脑传输文件 #电脑怎么传输文件到另一台电脑 #电脑传输文件到另一台电脑 #身体实验室 #健康认知重构 #系统思维 #微行动 #NEAT效应 #亚健康自救 #ICT人 #云开发 #性能 #优化 #RAM #KMS 激活 #AI智能棋盘 #Rock Pi S #边缘计算 #nacos #银河麒麟aarch64 # 服务器迁移 # 回滚方案 #大模型入门 #homelab #Lattepanda #Jellyfin #Plex #Kodi #开关电源 #热敏电阻 #PTC热敏电阻 #云计算运维 #asp.net上传大文件 #iphone #TensorRT # 推理优化 #SSH别名 #BoringSSL #企业存储 #RustFS #对象存储 #高可用 #Jetty # 嵌入式服务器 #模版 #函数 #类 #笔试 #模块 # 权限修复 #ICE #OPCUA #群晖 #音乐 # 鲲鹏 #IntelliJ IDEA #Spring Boot #http头信息 #Coturn #TURN #log4j # HiChatBox # 离线AI #建筑缺陷 #红外 #数据集 #WEB #TCP服务器 #开发实战 #SMARC #ARM #全文检索 #银河麒麟服务器系统 # 代理转发 #阿里云RDS #coffeescript #模型上下文协议 #MultiServerMCPC #load_mcp_tools #load_mcp_prompt #文生视频 #WAN2.2 #AI视频生成 #游戏策划 #游戏程序 #用户体验 #markdown #建站 #Ubuntu #数据报系统 #数学建模 #2026年美赛C题代码 #2026年美赛 #智能制造 #供应链管理 #工业工程 #库存管理 #mtgsig #美团医药 #美团医药mtgsig #美团医药mtgsig1.2 #智慧城市 #反向代理 #WinDbg #Windows调试 #内存转储分析 #运维 #华为od机试 #华为od机考 #华为od最新上机考试题库 #华为OD题库 #华为OD机试双机位C卷 #od机考题库 #静脉曲张 #腿部健康 #运动 #Ascend #MindIE #FRP #上下文工程 #langgraph #意图识别 #边缘AI # Kontron # SMARC-sAMX8 #OpenAI #故障 #CA证书 #UDP套接字编程 #UDP协议 #网络测试 #二值化 #Canny边缘检测 #轮廓检测 #透视变换 #期刊 #SCI #面向对象 #基础语法 #标识符 #常量与变量 #数据类型 #运算符与表达式 #交换机 #三层交换机 #session #主板 #总体设计 #电源树 #框图 #数模美赛 #duckdb