Linux知识点 -- 网络编程套接字

news2024/7/6 19:16:55

Linux知识点 – 网络编程套接字

文章目录

  • Linux知识点 -- 网络编程套接字
  • 一、预备知识
    • 1.认识端口号
    • 2.套接字
    • 3.TCP协议与UDP协议
    • 4.网络字节序
  • 二、socket编程接口
    • 1.socket常见API
    • 2.sockaddr结构
  • 三、UDP套接字编程
    • 1.直接打印客户端信息
    • 2.执行客户端发来的指令
    • 3.多用户聊天
    • 4.在windows环境下运行客户端,与云服务器下的Linux服务端通信
  • 四、TCP套接字
    • 1.打印客户端信息并发回
    • 2.多线程版服务器
  • 五、关于地址转换函数
    • 1.字符串转in_addr的函数
    • 2.in_addr转字符串的函数


一、预备知识

1.认识端口号

端口号(port)是传输层协议的内容;

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP地址+端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用;
  • 源端口号就是发送数据的端口,目的端口号就是接收数据的端口;

注:一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定;

在平常使用的APP上,客户端软件发出的请求,通过网络传输到服务端软件进行处理;
真正的网络间通信,本质其实是进程间通信
将数据在主机间转发仅仅是手段,机器收到之后,需要将数据交付给指定的进程;

2.套接字

IP地址 + 端口号就是套接字;

3.TCP协议与UDP协议

TCP协议:

  • 传输层协议
  • 有连接(可理解为打电话,对方必须响应)
  • 可靠传输(为保证可靠性,需要更多的策略)
  • 面向字节流

UDP协议:

  • 传输层协议
  • 无连接(写信或发邮件)
  • 不可靠传输(使用成本更低;适合直播、视频网站)
  • 面向数据报

4.网络字节序

内存中的数据存储分大端和小端,因此网络通信中不同的主机也会有不同的大小端主机之间通信;
网络规定:所有网络数据,都必须是大端

网络字节序和主机字节序的转换库函数:
在这里插入图片描述
其中:

  • h表示host,n表示network,I表示32位长整数,s表示16位短整数;
  • 例如htonI表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送;
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回;

二、socket编程接口

1.socket常见API

在这里插入图片描述

2.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket;然而,各种网络协议的地址格式并不相同;

常见的套接字类型:

  • 域间socket
  • 原始socket
  • 网络socket

理论上是三种场景,对应三套接口;
但实际上所有的地址接口都是统一的;
所有接口传入的都是struct sockaddr这个类型的地址参数;
网络和域间套接字的前两个字节是不同的,在函数内部会进行判断;
在这里插入图片描述

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址;
  • IPv4、IPv6地址类型分别定义为常数AF_ INET、AF_INET6;这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
  • socket API可以都用struct sockaddr*类型表示,在使用的时候需要强制转化成sockaddr_in;这样的好处是程序的通用性可以接收IPv4, IPv6,以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

三、UDP套接字编程

1.直接打印客户端信息

udp_server.hpp

  • socket接口:创建套接字
    在这里插入图片描述
    创建套接字,建立一个通信的一端
    创建成功,返回文件描述符 ,但是UDP是非字节流的操作,不能使用之前的文件接口访问;失败返回-1,并设置errno;
    domain:域,套接字的类型;
    AF_INET是网络通信
    AF_LOCAL是本地通信
    在这里插入图片描述
    type:套接字通信种类,UDP是面向数据报,使用SOCK_DGRAM;
    SOCK_DGRAM:用户数据报
    在这里插入图片描述
    protocol:前两个参数确定,这个参数也确定了,一般写为0;

  • bind接口:绑定套接字
    在这里插入图片描述
    将用户设置的ip和port在内核中和我们当前的进程强关联;
    成功返回0,失败返回-1,设置errno;
    sockfd:套接字;
    sockaddr:通用addr结构,AF_INET网络通信使用sockaddr_in地址结构;
    要显示出来sockaddr_in类型,需包含头文件;

    在这里插入图片描述
    sockaddr_in结构体:
    在这里插入图片描述
    在这里插入图片描述
    其中,sin_port为端口号,sin_addr为ip地址,还需要设置一个sin_family成员,与套接字的类型一致;

  • IP地址格式转换
    “192.168.110.132” -> 点分十进制字符串风格的IP地址,每一个区域取值范围是[0-255]: 1字节 -> 4个区域;
    理论上,表示一个IP地址,其实4字节就够了;
    需要将点分十进制字符串风格的IP地址 <-> 4字节;
    Sin_addr其实是一个整数类型;
    在这里插入图片描述
    bzero接口:将指定长度的空间全部置0;
    先要将点分十进制字符串风格的IP地址 -> 4字节,再将4字节主机序列 -> 网络序列;
    有一套接口,可以一次帮我们做完这两件事情, 让服务器在工作过程中,可以从任意IP中获取数据;
    在这里插入图片描述
    其中,inet_addr接口就是将字符串IP地址转换为网络序列IP地址;

  • recvfrom:从网络中读取数据
    在这里插入图片描述
    buf:数据存储缓冲区
    len:缓冲区大小
    flags:读取方式,默认0为阻塞方式
    src_addr:输出型参数,拿到数据发送方的ip和port
    addrlen:输入输出型参数;输入: src_addr 缓冲区大小;输出: 实际读到的src_addr大小

  • 网络IP转主机IP
    在这里插入图片描述

  • sendto:向目标主机发送数据
    在这里插入图片描述
    dest_addr:目的主机地址
    addrlen:dest_addr的大小

  • 本地环回:127.0.0.1
    client和server发送数据只在本地协议栈中进行数据流动,不会把我们的数据发送到网络中,用于本地服务器的测试;

  • 服务器IP
    云服务器无法bind公网IP,对于服务器来讲,也不建议绑定确定的IP;
    INADDR_ANY宏默认为0,是让服务器在工作过程中,可以获取任意IP的数据;
    在这里插入图片描述
    只要是发送到这个服务器的端口的数据都可以获取,不再指定IP地址;
    服务端只需要指定端口号;

