TCP_SOCKET编程实现

news2024/10/9 6:40:55

文章目录

  • 与UDP_SOCKET的区别
  • 第一代Tcp_Server
  • Tcp_Client
  • 第二代Tcp_Server
  • 第三代Tcp_server
  • 多线程版本Tcp_Server
  • 线程池版的Tcp_Server
    • 使用inet_ntop来解决线程安全问题
  • 业务逻辑编写
  • 总结
  • 补充说明&&业务代码完成
    • ping的真实作用
    • Translate编写
    • Transform业务代码
  • 整体总结

与UDP_SOCKET的区别

与udp大同小异
多了一些步骤
udp是不可靠的, 不连接的协议
tcp是面向连接的
c/s

谁来建立这个链接?

是client主动建立链接
如手机上的抖音打开, 淘宝打开等
服务器是在一直等待链接的到来, 好比餐厅老板等待客人到来

所以与udp的区别

  1. 设置socket为监听状态
    函数接口:
    在这里插入图片描述
    参数2后续解释

第一代Tcp_Server

tcp初始化操作, 都是固定套路
初始化代码实现:

  void Init()
    {
        // 1. 创建socket, file fd, 本质是文件
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建tcp套接字, 第一个参数不变, 第二个参数是tcp, 面向字节流
        if (_sockfd < 0)
        {
            lg.LogMessage(Fatal, "create socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
            exit(Fatal);
        }
        lg.LogMessage(Debug, "create socket success, sockfd: %d\n", _sockfd);
        // 2. 填充网络信息并绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_addr.s_addr = htonl(INADDR_ANY);//必须是0, 表示任意
        local.sin_family = AF_INET;//IPv4的网络套接字
        local.sin_port = htons(_port);
        local.sin_zero[0] = 0;//填充剩余部分, 必须置零
        if(bind(_sockfd, CONV(&local), sizeof(local) != 0))
        {
            lg.LogMessage(Fatal, "bind socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
            exit(Bind_Err);
        }
        lg.LogMessage(Debug, "bind socket success, sockfd: %d\n", _sockfd);
        // 3.设置socket为监听状态
        if(listen(_sockfd, default_backlog) != 0)
        {
            lg.LogMessage(Fatal, "listen socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
            exit(Listen_Err);
        }
        lg.LogMessage(Debug, "listen socket success, sockfd: %d\n", _sockfd);
  1. tcp_server获取链接
    在这里插入图片描述

后两个参数是输入输出型, 更强调输出,
这个参数相当于udp的recvfrom

在这里插入图片描述

他的返回值成功会返回一个非0的文件描述符
udp里面, sockfd只有一个

  1. 但是在tcp这里, 会新增一个文件描述符
    eg: 门口拉客的 = 旧的sockfd
    店内传菜的 = 新创建的sockfd
    使用旧的sockfd和client进行获取链接
    使用新的sockfd和client进行通信
    这样看来, 旧的sockfd只用于listen
    新的sockfd才是真的sockfd

  2. udp是面向数据报, 而tcp是面向字节流, 这和文件, 管道的特性一模一样
    Start启动完成

 void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            // 4. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensock, CONV(&peer), &len);// 进行获取链接
            if(sockfd < 0)
            {
                lg.LogMessage(Warning, "accept socket error, errno code: %d, error string: %s\n", errno, strerror(errno));
                continue;// 获取失败则继续获取
            }
            lg.LogMessage(Debug, "accept socket success, get a new sockfd: %d\n", sockfd);
        
            // 5. 提供服务
            Service(sockfd);
            close(sockfd);
        } 
    }

    // 这个sockfd表示连接是全双工的
    void Service(int sockfd)
    {
        char buffer[1024];
        while(true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if(n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if(n == 0)// read如果为0, 文件中表示读到了结尾, 这边表示对端关闭了连接(与管道的情况一模一样)
            {
                lg.LogMessage(Info, "client quit...\n");
                break;
            }
            else
            {
                lg.LogMessage(Error, "read error, errno code: %d, error string: %s\n", errno, strerror(errno));
                break;
            }
        }
    }

  1. 查看结果
    在这里插入图片描述
    tcp_client编写
    与udp的差别, tcp在创建了sockfd之后, 只需要建立连接

Tcp_Client

与udp的差别, tcp在创建了sockfd之后, 只需要建立连接

  1. connect连接
    在这里插入图片描述

  2. 文件流进行消息读写

 // 2. client必须要ip和port, 但是不需要显示绑定, client 系统随机端口
	    // tcp发起链接的时候, 被OS自动进行本地绑定
	    // 建立连接connect
	    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());
	    // 换一个接口 ipv4转二进制 p当成process n是网络 -- 不太准确, 但是好记
	    inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // 1. IPV4--> 四字节ip 2. 进程转网络序列
	    int n = connect(sockfd, CONV(&server), sizeof(server)); // 这里自动进行bind
	    if (n < 0)
	    {
	        cerr << "connect error" << endl;
	        return 2;
	    }
	    // 3. 发送数据
	    // 与服务器进行持续通信的循环
	    while (true)
	    {
	        // 用户输入的消息
	        string message;
	        // 提示用户输入信息
	        cout << "Please Enter# ";
	        // 读取用户输入的整行文本
	        getline(cin, message);
	        // 尝试向服务器发送消息
	        ssize_t n = write(sockfd, message.c_str(), message.size());
	        // 检查消息是否成功发送
	        if (n > 0)
	        {
	            // 用于接收服务器响应的缓冲区
	            char buffer[1024];
	            // 从服务器读取响应, sockfd读写都用, 更加说明是全双工
	            ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);
	            // 检查是否接收到响应
	            if (m > 0)
	            {
	                // 确保响应字符串以空字符结尾
	                buffer[m] = 0;
	                // 打印服务器的响应
	                std::cout << "get a echo message# " << buffer << std::endl;
	            }
	            // 检查服务器是否关闭连接
	            else if (m == 0)
	            {
	                // 服务器关闭时打印信息并退出循环
	                std::cout << "server close!" << std::endl;
	                break;
	            }
	        }
	        // 发送消息时发生错误
	        else
	        {
	            // 打印错误信息并退出循环
	            cerr << "write error" << endl;
	            break;
	        }
	    }
	    close(sockfd);

进行测试:

在这里插入图片描述

结果说明
1. 运行后查看netstat -ntp 有两个服务, 不是一个吗?
原因是, 现在client和server在一台机器上, 所有有两个, 将来如果是在一台机器上, 那就只有一个
在这里插入图片描述2. 这样的新创建的client因为是单进程, 所以新建的client 会阻塞, 发的消息也会阻塞, 当旧的client关闭, 新的client会瞬间显示阻塞的信息, 这个阻塞的极限是多少, 由我们服务器的缓冲区决定
在这里插入图片描述

可以看到, 消息被输出后, 一部分成功输出, 另一部分write失败, 导致溢出到shell中输出
3. 服务器断线重连实现:

a. 将上述的工作放到visitServer函数中
然后, 进行断线重连的操作

int count = 1;
		    while(count <= ReTry_count)
		    {
		        bool result = visitServer(serverip, serverport);
		        if(result) break;
		        else
		        {
		            // 重连操作
		            sleep(1);
		            cout << "reconnecting..., count: " << count++ << endl;
		        }
		    }
		    if(count > ReTry_count) 
		    {
		        cout << "reconnect failed" << endl;
		    }

b. 有时client端会在创建后, server退出, 此时client重连, 让他实现从1次重连开始
在这里插入图片描述

像这样,就需要修改代码
只需让他多加一个参数, 在这个函数内返回false时, 让count = 1即可, 这个意义不太大, 有了解即可, 因为现实情况是远比当前复杂, 有可能是客户端断线…等等
在这里插入图片描述

  1. tcp_server启动有时候会出现绑定失败的情况
    在这里插入图片描述

原理先不解释, 先说解决方式
setsockopt
在这里插入图片描述

写在server创建listensockfd那里
在这里插入图片描述

第二代Tcp_Server

多进程版本的相互通信
对于一个父进程, 他有他自己的文件描述符表, 且表中的每个信息指向它对应的struct file, 创建子进程之后, 会有多个表中的信息指向同一个struct file, 所以可以像管道那样, 对于父子进程关闭不需要的文件fd
在这里插入图片描述

第三代Tcp_server

信号版本的Tcp_server
在linux 中, 如果对SIGCHLD设置为SIG_IGN, 则会自动回收子进程, 不会wait阻塞住
所以开局设置signal
在这里插入图片描述

然后创建子进程方面默认执行
在这里插入图片描述

因为设置了信号机制, 所以也不用wait

子进程的退出会自动OS回收, 如果不设置信号, 单纯使用上述的代码, 会引起僵尸进程问题

每次创建进程都会消耗时间, 推荐使用进程池来使用, 这边就不进行讲述
问题:
先创建的子进程, 再进行的链接, 进程看不到sockfd,怎么办?
在父子进程之间传递文件描述符的实现, 一个比较老的功能, 可以通过这个解决问题

多线程版本Tcp_Server

相比多进程, 更加的方便, 也不需要文件描述符的传递, 也不需要关闭所谓的fd, 主子线程共享文件描述符表
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

完成之后, 为什么有报错?

在这里插入图片描述

主要是因为类型不匹配导致的
可以考虑使用std::bind来适配成员函数的调用格式,但是更推荐重构成员函数为静态函数或全局函数
传参
在这里插入图片描述

定义实现
在这里插入图片描述

封装
在这里插入图片描述

结果演示

在这里插入图片描述
每加一个线程, 文件描述符表也会增加
链接来了, 才创建线程, 还是慢, 所以使用线程池, 且加上client的信息, 通过accept函数获取, 后两个参数也有输出功能

线程池版的Tcp_Server

先加client信息的处理
使用InetAddr.hpp获取属性
相关代码改动

在这里插入图片描述

在这里插入图片描述
完成之后, 进行验证
在这里插入图片描述

相关问题
在这里插入图片描述

使用inet_ntop来解决线程安全问题

这个转IP的函数返回值是一个char*, 未来的操作可能会有线程安全问题

在这里插入图片描述
他会在函数内部申请一个静态的内存地址, 然后返回, 所以他是一个可重入函数, 存在线程安全问题
应当减少使用, 推荐使用inet_ntop, 这个函数允许我们自己维护一个缓冲区, 而不是函数内部自己去申请
可以有效避免线程安全问题
在这里插入图片描述
在这里插入图片描述
多线程修改:
防止命名冲突, 将threadpool放在ThreadNs里面, 然后再TcpServer的构造函数里面
在这里插入图片描述

下面进行修改代码, 让server能被所有线程就进行访问
往后的任务都是线程池中的线程来接受任务, 进行处理, 而不是创建新的线程
下面实现代码让线程来执行任务

这个任务可以使用Task类来进行处理, 但是也可以使用function来进行处理
这样的方式比较简单
在这里插入图片描述
下面用这个包装器来完成任务
未来获取新的文件文件描述符信息等后, 可以直接进行如下构建
在这里插入图片描述再这样修改完成之后, 因为线程池中的线程数目是固定的, 所以我们的client链接数目有限, 当超过限制的client数目时, 就会出现, 连不上的情况. 同时, 为别人提供的服务是死循环式对我们的server端的压力是有点大的, 所以在一般我们很少去这样写代码

现在对他进行调整, 让它变得实际有用一点, 而不是像现在这样进行通信
也就是说, 不能让我们的服务像是死循环这样一直执行, 而是实现一个业务逻辑
这边的策略是实现一个英译汉的功能,要利用I/O, 同时引入unordered_map
定义回调函数(后期可能进行修改)
在这里插入图片描述
现在只需要Tcp_server进行IO就可以了
然后构建业务逻辑

业务逻辑编写

在这里插入图片描述
在这里存放所有的任务
接下来给Tcp_Server一个register功能
在这里插入图片描述
Read方式是进行读取用户第一次输入必须是输入他想要的功能
在 ping Translate Transform之间三选一
read看是读取了哪个结果
在这里插入图片描述进行一个Routine 的路由功能, 实现read到对应的内容给type, 好实现对应的功能, 对应的功能是放在funcs中的
在这里插入图片描述然后, 线程在启动执行的时候, 将对应的sockfd 的功能和Routine进行绑定

在这里插入图片描述
然后, 服务器创建出来之后(这一步在Main.cc文件中), 执行注册服务
在这里插入图片描述
注册之后, 执行对应的服务, 以便调用时执行对应的回调函数

这个register进行添加对应的name和方法到map
然后在进行Start的时候, 将要执行的方法和对应的用户输入名称之间的匹配调用, 此时Ping之类的函数才算是被真正的执行

Ping函数要做的事就是之前在Tcp_server里面, server方法要做的事

在这里插入图片描述然后在修改Tcp_Client

在这里插入图片描述
完成代码进行执行查看, 可以正常执行
server-log
在这里插入图片描述

client-log
在这里插入图片描述
现在补充其他的路由信息,进行基本的完善
在这里插入图片描述
在这里插入图片描述具体功能后续进行补齐

现在ping的作用事是想实现一下一次ping之后的结果, 所以还要进行修改代码
在这里插入图片描述然后因为之前是将Sockfd的关闭放在tcp_server的ThreadData类的析构函数中的, 但是现在已经不需要这个类了, 所以还要进行文件描述符的关闭操作, 这个操作可以放在这个回调函数执行完毕的后面

在这里插入图片描述
下面继续执行代码

在这里插入图片描述
可以看到在一次运行结束之后, 这个client会进行退出, 主要是因为当期的ping服务已经变成了短服务,只需要执行一次, 而不是像之前那样的死循环

现在进行总结一下

总结

未来服务可能部署在云服务器上面, 如何在未来的某一时刻知道这个服务器是否健康的呢?

可以定期(30s)给服务器发送一个最小服务请求, 如果得到回复, 那么这个服务就是正常的

这个机制, 我们称为心跳机制, 我们可以通过client->服务器, 同时也可以反向的得到server->client
而这个编写的Ping, 其实就是对心跳的一个响应机制, 用于检测服务器是否是正常的

补充说明&&业务代码完成

ping的真实作用

在这里插入图片描述Interact是进行交互功能, 它可以进行心跳机制的检测, 读消息是out, 发消息是in, 以此完成交互
在这里插入图片描述
下面进行Translate

Translate编写

首先, 我们到此为止,所有的代码都是网络
在这里插入图片描述
下面是业务代码, 顺便完成Translate
为了方便, 可以将单词加载在resource的dict.txt文件内, 进行文件操作

在这里插入图片描述

然后构建Translate.hpp业务逻辑代码
在这里插入图片描述
定义正常的常变量
在这里插入图片描述
字典路径, 当前字典, 当前字典读取的内容
然后是构造函数和加载字典的方法
在这里插入图片描述然后是内部函数

在这里插入图片描述
现在可以DEBUG一下, 看看有没有读取成功
推荐使用条件编译
在这里插入图片描述
但是这个是.hpp文件, 所以不能这样进行条件编译操作
可以创建一个test.cc来进行debug在这里插入图片描述
在这里插入图片描述
说明我们在Translate的构造函数, 读取字典函数, private变量都没有问题

然后进行分割功能
在这里插入图片描述做完分割, 那么可以对这个Debug进行修改

在这里插入图片描述
在这里插入图片描述
未来的加载词库, 只需要往dict.txt中加就行了. 当然更推荐将这个Translate类改为单例模式, 这边就不做处理

那么如何将这个Translate服务于网络进行结合呢?
那就到了Translate调用
首先定义全局变量(改为单例最好)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
所以未来服务首先都是进行注册的

Transform业务代码

在这里插入图片描述
在这里插入图片描述
将1按照OP的格式转化为result存到1里面
在这里插入图片描述
代码完成, 对于defaultserver的部分修改就不演示

整体总结

  1. IO类函数, write/read其实他们已经做了转网络序列的处理
  2. udp :数据包 tcp: 面向字节流
    a. 区别:
    1). TCp代码中我们使用read/write目前是有BUG的(下节讲解), TCP要进行正确读写, 必须结合用户协议
    2). udp-----数据和数据是有边界的, sendto只发了一次, 对端对应对端, recvfrom一次----面向数据报
    3). tcp------write->1,10, 100->接收方可能一次收完, 也可能是很多次, 但无论是多少次,都和对方发送无关—面向字节流
    b.udp就好比是一封信件, 只能一封一封去读
    c. tcp是全读, 然后进行手动分割
  3. 网络服务在真正运行时, 必须在Linux中以守护进程(精灵进程)的形式进行运行

