【Linux】Socket编程-TCP构建自己的C++服务器

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

  • 一:🔥 Socket 编程 TCP
    • 🦋 TCP socket API 详解
    • 🦋 多线程远程命令执行
    • 🦋 网络版计算器(应用层自定义协议与序列化)
  • 二:🔥 共勉

一:🔥 Socket 编程 TCP

🦋 TCP socket API 详解

下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中
socket

#include <sys/types.h>
#include <sys/socket.h>

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

domain:/ 协议家族
	AF_INET      IPv4 Internet protocols
	AF_INET6     IPv6 Internet protocols

type: 报文类型
    SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
    SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.

protocol: 传输层类型
	默认为0

On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set appropriately.

bind

#include <sys/types.h>
#include <sys/socket.h>

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.


// 2. 填充网络信息,并bind绑定
// 2.1 没有把socket信息设置进入内核
struct sockaddr_in local;
bzero(&local, sizeof(local));       // string.h
local.sin_family = AF_INET;
local.sin_port = ::htons(_port);   // 要被发送给对方,既要发送到网络中!   主机序列转换为网络序列 大小端转换 网络中都是大端 #include <arpa/inet.h>
local.sin_addr.s_addr = ::inet_addr(_ip.c_str());    // 1. string ip -> 4bytes 2. network order   #include <sys/socket.h>    #include <netinet/in.h>  #include <arpa/inet.h>
local.sin_addr.s_addr = INADDR_ANY;

// 2.1 bind    这里设置进入内核
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());

📚 我们之前在调用socket的时候,明明已经填充了一次 AF_INET, 为什么这里还需要一次呢?

创建套接字的时候填充的 AF_INET 是给操作系统文件系统里的网络文件接口,告诉我们的操作系统我们要创建一个网络的套接字。

这里则是用来填充 sockaddr_in 网络信息,只有套接字的结构和这里的结构一样,操作系统才能绑定成功。

📚 必带四件套

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

listen

#include <sys/types.h>          
#include <sys/socket.h>
       
int listen(int sockfd, int backlog);

sockfd:			 指定的套接字
backlog				 等待连接队列的最大长度。

On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

listen() 声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大 (一般是 5)

accept

#include <sys/types.h>      
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:			 指定的套接字
  • 三次握手完成后, 服务器调用 accept() 接受连接;
  • 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址;
  • addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

connect

#include <sys/types.h>       
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:			 指定的套接字
  • 客户端需要调用 connect()连接服务器;
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址;
  • connect() 成功返回 0,出错返回-1

🦋 多线程远程命令执行

📚 代码结构

C++
CommandExec.hpp  Common.hpp  Cond.hpp  InetAddr.hpp  Log.hpp  Makefile  
Mutex.hpp  TcpClient.cc  TcpServer.cc  TcpServer.hpp  Thread.hpp  ThreadPool.hpp

TcpServer.hpp

#pragma once

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

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule;

static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string)>;

#define BACKLOG 8

class TcpServer
{
    using task_t = std::function<void()>;
    struct ThreadData
    {
        int sockfd;
        TcpServer *self;
    };
public:
    TcpServer(handler_t handler, int port = gport)
        : _handler(handler), 
        _port(port), 
        _isrunning(false)
    {
    }

    bool InitServer()
    {
        // 1. 创建tcp socket
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0); // Tcp Socket
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "_listensockfd error";
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "_listensockfd create success, _listensockfd is : " << _listensockfd;

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 2. bind
        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, _listensockfd is : " << _listensockfd;

        // 3. cs tcp是面向连接的,就要求tcp随时随地等待被连接
        // tcp 需要将socket设置成为监听状态
        n = ::listen(_listensockfd, BACKLOG);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success, _listensockfd is : " << _listensockfd;