#ifndef _UDP_SERVER_HPP_
#define _UDP_SERVER_HPP_

#include"log.hpp"
#include<iostream>
#include<unordered_map>
#include<cstdio>
#include<string>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<queue>

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = "")
        : _port(port)
        , _ip(ip)
        , _sock(-1) // 套接字先初始化为-1
    {}

    bool initServer()
    {
        // 1.创建套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0); // AF_NET与PF_NET是一样的
        if(_sock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }

        //2.bind:将用户设置的ip和port在内核中和我们当前的进程强关联
        struct sockaddr_in local; // 本地主机地址
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET; // 与套接字类型一致
        // 服务器的IP和端口号未来也是要发送给对方主机的,要先将数据发送到网络
        local.sin_port = htons(_port); // 考虑大小端转换
        // 将主机IP转换成网络IP
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)// 绑定,须强转成同一类型struct sockaddr*
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(3);
        }
        logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
        return true;
    }

    //直接显示客户端发的消息
    void start()
    {
        // 服务器是永不退出的
        char buffer[SIZE];
        for(;;)
        {
            struct sockaddr_in peer; // 远端地址,输出型参数
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);   // 输入输出型参数
                                            // 输入时,大小为src_addr的大小
                                            // 输出时,值为实际读到的dst_addr大小
            // 读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                buffer[s] = 0; //目前的数据当作字符串
                uint16_t cli_port = ntohs(peer.sin_port); //输出型参数,从网络中来的
                std::string cli_ip = inet_ntoa(peer.sin_addr); // 网络4字节IP转为主机IP
                printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
            }
            //写回数据
            sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);
        }
    }


    ~UdpServer()
    {
        if(_sock >= 0)
        {
            close(_sock); // 析构关闭套接字
        }
    }

private:
    //一个服务器,一般需要ip地址和port(16位整数)
    uint16_t _port;
    std::string _ip;
    int _sock; // 套接字
};

#endif

udp_client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << "serverIP serverPort\n" << std::endl;
}


int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    //创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }

    // client要不要bind??要,但是一般client不会显示的bind,程序员不会自己bind
    // client是一个客户端 -> 普通人下载安装启动使用的-> 如果程序员自己bind了->
    // client 一定bind了一个固定的ip和port,万一,其他的客户端提前占用了这个port呢??
    // client一般不需要显示的bind指定port,而是让OS自动随机选择(什么时候做的呢?)
    std::string message;
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    char buffer[1024];
    while(true)
    {
        std::cout << "请输入你的信息# ";
        std::getline(std::cin, message);
        if(message == "quit") break;
        //发送消息
        //当client首次发送消息给服务器的时候,OS会自动给client bind服务器的IP和port
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        //接受回信
        ssize_t s = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }   
    close(sock);

    return 0;
}
  • client不需要bind特定的套接字
    client是一个客户端 -> 普通人下载安装启动使用的-> 如果程序员自己bind了->一定bind了一个固定的ip和port,万一,其他的客户端提前占用了这个port呢;
    client一般不需要显示的bind指定port,而是让OS自动随机选择;
    当client首次发送消息给服务器的时候,OS会自动给client bind服务器的IP和port;

udp_server.cc

#include"udp_server.hpp"
#include<memory>
#include<cstdlib>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << "port\n" << std::endl;
}

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

    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> svr(new UdpServer(port)); // 使用智能指针管理对象
    svr->initServer();
    svr->start();

    return 0;
}
  • 服务器不需要指定客户的IP地址获取数据;
    让服务器再工作过程中,可以获取任意IP的数据;
    只要是发送到这个服务器的端口的数据都可以获取,不再指定IP地址;
    服务端只需要指定端口号;

Log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志级别
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./threadpool.log"

// 完整的日志功能,至少需要:日志等级 时间 支持用户自定义(日志内容,文件行,文件名)

void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if (level == DEBUG)
        return;