要完成上述工作, 应当利用守护进程, 下篇讲解~~~~~

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

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

相关文章

基于Web的实时动作捕捉工具

随着Web技术的不断发展,实时动作捕捉已经成为可能。一款名为MiKaPo的基于Web的实时动作捕捉工具应运而生,它能够捕捉视频中的人物动作并将其应用到3D模型上。本文将详细介绍MiKaPo的核心功能、技术栈、工作原理以及其优势和应用场景。 1. 概述 MiKaPo是一款基于Web的实时动…

gaussdb hccdp认证模拟题(单选)

1.在GaussDB逻辑架构中&#xff0c;由以下选项中的哪一个组件来负责提供集群日常运维、配置管理的管理接口、工具&#xff1f;(1 分) A. CN B. DN C. GTM D. OM --D 2.在以下命令中&#xff0c;使用以下哪一个选项中的命令可以以自定义归档形式导出表t1的定义&#xf…

◇【论文_20181020 v6】广义优势估计器 (generalized advantage estimator, GAE)

https://arxiv.org/abs/1506.02438 ICLR 2016 加州伯克利 电子工程与计算机科学系 High-Dimensional Continuous Control Using Generalized Advantage Estimation 文章目录 摘要1 引言2 预备知识3 优势函数估计4 解释为 奖励设计reward shaping5 价值函数估计6 实验6.1 策略优…