        //::signal(SIGCHLD, SIG_IGN);     // 子进程退出,OS会自动回收资源,不用再wait了
        return true;
    }

    void HandlerRequest(int sockfd)   // TCP 也是全双工通信
    {
        LOG(LogLevel::INFO) << "HandlerRequest, sockfd is : " << sockfd;
        char inbuffer[4096];
        // 长任务
        while(true)
        {
            ssize_t n = ::recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
            if(n > 0)
            {
                LOG(LogLevel::INFO) << inbuffer;

                inbuffer[n] = 0;
                // std::string echo_str = "server echo# ";
                // echo_str += inbuffer; 

                std::string cmd_result = _handler(inbuffer);

                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
            }
            else if(n == 0)
            {
                // read 如果读取返回值是0,表示client退出
                LOG(LogLevel::INFO) << "client quit: " << sockfd;
                break;
            }
            else {
                // 读取失败了
                break;
            }
        }

        ::close(sockfd);   // fd泄露问题
    }

    static void *ThreadEntry(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData* data = (ThreadData*)args;
        data->self->HandlerRequest(data->sockfd);
        delete data;
        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 不能直接读取数据
            // 1. 获取新连接
            struct sockaddr_in peer;
            socklen_t peerlen = sizeof(peer);
            LOG(LogLevel::DEBUG) << "accept ing ...";
            // 我们要获取客户端的信息:数据(sockfd) + client socket信息(accept)
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error" << strerror(errno);
                continue;
            }

            // 获取连接成功了
            LOG(LogLevel::INFO) << "accept success, socket is : " << sockfd;
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "client info: " << addr.Addr();

            // version-0
            // HandlerRequest(sockfd);


            // version-1 多进程版本
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // child
            //     // 问题1: 父进程的文件描述符表子进程会继承 父子各一张共两张 
            //     // 1.关闭不需要的fd
            //     ::close(_listensockfd);
            //     if(fork() > 0) exit(0); // 子进程退出
            //     // 孙子进程 -> 孤儿进程 -> 1
            //     HandlerRequest(sockfd);
            //     exit(0);
            // }
            // ::close(sockfd);    // 父进程也关闭不需要的 已经交给子进程了

            // // 不会阻塞
            // pid_t rid = ::waitpid(id, nullptr, 0);
            // if(rid < 0)
            // {
            //     LOG(LogLevel::WARNING) << "waitpid error";
            // }



            // version-2 多线程版本
            // pthread_t tid;
            // ThreadData* data = new ThreadData;
            // data->sockfd = sockfd;
            // data->self = this;
            // pthread_create(&tid, nullptr, ThreadEntry, data);    // 主线程和新线程是如何看待,文件描述符表, 共享一张文件描述符表!!属于同一个进程 !



            // version-3 线程池版本  一般用于短任务(注册登录),少量用户
            // task_t f = std::bind(&TcpServer::HandlerRequest, this, sockfd);     // 构建任务
            // ThreadPool<task_t>::getInstance()->Equeue(f);
            ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){
                this->HandlerRequest(sockfd);
            });
        }
    }

    void Stop()
    {
        _isrunning = false;
    }

    ~TcpServer()
    {
    }

private:
    int _listensockfd; // 监听socket
    uint16_t _port;
    bool _isrunning;

    // 处理上层任务的入口
    handler_t _handler;
};

TcpServer.cc

#include "TcpServer.hpp"
#include "CommandExec.hpp"
#include <memory>

using namespace LogModule;

int main()
{
    ENABLE_CONSOLE_LOG();
    Command cmd;
    
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>([&cmd](std::string cmdstr){
        return cmd.Execute(cmdstr);
    });

    tsvr->InitServer();
    tsvr->Start();

    return 0;
}

TcpClient.cc

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

// ./client_tcp serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cout << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        return 1;
    }
    std::string serverip = argv[1];
    int server_port = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cout << "Create socket failed." << std::endl;
        return 2;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);    
    server_addr.sin_addr.s_addr = inet_addr(serverip.c_str());

    // client 不需要显示的进行bind, tcp是面向连接的, connect 底层自动会进行bind
    int n = ::connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(n < 0)
    {
        std::cout << "Connect to server failed." << std::endl;
        return 3;
    }

    // echo client
    std::string message;
    while(true)
    {
        char inbuffer[1024];
        std::cout << "input message: ";
        std::getline(std::cin, message);

        n = ::write(sockfd, message.c_str(), message.size());
        if(n > 0)
        {
            int m = ::read(sockfd, inbuffer, sizeof(inbuffer));
            if(m > 0)
            {
                inbuffer[m] = 0;
                std::cout << inbuffer << std::endl;
            }
            else break;
        }
        else break;
    }


    ::close(sockfd);
    return 0;   
}