#endif

    char stdBuffer[1024]; // 标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; // 自定义部分
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof(logBuffer), format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    // fprintf(fp, "%s %s\n", stdBuffer, logBuffer);
    // fclose(fp);

    printf("%s %s\n", stdBuffer, logBuffer);
}

上面的代码实现了服务器直接打印客户端发送的信息,并将信息在发回客户端;

  • netstat -anup指令:查看当前网络中的UDP协议服务器状态
    在这里插入图片描述

运行结果:
在这里插入图片描述

2.执行客户端发来的指令

在这里插入图片描述
popen接口可以执行传入的字符串command;
执行command,创建pipe管道进行进程间通信,fork子进程执行(exec
)command命令;
FILE可以将执行成果通过FILE指针进行读取;
*

udp_server.hpp中的start成员函数

    // 执行客户端指令
    void start()
    {
        // 服务器是永不退出的
        char buffer[SIZE];
        for (;;)
        {
            struct sockaddr_in peer; // 远端地址,输出型参数
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer); // 输入输出型参数
                                          // 输入时,大小为src_addr的大小
                                          // 输出时,值为实际读到的dst_addr大小
            char result[256];
            std::string cmd_echo;
            // 读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                buffer[s] = 0; // 目前的数据当作字符串
                if (strcasestr(buffer, "rm") != nullptr || strcasestr(buffer, "rmdir"))
                {
                    std::string err_message = "bad !";
                    std::cout << err_message << buffer << std::endl;
                    sendto(_sock, err_message.c_str(), err_message.size(), 0, (struct sockaddr *)&peer, len);
                    continue;
                }
                FILE* fp = popen(buffer, "r");// 执行指令
                if(nullptr == fp)
                {
                    logMessage(ERROR, "popen: %d:%s", errno, strerror(errno));
                    continue;
                }
                while(fgets(result, sizeof(result), fp) != nullptr) //通过fp读取执行结果
                {
                    cmd_echo += result;
                }
                fclose(fp);
            }
            // 写回数据
            sendto(_sock, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr *)&peer, len);
        }
    }

运行结果:
在这里插入图片描述

3.多用户聊天

服务器将返回的消息发送给所有用户,聊天室功能;
udp_server.hpp中的start函数

class UdpServer
{
public:
    void start()
    {
        // 服务器是永不退出的
        char buffer[SIZE];
        for (;;)
        {
            struct sockaddr_in peer; // 远端地址,输出型参数
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer); // 输入输出型参数
                                          // 输入时,大小为src_addr的大小
                                          // 输出时,值为实际读到的dst_addr大小
            char key[64];                 // 保存客户端地址信息
            // 读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                buffer[s] = 0; // 目前的数据当作字符串
                uint16_t cli_port = ntohs(peer.sin_port); // 获取端口号
                std::string cli_ip = inet_ntoa(peer.sin_addr); // 获取4字节ip地址
                snprintf(key, sizeof key, "%s-%u", cli_ip.c_str(), cli_port); //格式化打印到key中
                logMessage(NORMAL, "key: %s", key);
                auto it = _users.find(key);
                if(it == _users.end()) // 若用户不存在,则添加用户
                {
                    logMessage(NORMAL, "add new user : %s", key);
                    _users.insert({key, peer});
                }
            }
            for(auto &iter : _users) // 将消息推送给所有用户
            {
                std::string sendMessage = key;
                sendMessage += "# ";
                sendMessage += buffer; // 发回消息的时候加上用户信息
                logMessage(NORMAL, "push message to %s", iter.first.c_str());
                sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));
            }
        }
    }
    private:
        // 一个服务器,一般需要ip地址和port(16位整数)
        uint16_t _port;
        std::string _ip;
        int _sock; // 套接字
        std::unordered_map<std::string, struct sockaddr_in> _users; // 存储用户信息
    };

客户端一边收消息,一边发消息,多线程;
thread.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <cstdio>

typedef void *(*fun_t)(void *); // 定义函数指针类型,后面回调

class ThreadData // 线程信息结构体
{
public:
    void *_args;
    std::string _name;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void *args)
        : _func(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof(nameBuffer), "Thread-%d", num);
        _name = nameBuffer;

        _tdata._args = args;
        _tdata._name = _name;
    }

    void start() // 创建线程
    {
        pthread_create(&_tid, nullptr, _func, (void *)&_tdata); // 直接将_tdata作为参数传给回调函数
    }

    void join() // 线程等待
    {
        pthread_join(_tid, nullptr);
    }

    std::string name()
    {
        return _name;
    }

    ~Thread()
    {
    }

private:
    std::string _name;
    fun_t _func;
    ThreadData _tdata;
    pthread_t _tid;
};

udp_client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include "thread.hpp"

uint16_t server_port = 0;
std::string server_ip;

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << "serverIP serverPort\n"
              << std::endl;
}