计算机毕业设计-自主完成指南

计算机毕业设计通常都涉及到较为复杂的软件系统&#xff0c;许多同学在选定课题之后&#xff0c;往往处于一种懵懂的状态&#xff0c;不知道如何着手开展工作。现在&#xff0c;让我们转换一种视角&#xff0c;把毕设想象成自己逐步开发的一款产品。最终在答辩环节&#xff0c;…

0,Verilog基础专栏说明

给大家推荐一个好用的Verilog基础训练网站HDL Bits&#xff0c;专用于Verilog语言的练习&#xff0c;非常丰富并且包含仿真。 注&#xff1a;本专栏内容涉及到的Verilog相关题目均为HDL Bits网站上内容&#xff0c;解题代码均为原创。 1&#xff0c;章节分类 1&#xff0c;V…

Qt设计登录界面

优化登录框&#xff1a; 将两个按钮连接到槽函数 在构造函数中定义 connect(this->btn1,&QPushButton::clicked,this,&Logon::my_slot);connect(this->btn2,&QPushButton::clicked,this,&Logon::my_cancel); 定义登录按钮连接的槽函数 void Logon::my…

【物流配送中心选址问题】基于退火算法混合粒子群算法

课题名称&#xff1a; 基于退火算法混合粒子群算法的物流配送中心选址问题 改进方向&#xff1a;模拟退火算法优化粒子群算法 代码获取方式&#xff08;付费&#xff09;&#xff1a; 模型说明&#xff1a; 待补充 Matlab仿真结果&#xff1a; 1. 模型优化后的仿真结果 2…