CommandExec.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <set>

const int line_size = 1024;

class Command
{
public:
    Command()
    {
        _white_list.insert("ls");
        _white_list.insert("pwd");
        _white_list.insert("ls -l");
        _white_list.insert("who");
        _white_list.insert("whoami");
        _white_list.insert("ll");
    }

    bool SafeCheck(const std::string& cmdstr)
    {
        auto iter = _white_list.find(cmdstr);
        return iter == _white_list.end() ? false : true;
    }

    // 给你一个命令字符串"ls -l",执行它并返回执行结果
    std::string Execute(std::string cmdstr)
    {
        // 1. pope
        // 2.fork + dup2(pipe[1], 1) + exec*, 执行结果给父进程, pipe[0]
        // 3. return 
        // FILE *popen(const cahr *command, const char *type);
        // pclose(FILE *stream);

        if(!SafeCheck(cmdstr))
        {
            return std::string(cmdstr + "不支持");
        }

        FILE *fp = popen(cmdstr.c_str(), "r");
        if(fp == nullptr)
        {
            return std::string("Failed");
        }

        char buffer[line_size];
        std::string result;
        while(true)
        {
            char *ret = ::fgets(buffer, sizeof(buffer), fp);
            if(!ret) break;
            result += ret;
        }

        pclose(fp);
        return result.empty() ? std::string("Done") : result;
    }
private:
    std::set<std::string> _white_list;
};

CommandExec.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <set>

const int line_size = 1024;

class Command
{
public:
    Command()
    {
        _white_list.insert("ls");
        _white_list.insert("pwd");
        _white_list.insert("ls -l");
        _white_list.insert("who");
        _white_list.insert("whoami");
        _white_list.insert("ll");
    }

    bool SafeCheck(const std::string& cmdstr)
    {
        auto iter = _white_list.find(cmdstr);
        return iter == _white_list.end() ? false : true;
    }

    // 给你一个命令字符串"ls -l",执行它并返回执行结果
    std::string Execute(std::string cmdstr)
    {
        // 1. pope
        // 2.fork + dup2(pipe[1], 1) + exec*, 执行结果给父进程, pipe[0]
        // 3. return 
        // FILE *popen(const cahr *command, const char *type);
        // pclose(FILE *stream);

        if(!SafeCheck(cmdstr))
        {
            return std::string(cmdstr + "不支持");
        }

        FILE *fp = popen(cmdstr.c_str(), "r");
        if(fp == nullptr)
        {
            return std::string("Failed");
        }

        char buffer[line_size];
        std::string result;
        while(true)
        {
            char *ret = ::fgets(buffer, sizeof(buffer), fp);
            if(!ret) break;
            result += ret;
        }

        pclose(fp);
        return result.empty() ? std::string("Done") : result;
    }
private:
    std::set<std::string> _white_list;
};

🦋 网络版计算器(应用层自定义协议与序列化)

代码结构

C++
Calculator.hpp  Common.hpp  Cond.hpp  Deamon.hpp  InetAddr.hpp  Log.hpp  Makefile  Mutex.hpp  
Protocol.hpp  TcpClient.cc  TcpServer.cc  TcpServer.hpp  Thread.hpp  ThreadPool.hpp
// 简单起见, 可以直接采用自定义线程
// 直接 client<<->>server 通信, 这样可以省去编写没有干货的代码

网络版计算器(应用层自定义协议与序列化)

二:🔥 共勉

以上就是我对 【Linux】Socket编程-TCP构建自己的C++服务器 的理解,想要完整代码可以私信博主噢!觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