static void *udpSend(void *args)
{
    int sock = *(int *)((ThreadData *)args)->_args;
    std::string name = ((ThreadData *)args)->_name;
    std::string message;
    struct sockaddr_in server; // 服务器地址
    memset(&server, 0, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    while (true)
    {
        std::cerr << "请输入你的信息# "; // 标准错误,fd == 2打印
        std::getline(std::cin, message);
        if (message == "quit")
        {
            break;
        }
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
    }
    return nullptr;
}

static void *udpRecv(void *args)
{
    int sock = *(int *)((ThreadData *)args)->_args;
    std::string name = ((ThreadData *)args)->_name;
    char buffer[1024];
    while(true)
    {
        memset(buffer, 0, sizeof buffer);
        struct sockaddr_in temp;
        socklen_t len = sizeof temp;
        ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }

    server_ip = argv[1];
    server_port = atoi(argv[2]);

    // client要不要bind??要,但是一般client不会显示的bind,程序员不会自己bind
    // client是一个客户端 -> 普通人下载安装启动使用的-> 如果程序员自己bind了->
    // client 一定bind了一个固定的ip和port,万一,其他的客户端提前占用了这个port呢??
    // client一般不需要显示的bind指定port,而是让OS自动随机选择(什么时候做的呢?)
    std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void *)&sock)); // 发送消息线程
    std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void *)&sock)); // 接收消息线程
    sender->start();
    recver->start();
    sender->join();
    recver->join();

    close(sock);

    return 0;
}

运行结果:
通过创建管道文件,让客户端发送信息和接收信息的会话分开:
在这里插入图片描述

4.在windows环境下运行客户端,与云服务器下的Linux服务端通信

windows下的套接字编程与Linux下的大致接口相同,只需要添加一些windows下独有的语句就可以:
udpClient.cpp

#pragma warning(disable:4996) //禁用报错
#include <WinSock2.h> //win套接字头文件
#include <iostream>
#include <string>
using namespace std;
#pragma comment(lib,"ws2_32.lib") //固定用法,引入win下的套接字库
uint16_t serverport = 8080;
std::string serverip = "120.78.126.148"; //云服务器公网ip
int main()
{
    // windows 独有的
    WSADATA WSAData;
    WORD sockVersion = MAKEWORD(2, 2); //选中库
    if (WSAStartup(sockVersion, &WSAData) != 0)
        return 0;
    SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (INVALID_SOCKET == clientSocket)
    {
        cout << "socket error!";
        return 0;
    }
    sockaddr_in dstAddr;
    dstAddr.sin_family = AF_INET;
    dstAddr.sin_port = htons(serverport);
    dstAddr.sin_addr.S_un.S_addr = inet_addr(serverip.c_str());
    char buffer[1024];
    while (true)
    {
        std::string message;
        std::cout << "请输入# ";
        std::getline(std::cin, message);
        sendto(clientSocket, message.c_str(), (int)message.size(), 0, (sockaddr*)&dstAddr, sizeof(dstAddr));
        struct sockaddr_in temp;
        int len = sizeof(temp);
        int s = recvfrom(clientSocket, buffer, sizeof buffer, 0, (sockaddr*)&temp, &len);
        if (s > 0)
        {
            buffer[s] = '\0';
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    // windows 独有
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}

运行结果:
在这里插入图片描述

四、TCP套接字

1.打印客户端信息并发回

(1)tcp_server.hpp

  • 套接字类型
    在这里插入图片描述
    由于TCP协议是面向字节流的协议,因此套接字的类型需选择SOCK_STREAM;
    在这里插入图片描述

  • 面向连接的协议
    因为TCP协议是面向连接的,当我们正式通信的时候,需要先建立连接;
    在这里插入图片描述
    将套接字状态设置为监听状态;
    backlog:全链接队列长度;
    成功返回0,失败返回-1,并设置errno;

  • netstat -antp指令:查看网络中的TCP协议服务器
    在这里插入图片描述

  • 获取连接
    在这里插入图片描述
    accept接口:获取与客户端的连接;
    addr:输出型参数,拿到客户端的ip和端口号;
    addlen:输入输出型参数,输入服务器addr大小,输出客户端addr大小;
    返回值:成功:返回套接字,这是真正进行IO服务的套接字;失败返回-1;
    传入的sockfd参数那个套接字只是获取底层的连接,是监听套接字

  • 读取消息
    TCP流式套接字可以直接使用read和write接口(recvfrom专用于UDP数据报读取);

单进程循环处理
一次处理一个客户端,处理完了一个,才能处理下一个;

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"

// 打印服务
static void service(int sock, const std::string &cli_ip, const uint16_t &cli_port)
{
    // 读取消息:TCP流式套接字可以直接使用read和write接口(recvfrom专用于UDP数据报读取)

    char buffer[1024];
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer - 1));
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << cli_ip << ":" << cli_port << "# " << buffer << std::endl;
        }
        else if (s == 0) // 对端关闭连接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", cli_ip.c_str(), cli_port);
            break;
        }
        else
        {
            logMessage(ERROR, "read sock error, %d:%s", errno, strerror(errno));
            break;
        }
        write(sock, buffer, strlen(buffer));
    }
}

class TcpServer
{
private:
    const static int gbacklog = 20;

public:
    TcpServer(uint16_t port, std::string ip = "")
        : _port(port), _ip(ip)
    {
    }