【笔记】Day1的代码总结

代码中自定义接口AuthFilter的实现用了自定义类名称ManagerTokenGatewayFilterFactory继承抽象类抽象网关过滤器AbstractGatewayFilterFactory 用来实现AuthFilter接口的auth()方法 白名单路径&#xff1a;写在.yml文件中的sl.noAuthPath中

844.比较含退格的字符串

题目:844. 比较含退格的字符串 - 力扣&#xff08;LeetCode&#xff09; 思路:首先要知道这个#的意思是可以将前面一个数清空&#xff0c;(当时 我以为是将前面一串清空。。。看了半天)&#xff0c;由于 # 号只会消除左边的一个字符&#xff0c;所以对右边的字符无影响&#x…

超越GPT-4的视觉与文本理解能力,开源多模态模型领跑者 - Molmo

Molmo是由艾伦人工智能研究所&#xff08;Ai2&#xff09;发布的一系列多模态人工智能模型&#xff0c;旨在提高开放系统在性能上与专有系统&#xff08;如商业模型&#xff09;之间的竞争力。以下是对Molmo的详细总结&#xff1a; Molmo是什么&#xff1a; Molmo是基于Qwen2和…

分布式事务讲解 - 2PC、3PC、TCC

分布式事务讲解 - 2PC、3PC、TCC 前置知识 BASE理论&#xff1a; BASE是Basically Availbale(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)三个词语的缩写。BASE理论是对CAP理论中AP的一个扩展&#xff0c;通过牺牲强一致性来获得可用性&#xff0c;当…