森林网络部署,工业4G路由器实现林区组网远程监控

在广袤无垠的林区&#xff0c;每一片树叶的摇曳、每一丝空气的流动&#xff0c;都关乎着生态的平衡与安宁。林区监控正以强大的力量&#xff0c;为这片绿色家园筑起一道坚固的防线。 工业 4G 路由器作为林区监控组网的守护者&#xff0c;凭借着卓越的通讯性能&#xff0c;突破…

数据库管理-第284期 奇怪的sys.user$授权(20250116)

数据库管理284期 20245-01-16 数据库管理-第284期 奇怪的sys.user$授权&#xff08;20250116&#xff09;1 问题2 CDB与PDB3 跨实例3.1 通过scanip访问数据库3.2 通过节点1的VIP访问数据库3.3 通过节点3的VIP访问数据库3.4 在节点3赋权后测试3.5 小结 总结 数据库管理-第284期 …

vue2配置跨域后请求的是本机

这个我来说明一下&#xff0c;因为我们公司的后端设置解决了跨域问题&#xff0c;所以我有很久没有看相关的内容了&#xff0c;然后昨天请求了需要跨域的接口&#xff0c;请求半天一直不对&#xff0c;浏览器显示的是本机地址&#xff0c;我以为是自己配置错了&#xff0c;后面…

从玩具到工业控制--51单片机的跨界传奇【3】

在科技的浩瀚宇宙中&#xff0c;51 单片机就像一颗独特的星辰&#xff0c;散发着神秘而迷人的光芒。对于无数电子爱好者而言&#xff0c;点亮 51 单片机上的第一颗 LED 灯&#xff0c;不仅仅是一次简单的操作&#xff0c;更像是开启了一扇通往新世界的大门。这小小的 LED 灯&am…

Java技术栈 —— 如何把项目部署到公网?

如何把项目部署到公网&#xff1f; 一、准备工作1.1 获得一台云服务器1.2 安装SSH客户端 二、云服务器部署2.1 配置云服务器2.2 使用nginx部署1个或多个前端项目 三、访问测试四、访问控制 平常大部分人都是本地写写项目&#xff0c;然后通过localhost的方式去访问&#xff0c;…

春秋杯-WEB

SSTI 可以看到主页那里有个登录测试之后为ssti {{4*4}} fenjing梭哈即可得到payload {{((g.pop.__globals__.__builtins__.__import__(os)).popen(cat flag)).read()}}file_copy 看到题目名字为file_copy&#xff0c; 当输入路径时会返回目标文件的大小&#xff0c; 通…

基于微信小程序教学辅助系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(六)

文章目录 一、考试管理模块实现1、添加考试功能实现1.1 页面设计1.2 前端功能实现1.3 后端功能实现1.4 效果展示2、考试管理功能实现2.1 页面设计2.2 前端功能实现2.3 后端功能实现2.3.1 后端查询接口实现2.3.2 后端编辑接口实现2.3.3 后端删除接口实现2.4 效果展示二、代码下载…

计算机网络 (43)万维网WWW

前言 万维网&#xff08;World Wide Web&#xff0c;WWW&#xff09;是Internet上集文本、声音、动画、视频等多种媒体信息于一身的信息服务系统。 一、基本概念与组成 定义&#xff1a;万维网是一个分布式、联机式的信息存储空间&#xff0c;通过超文本链接的方式将分散的信息…

如何学习数学 | 数学家如何思考

学习数学的关键在哪里&#xff1f; 原创 遇见数学 不少人面对数学都会觉得高深莫测&#xff0c;甚至非常枯燥乏味。 只有当你真正走入它的世界&#xff0c;才会发现里面蕴含着无尽的智慧和美感。要想推开这座数学的大门&#xff0c;需要的不仅仅是背公式&#xff0c;或者做一…

【Python通过UDP协议传输视频数据】(界面识别)

提示&#xff1a;界面识别项目 前言 随着网络通信技术的发展&#xff0c;视频数据的实时传输在各种场景中得到了广泛应用。UDP&#xff08;User Datagram Protocol&#xff09;作为一种无连接的协议&#xff0c;凭借其低延迟、高效率的特性&#xff0c;在实时性要求较高的视频…