    void initServer()
    {
        // 1.创建套接字socket -- 进程和文件
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", _listensock);

        // 2.bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_listensock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3.因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }

    void start()
    {
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;                                               // 输出型参数,获取对方主机地址
            socklen_t len = sizeof src;                                           // 输入输出型参数,对方主机地址的长度
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len); // 得到真正进行IO服务的套接字
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue; // 获取连接失败后继续获取
            }
            // 获取连接成功了,通信对象的地址在accept函数的后两个参数中
            uint16_t cli_port = ntohs(src.sin_port);
            std::string cli_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link succsee, servicesock: %d | %s : %d |\n",
                       servicesock, cli_ip.c_str(), cli_port);
            // 开始进行通信服务
            // 单进程循环版 -- 一次处理一个客户端,处理完一个才能处理下一个
            service(servicesock, cli_ip, cli_port);
            close(servicesock);
        }
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensock; // 监听套接字
};

(2)tcp_server.cc

#include "tcp_server.hpp"
#include <memory>
static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    svr->initServer();
    svr->start();
    return 0;
}
  • 使用远程登陆工具telnet模拟客户端
    在这里插入图片描述
    连接服务端
    在这里插入图片描述
    按照提示按下ctrl + ]进入telnet
    在这里插入图片描述

运行结果
在这里插入图片描述
如果创建两个客户端
在这里插入图片描述
服务端只会响应第一个客户端,只有当第一个客户端退出时,第二个客户端才会被响应;

(3)多进程版服务器
tcp_server.hpp中的start成员函数
**父进程waitpid会阻塞进程,和单进程就没区别了;

  • 方案一:
    可以主动忽略SIGCHID信号让子进程自动释放僵尸状态;
    父进程关闭servicesock,因为每个进程可用的文件描述符都是有限的,这里关闭了,还有子进程的fd指向文件,因此不会有问题;
    如果不关闭,就会导致文件描述符泄露;
    void start()
    {
        signal(SIGCHLD, SIG_IGN); // 对于SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;                                               // 输出型参数,获取对方主机地址
            socklen_t len = sizeof src;                                           // 输入输出型参数,对方主机地址的长度
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len); // 得到真正进行IO服务的套接字
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue; // 获取连接失败后继续获取
            }
            // 获取连接成功了,通信对象的地址在accept函数的后两个参数中
            uint16_t cli_port = ntohs(src.sin_port);
            std::string cli_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link succsee, servicesock: %d | %s : %d |\n",
                       servicesock, cli_ip.c_str(), cli_port);
            // 开始进行通信服务

            //多进程版 -- 创建子进程
            //让子进程给新的连接提供服务,子进程是能够直接打开父进程曾经打开的文件fd的
            pid_t id = fork();
            assert(id != -1);
            if(id == 0)
            {
                //子进程,能够继承父进程的文件fd
                //子进程是来提供服务的,不需要知道监听socket
                close(_listensock);//关闭不需要的文件描述符
                service(servicesock, cli_ip, cli_port);
                exit(0); // 使用忽略SIGCHLD信号来自动退出僵尸状态
            }
            close(servicesock);
        }
    }

运行结果:
可以进行多客户端通信了;
在这里插入图片描述

  • 方案二(不忽略SIGCHLD信号)
    在子进程中再fork,创建孙子进程,让孙子进程执行service,子进程立马退出;
    孙子进程就变成孤儿进程,让OS领养,OS在孤儿进程退出的时候,由OS自动回收孤儿进程;
    void start()
    {
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;                                               // 输出型参数,获取对方主机地址
            socklen_t len = sizeof src;                                           // 输入输出型参数,对方主机地址的长度
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len); // 得到真正进行IO服务的套接字
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue; // 获取连接失败后继续获取
            }
            // 获取连接成功了,通信对象的地址在accept函数的后两个参数中
            uint16_t cli_port = ntohs(src.sin_port);
            std::string cli_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link succsee, servicesock: %d | %s : %d |\n",
                       servicesock, cli_ip.c_str(), cli_port);

            // 多进程版 -- 创建子进程
            // 让子进程给新的连接提供服务,子进程是能够直接打开父进程曾经打开的文件fd的
            pid_t id = fork();
            assert(id != -1);

            // 多进程版 -- 创建孙子进程
            if (id == 0)
            {
                // 子进程,能够继承父进程的文件fd
                // 子进程是来提供服务的,不需要知道监听socket
                close(_listensock); // 关闭不需要的文件描述符
                if(fork() > 0)
                {
                    exit(0);// 子进程调用fork,创建孙子进程,然后子进程退出
                }
                // 孙子进程在子进程退出后编程孤儿进程,OS领养,OS在孤儿进程退出后自动回收
                service(servicesock, cli_ip, cli_port);
                exit(0); // 使用忽略SIGCHLD信号来自动退出僵尸状态
            }
            waitpid(id, nullptr, 0); // 子进程及时退出,不会阻塞等待
            close(servicesock);
        }
    }

运行结果:
在这里插入图片描述