2024最新分别用sklearn和NumPy设计k-近邻法对鸢尾花数据集进行分类(包含详细注解与可视化结果)

本文章代码实现以下功能&#xff1a; 利用sklearn设计实现k-近邻法。 利用NumPy设计实现k-近邻法。 将设计的k-近邻法对鸢尾花数据集进行分类&#xff0c;通过准确率来验证所设计算法的正确性&#xff0c;并将分类结果可视化。 评估k取不同值时算法的精度&#xff0c;并通过…

基于SpringBoot的轻量级CRM管理系统+搭建教程

运行环境&#xff1a;jdk8 IntelliJ IDEA maven 宝塔面板 技术框架&#xff1a;SpringBoot lombok MyBatis 分页助手 freemarker SpringMVC SpringMail 系统功能: 这是一套轻量级的crm管理系统源码&#xff0c;基于SSM的SpringBoot架构。 这套源码用到很多潮流技术…

清华大模型公开课第二季 | Lecture 2 神经网络与大模型基础 Part 1

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结&#xff0c;2小时视频&#xff0c;5分钟阅读&#xff0c;加速内容学习与传播。 大纲 引言 课程介绍 主讲人介绍 课程内容概述 神经网络基础知识 神经网络的定义和结构 神经元的基本单元 多维输入和权重…