ZNS SSD垃圾回收优化方案解读-2

四、Brick-ZNS 关键设计机制解析 Brick-ZNS 作为一种创新的 ZNS SSD 设计&#xff0c;聚焦于解决传统 ZNS SSDs 在垃圾回收&#xff08;GC&#xff09;过程中的数据迁移低效问题&#xff0c;其核心特色为存储内数据迁移与地址重映射功能。在应用场景中&#xff0c;针对如 Rock…

当PHP遇上区块链:一场奇妙的技术之旅

PHP 与区块链的邂逅 在技术的广袤宇宙中&#xff0c;区块链技术如同一颗耀眼的新星&#xff0c;以其去中心化、不可篡改、透明等特性&#xff0c;掀起了一场席卷全球的变革浪潮。众多开发者怀揣着对新技术的热忱与探索精神&#xff0c;纷纷投身于区块链开发的领域&#xff0c;试…

【原创】大数据治理入门(10)《数据资产化:从数据到价值》入门必看 高赞实用

数据资产化&#xff1a;从数据到价值 引言&#xff1a;数据资产化的概念 数据资产化&#xff08;Data Monetization&#xff09;是指将企业内部的各种数据转化为有价值的资产&#xff0c;通过数据的应用和分析提升企业的运营效率、降低成本、增加收入和优化决策。在大数据时代…

5-1 创建和打包AXI Interface IP

创建和打包AXI Interface IP的前流程和后流程 step 1 &#xff1a; 选择类型 1&#xff1a; 将当前的工程打包成IP 2&#xff1a; 将当前的BD工程打包成IP 3&#xff1a; 将指定的源码打包成IP 4&#xff1a; 创建一个新的AXI 接口IP 其中3和4是比较常用的&#xff0c;本次…

一文简要了解为什么需要RAG、核心原理与应用场景

欢迎来到AI应用探索&#xff0c;这里专注于探索AI应用。 一、为什么需要RAG&#xff0c;它解决了哪些问题 在自然语言处理领域&#xff0c;生成式预训练模型&#xff08;如GPT&#xff09;已经展示了强大的文本生成能力。然而&#xff0c;这些模型有以下局限性&#xff1a; 知…

很简单的K8s集群部署方法-KubeKey自动化部署

K8s集群部署方法-KubeKey自动化部署 文章后续KubeSphere部署安装&#xff0c;接入KubeKey安装的k8s集群 文章目录 K8s集群部署方法-KubeKey自动化部署 一.清理kubeadm安装的k8s集群缓存二.服务器安装前准备1.设置主机名2.配置时间同步3.关闭系统防火墙4.安装系统依赖5.关闭swap…

Linux之文件系统前世今生(一)

Linux在线1 Linux在线2 一、 基本概念 1.1 块&#xff08;Block&#xff09; 在计算机存储之图解机械硬盘这篇文章中我们提到过&#xff0c;磁盘读写的最小单位是扇区&#xff0c;也就是 512 Byte&#xff1b;很明显&#xff0c;每次读写的效率非常低。 为了提高IO效率&…

.netframwork模拟启动webapi服务并编写对应api接口

在.NET Framework环境中模拟启动Web服务&#xff0c;可以使用几种不同的方法。一个常见的选择是利用HttpListener类来创建一个简单的HTTP服务器&#xff0c;或者使用Owin/Katana库来自托管ASP.NET Web API或MVC应用。下面简要介绍Owin/Katana示例代码。这种方法更加灵活&#x…

【0x0052】HCI_Write_Extended_Inquiry_Response命令详解

目录 一、命令概述 二、命令格式及参数 2.1. HCI_Write_Extended_Inquiry_Response命令格式 2.2. FEC_Required 2.3. Extended_Inquiry_Response 三、生成事件及参数 3.1. HCI_Command_Complete 事件 3.2. Status 四、命令执行流程 4.1. 命令准备阶段(主机端) 4.2…