(4)客户端:
TCP的服务端需要bind,一定需要一个确定的port;
TCP的客户端不需要bind,一旦bind,就说明客户端绑定的是一个具体端口号,两个客户端可能是由不同公司写的,可能端口号会出现冲突;
需要让操作系统自动进行port选择;
客户端需要连接别人;

  • connect接口:连接特定的IP和port
    在这里插入图片描述
    后两个参数与sendto参数相同,目的主机的地址和地址的大小;
    成功返回0,失败返回-1;

  • send接口:tcp发送接口
    在这里插入图片描述
    flags默认为0

  • recv接口:tcp接收接口
    在这里插入图片描述

注:send和recv也可以使用read和write代替;

tcp_client.cc

#include "tcp_server.hpp"
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << "serverIP serverPort\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    bool alive = false; // 连接是否还存在
    int sock = 0;
    std::string line;
    while (true)
    {
        if (!alive) // 如果连接不存在,重新建立连接
        {
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock < 0)
            {
                std::cerr << "socket error" << std::endl;
                exit(2);
            }

            // client不需要显式bind,但是一定需要port
            // 要让OS自动进行port选择

            struct sockaddr_in server;
            memset(&server, 0, sizeof server);
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());
            if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
            {
                std::cerr << "connect error" << std::endl;
                exit(3);
            }
            std::cout << "connect success" << std::endl;
            alive = true;
        }
        std::cout << "请输入# ";
        std::getline(std::cin, line);
        if(line == "quit")
        {
            break;
        }
        ssize_t s = send(sock, line.c_str(), line.size(), 0); // 向服务器发送消息
        if(s > 0)
        {
            // 发送成功
            char buffer[1024];
            ssize_t l = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if(s > 0)
            {
                // 接收回信成功
                buffer[s] = 0;
                std::cout << "server 回显# " << buffer << std::endl;
            }
            else if(s == 0)
            {
                //接收到文件结尾
                alive = false;
                close(sock);
            }
        }
        else
        {
            // 发送失败,则重新开始建立连接
            alive = false;
            close(sock);
        }
    }

    return 0;
}

运行结果:
在这里插入图片描述

2.多线程版服务器

(1)每次连接时都创建新的线程
tcp_server.hpp

class ThreadData
{
public:
    int _sock;
    std::string _ip;
    uint16_t _port;
};

class TcpServer
{
private:
    const static int gbacklog = 20;

    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        service(td->_sock, td->_ip, td->_port);
        delete td;

        return nullptr;
    }

public:
    TcpServer(uint16_t port, std::string ip = "")
        : _port(port), _ip(ip)
    {
    }

    void initServer()
    {
        // 1.创建套接字socket -- 进程和文件
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", _listensock);

        // 2.bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_listensock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3.因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }
   
    // 多线程版
    void start()
    {
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;                                               // 输出型参数,获取对方主机地址
            socklen_t len = sizeof src;                                           // 输入输出型参数,对方主机地址的长度
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len); // 得到真正进行IO服务的套接字
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue; // 获取连接失败后继续获取
            }
            // 获取连接成功了,通信对象的地址在accept函数的后两个参数中
            uint16_t cli_port = ntohs(src.sin_port);
            std::string cli_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link succsee, servicesock: %d | %s : %d |\n",
                       servicesock, cli_ip.c_str(), cli_port);
            // 多线程
            ThreadData* td = new ThreadData();
            td->_sock = servicesock;
            td->_ip = cli_ip;
            td->_port = cli_port;
            pthread_t tid;
            // 多线程不需要关闭文件描述符,因为多线程共享文件描述符
            pthread_create(&tid, nullptr, threadRoutine, td);
        }
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensock; // 监听套接字
};

运行结果:
在这里插入图片描述

(2)使用线程池管理线程

引入之前写的线程池,具体代码见Linux知识点 – Linux多线程(四)
线程池代码中:
Task.hpp
更改了回调函数的类型;

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include "log.hpp"

//typedef std::function<void (int, const std::string&, const uint16_t&, const std::string&)> func_t;

//与上面的写法是等价的
using func_t = std::function<void (int, const std::string&, const uint16_t&, const std::string&)>;

class Task
{

public:
    Task() {}
    Task(int sock, const std::string ip, uint16_t port, func_t func) 
        : _sock(sock)
        , _ip(ip)
        , _port(port)
        , _func(func)
    {}

    void operator()(const std::string &name)
    {
        _func(_sock, _ip, _port, name);
    }

public:
    int _sock;
    std::string _ip;
    uint16_t _port;
    func_t _func;
};

tcp_server.hpp
更改了service回调函数和类内成员函数start,每次将任务push进任务队列,等待线程池处理;

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>
#include "thread-pool/log.hpp"
#include "thread-pool/threadPool.hpp"
#include "thread-pool/Task.hpp"