从《被程序员耽搁的外卖员》看IT就业前景

《被程序员耽搁的外卖员》这部作品乍一看&#xff0c;似乎只是一个轻松幽默的故事&#xff0c;讲述一位外卖员因为学习编程而改变生活轨迹的小故事。然而&#xff0c;它在反映社会现实、揭示IT行业就业前景方面具有诸多启示。本文旨在通过此故事来分析当前IT就业的现状和未来发…

Spring Boot读取resources目录下文件(打成jar可用),并放入Guava缓存

1、文件所在位置&#xff1a; 2、需要Guava依赖&#xff1a; <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency>3、启动时就读取放入缓存的代码&#xf…

​Leetcode 746. 使用最小花费爬楼梯​ 入门dp C++实现

问题&#xff1a;Leetcode 746. 使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你…

Linux源码阅读笔记-以太网驱动分析

驱动框架 Linux 内核网络设备驱动框架分别为四个模块&#xff0c;分别为网络协议借口模块、网络设备接口模块、设备驱动功能模块和网络设备与媒介模块。具体视图如下&#xff1a; 网络协议接口模块&#xff1a;主要功能 网络接口卡接收和发送数据在 Linux 内核当中处理流程如下…

LoRA技术详解---附实战代码

LoRA技术详解—附实战代码 引言 随着大语言模型规模的不断扩大&#xff0c;如何高效地对这些模型进行微调成为了一个重要的技术挑战。Low-Rank Adaptation&#xff08;LoRA&#xff09;技术应运而生&#xff0c;它通过巧妙的低秩分解方法&#xff0c;显著减少了模型微调时需要…

UNIAPP popper气泡弹层【unibest框架下】vue3+typescript

看了下市场的代码&#xff0c;要么写的不怎么好&#xff0c;要么过于复杂。于是把市场的代码下下来了自己改。200行代码撸了个弹出层组件。兼容H5和APP。 功能&#xff1a; 1)只支持上下左右4个方向的弹层不支持侧边靠齐 2)不对屏幕边界适配 3)支持弹层外边点击自动隐藏 4)支持…