static void service(int sock, const std::string &cli_ip,
                    const uint16_t &cli_port, const std::string &thread_name)
{
    // 读取消息:TCP流式套接字可以直接使用read和write接口(recvfrom专用于UDP数据报读取)

    char buffer[1024];
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer - 1));
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << thread_name << "|" <<  cli_ip << ":" << cli_port << "# " << buffer << std::endl;
        }
        else if (s == 0) // 对端关闭连接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", cli_ip.c_str(), cli_port);
            break;
        }
        else
        {
            logMessage(ERROR, "read sock error, %d:%s", errno, strerror(errno));
            break;
        }
        write(sock, buffer, strlen(buffer));
    }
    close(sock); // 线程在回调函数中关闭不用的文件描述符
}


class TcpServer
{
private:
    const static int gbacklog = 20;

public:
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _listensock(-1)
        , _port(port)
        , _ip(ip)
        , _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {}

    void initServer()
    {
        // 1.创建套接字socket -- 进程和文件
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", _listensock);

        // 2.bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_listensock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3.因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }


    // 线程池
    void start()
    {
        _threadpool_ptr->run(); // 启动线程池
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;                                               // 输出型参数,获取对方主机地址
            socklen_t len = sizeof src;                                           // 输入输出型参数,对方主机地址的长度
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len); // 得到真正进行IO服务的套接字
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue; // 获取连接失败后继续获取
            }
            // 获取连接成功了,通信对象的地址在accept函数的后两个参数中
            uint16_t cli_port = ntohs(src.sin_port);
            std::string cli_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link succsee, servicesock: %d | %s : %d |\n",
                       servicesock, cli_ip.c_str(), cli_port);
            // 线程池版本
            Task t(servicesock, cli_ip, cli_port, service);
            _threadpool_ptr->pushTask(t);
        }
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensock; // 监听套接字
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr; // 线程池指针
};

其他代码都与之前的一致;
运行结果:
在这里插入图片描述
也可以传入其他的回调函数,完成不同的功能;

五、关于地址转换函数

1.字符串转in_addr的函数

在这里插入图片描述

2.in_addr转字符串的函数

在这里插入图片描述

  • 关于inet_ntoa
    因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果,因此inet_ntoa是线程不安全的;
    在多线程环境下,推荐使用inet_ ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/977726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ALBEF、VLMO、BLIP、BLIP2、InstructBLIP要点总结(WIP)

ALBEF&#xff08;ALign BEfore Fuse&#xff09; 为什么有5个loss&#xff1f; 两个ITC两个MIM1个ITM。ITM是基于ground truth的&#xff0c;必须知道一个pair是不是ground truth&#xff0c;同时ITM loss是用了hard negative&#xff0c;这个是和Momentum Distillation&…

优化爬虫效率:利用HTTP代理进行并发请求

网络爬虫作为一种自动化数据采集工具&#xff0c;广泛应用于数据挖掘、信息监测等领域。然而&#xff0c;随着互联网的发展和网站的增多&#xff0c;单个爬虫往往无法满足大规模数据采集的需求。为了提高爬虫的效率和性能&#xff0c;我们需要寻找优化方法。本文将介绍一种利用…

(位运算) 剑指 Offer 56 - I. 数组中数字出现的次数 ——【Leetcode每日一题】

❓剑指 Offer 56 - I. 数组中数字出现的次数 难度&#xff1a;中等 一个整型数组 nums 里除两个数字之外&#xff0c;其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是 O ( n ) O(n) O(n)&#xff0c;空间复杂度是 O ( 1 ) O(1) O(1)。 示例 …

开源对象存储系统minio部署配置与SpringBoot客户端整合访问

文章目录 1、MinIO安装部署1.1 下载 2、管理工具2.1、图形管理工具2.2、命令管理工具2.3、Java SDK管理工具 3、MinIO Server配置参数3.1、启动参数&#xff1a;3.2、环境变量3.3、Root验证参数 4、MinIO Client可用命令 官方介绍&#xff1a; MinIO 提供高性能、与S3 兼容的对…

MPDIoU: A Loss for Efficient and Accurate Bounding BoxRegression

MPDIoU: A Loss for Efficient and Accurate Bounding BoxRegression MPDIoU:一个有效和准确的边界框损失回归函数 摘要 边界框回归(Bounding box regression, BBR)广泛应用于目标检测和实例分割&#xff0c;是目标定位的重要步骤。然而&#xff0c;当预测框与边界框具有相同的…

vue3 + elementplus Cannot read properties of null (reading ‘isCE‘)

使用命令行直接下载的element-plus&#xff0c;使用时会报错。 卸载掉&#xff0c;然后在项目根目录下&#xff0c;使用vue ui安装依赖&#xff0c; 即可使用

逆向大漠插件/用VB6.0实现后台鼠标移动和后台鼠标左键点击

自动化设计软件&#xff0c;在一款做门的设计软件CypCut6.3 上实现了自动化勾选了 复选框。一切都是基于后台的。 Private Const GW_CHILD 5 Private Const GW_HWNDFIRST 0 Private Const GW_HWNDNEXT 2 Public Declare Function FindWindow Lib "user32" Alias &…

数据结构笔记:MX四叉树 VS PR 四叉树

个人理解&#xff0c;不一定对&#xff0c;还望批评指教&#xff01; 比如我们分别建一个数 MX四叉树PR四叉树插入(1.5,5.5)插入&#xff08;6.5&#xff0c;15.5&#xff09;插入&#xff08;9.5&#xff0c;5.5&#xff09;插入&#xff08;11.5&#xff0c;3.5&#xff09…

华为云云服务器评测|云耀云服务器实例基础使用实践

&#x1f996;我是Sam9029&#xff0c;一个前端 Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-JS学习,CSS学习,Vue-2领域博主 **&#x1f431;‍&#x1f409;&#x1f431;‍&#x1f409;恭喜你&#xff0c;若此文你认为写的不错&#xff0c;不要吝啬你的赞扬&#xff0c;求…

Java复习-20-接口(1)

接口的定义及使用 如果相对外部隐藏全部的实现细节&#xff0c;就要通过接口来实现。 接口的定义 使用interface关键字来定义。由于接口描述的是一个公共的定义标准&#xff0c;所以在接口之中所有的抽象方法的访问权限都为public interface IMessage{ // 为了区分接口&…

一步一步分析将数据响应式实现出来

写在前面 vue2 的数据响应式已经非常成熟且过时了&#xff0c;但是相信很多人还是对原理的东西一知半解&#xff0c;甚至还是不知道他究竟是怎么实现的&#xff0c;今天我们就试着一步一步分析看看响应式需要解决哪些问题&#xff0c;具体的问题难点是什么&#xff1f; 数据响应…

【计算机组成 课程笔记】5.1 处理器的设计步骤

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 5 - 1 - 501-处理器的设计步骤&#xff08;14-49--&#xff09;_哔哩哔哩_bilibili 处理器&#xff0c;或者说是CPU&#xff0c;是现代计算机中最为复杂的一个部件。不过先不要劝退&#xff0c;要设计一个简单但是…

1343. 大小为 K 且平均值大于等于阈值的子数组数目

1343. 大小为 K 且平均值大于等于阈值的子数组数目 C代码&#xff1a;滑动窗口 // 窗口长度固定、返回窗口平均值>threshold的窗口个数int numOfSubarrays(int* arr, int arrSize, int k, int threshold){int cnt 0;int l 0;int sum 0;for (int r 0; r < arrSize; r…

轻松制作玩具小程序商城

随着移动互联网的快速发展&#xff0c;小程序成为了各行各业的新宠儿。想要快速搭建一个属于自己的小程序商城吗&#xff1f;乔拓云平台将为你提供最简单的解决方案。下面就跟随我的步骤&#xff0c;一起来学习如何搭建一个玩具小程序商城吧&#xff01; 首先&#xff0c;我们需…

微机原理与技术(2)8086微处理器

8086微处理器 考点介绍功能和介绍后续会继续更新&#xff0c;点赞follow me继续学习 考点介绍 考点一: 掌握8086/8088CPU的功能构成及流水线技术&#xff0c;理解流水线管理规则。2.1 8086/8088 CPU的功能构成2.2 8086/8088CPU的流水线技术考点二:掌握8086/8088CPU寄存器的组成…

论文阅读_大模型_ToolLLM

英文名称: ToolLLM: Facilitating Large Language Models to Master 16000 Real-world APIs 中文名称: TOOLLLM&#xff1a;帮助大语言模型掌握16000多个真实世界的API 文章: http://arxiv.org/abs/2307.16789 代码: https://github.com/OpenBMB/ToolBench 作者: Yujia Qin 日期…

第66步 时间序列建模实战:ARIMA建模(SPSS)

基于WIN10的64位系统演示 一、写在前面 从这一期&#xff0c;我们使用SPSS进行SARIMA模型的构建。 同样&#xff0c;使用某省2005年1月至2016年12月AIDS死亡率的时间序列数据。 二、SPSS建立SARIMA实战 &#xff08;1&#xff09;录入数据和格式调整 双击打开IBM SPSS Sta…

Android 在TextView前面添加多个任意View且不影响换行

实现效果如下&#xff1a; 如上&#xff0c;将头像后面的东西看作一个整体&#xff0c;因为不能影响后面内容的换行&#xff0c;且前面控件的长度是可变的&#xff0c;所以采用自定义View的方法来实现&#xff1a; /*** CSDN深海呐 https://blog.csdn.net/qq_40945489/articl…

通俗易懂讲解大模型:Tokenizer

Tokenizer Tokenizer 是 NLP pipeline 的核心组件之一。Tokenizer 的目标是&#xff1a;将文本转换为模型可以处理的数据。模型只能处理数字&#xff0c;因此 Tokenizer 需要将文本输入转换为数字输入。 通常而言有三种类型的 Tokenizer &#xff1a;Word-based Tokenizer、Cha…

【漏洞复现】广联达办公OAsql+文件上传+弱口令

漏洞描述 广联达办公OA是一款综合办公自动化解决方案,旨在提高组织内部的工作效率和协作能力。它提供了一系列功能和工具,帮助企业管理和处理日常办公任务、流程和文档。默认弱口令admin password,后面就不提了。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当…