TCP/IP网络编程 第二十一章:异步通知I/O模型

理解异步通知I/O模型

理解同步和异步

首先解释“异步”(Asynchronous)的含义。异步主要指“不一致”,它在数据I/O中非常有用。之前的Windows示例中主要通过send&recv函数进行同步I/O。调用send函数时,完成数据传输后才能从函数返回;而调用recv函数时,只有读到期望大小的数据后才能返回。因此,相当于同步方式的I/O处理。

同步的关键是函数的调用及返回时刻,以及数据传输的开始和完成时刻。 

“调用send函数的瞬间开始传输数据,send函数执行完的时刻完成数据传输。”

“调用recv函数的瞬间开始接收数据,recv函数执行完的时刻完成数据接收。”

那异步I/O的含义又是什么呢?异步I/O是指I/O函数的返回时刻与数据收发的完成时刻不一致

同步I/O的缺点及异步方式的解决方案


异步I/O就是为了克服同步I/O的缺点而设计的模型。同步I/O有哪些缺点?异步方式又是如何解决的呢?其实,第17章的最后部分“条件触发和边缘触发孰强孰弱”中给出过答案。各位可能忘记这些内容,考虑到这一点,我将以不同的、更简单的方式解释。同步I/O的缺点:“进行I/O的过程中函数无法返回,所以不能执行其他任务!”而异步无论数据是否完成交换都返回函数,这就意味着可以执行其他任务。所以说“异步方式能够比同步方式更有效地使用CPU"

理解异步通知I/O模型

之前分析了同步和异步方式的I/O函数,确切地说,分析了同步和异步方式下I/O函数返回时
间点的差异。下面我希望扩展讨论的对象。
本章题目为“异步通知I/O模型”,意为“通知I/O”是以异步方式工作的。

首先了解一下“通知I/O”的含义:“通知输入缓冲收到数据并需要读取,以及输出缓冲为空故可以发送数据。"

顾名思义,“通知I/O”是指发生了I/O相关的特定情况。典型的通知I/O模型是select方式。还记得select监视的3种情况吗?其中具代表性的就是“收到数据的情况”。select函数就是从返回调点用的函数时通知需要I/O处理的,或可以进行I/O处理的情况。但这种通知是以同步方式进行的,原因在于,需要I/O或可以进行I/O的时间点与select函数的返回时间点一致

相信各位已理解通知I/O模型的含义。与“select函数只在需要或可以进行I/O的情况下返回”不同,异步通知I/O模型中函数的返回与I/O状态无关。本章的WSAEventSelect函数就是select函数的异步版本。
可能有人疑问:“既然函数的返回与I/O状态无关,那是否需要监视I/O状态变化?”
当然需要!异步通知I/O中,指定I/O监视对象的函数和实际验证状态变化的函数是相互分离的。因此,指定监视对象后可以离开执行其他任务,最后再回来验证状态变化。以上就是通知I/O的所有理论,下面通过具体函数实现该模型。

理解和实现异步通知I/O模型

WSAEventSelect函数和通知

如前所述,告知I/O状态变化的操作就是“通知”。I/O的状态变化可以分为不同情况。
□套接字的状态变化:套接字的I/O状态变化。
□发生套接字相关事件:发生套接字I/O相关事件。
这2种情况都意味着发生了需要或可以进行I/O的事件,我将根据上下文适当混用这些概念。

首先介绍WSAEventSelect函数,该函数用于指定某一套接字为事件监视对象

#include<winsock2.h>
int WSAEventSelect(SOCKET s,WSAEVENT hEventObeject,long lNetworkEvent);
//成功时返回0,失败时返回 SOCKET_ERROR。s               //监视对象的套接字句柄。hEventObject    //传递事件对象句柄以验证事件发生与否。INetworkEvents  //希望监视的事件类型信息。

传入参数s的套接字内只要发生INetworkEvents中指定的事件之一,WSAEventSelect函数就将hEventObject句柄所指内核对象改为signaled状态。因此,该函数又称“连接事件对象和套接字的函数。"

另外一个重要的事实是,无论事件发生与否,WSAEventSelect函数调用后都会直接返回。也就是说,该函数以异步通知方式工作。

下面介绍作为该函数第三个参数的事件类型信息,可以通过位或运算同时指定多个信息。

□ FD_READ:是否存在需要接收的数据?

□ FD_WRITE:能否以非阻塞方式传输数据?

□FD_OOB:是否收到带外数据?
□ FD_ACCEPT:是否有新的连接请求?
□ FD_CLOSE:是否有断开连接的请求?

以上就是WSAEventSelect函数的调用方法。

各位或许有如下疑问:“啊?select函数可以针对多个套接字对象调用,但WSAEventSelect函数只能针对1个套接字对象调用!”
的确,仅从概念上看,WSAEventSelect函数的功能偏弱。但使用该函数时,没必要针对多个套接字进行调用。从select函数返回时,为了验证事件的发生需要再次针对所有句柄调用函数,但通过调用WSAEventSelect函数传递的套接字信息已注册到操作系统,所以无需再次调用。这反而是WSAEventSelect函数比select函数的优势所在。

从前面关于WSAEventSelect函数的说明中可以看出,需要补充如下内容。
□WSAEventSelect函数的第二个参数中用到的事件对象的创建方法。
□调用WSAEventSelect函数后发生事件的验证方法。

□验证事件发生后事件类型的查看方法。

manual-reset 模式事件对象的其他创建方法

我们之前利用CreateEvent函数创建了事件对象。CreateEvent函数在创建事件对象时,可以在auto-reset模式和manual-reset模式中任选其一。但我们只需要manual-reset模式non-signaled状态的事件对象,所以利用如下函数创建较为方便。

#include<winsock2.h>
WSAEVENT WSACreateEvent(void);
//成功时返回事件对象句柄,失败时返回WSA_INVALID_EVENT。

上述声明中返回类型WSAEVENT的定义如下:

#define WSAEVENT HANDLE


实际上就是我们熟悉的内核对象句柄,这一点需要注意。

另外,为了销毁通过上述函数创建的事件对象,系统提供了如下函数。

#include<winsock2.h>
BOOL WSACloseEvent(WSAEVENT hEvent);//成功时返回TRUE,失败时返回FALSE

验证是否发生事件

既然介绍了WSACreateEvent函数,那调用WSAEventSelect函数应该不成问题。接下来就要考虑调用WSAEventSelect函数后的处理。为了验证是否发生事件,需要查看事件对象。完成该任务的函数如下,除了多1个参数外,其余部分与WaitForMultipleObjects函数完全相同。

#include<winsock2.h>
DWORD WSAWaitForMultipleEvent(DWORD cEvents, const WSAEVENT * lphEvents, BOOL fwaitAll, DWORD dwTimeout,BOOL fAlertable);
//成功时返回发生事件的对象信息,失败时返回WSA_INVALID_EVENT。cEvents   //需要验证是否转为signaled状态的事件对象的个数IphEvents //存有事件对象句柄数组地址值fWaitAll  //传递TRUE时,所有事件对象在signaled状态时返回;传递FALSE时,只要其中1个变为//signaled状态就返回。dwTimeout //以1/1000秒为单位指定超时,传递WSA_INFINITE时,直到变为signaled状态时才会返 //回。fAlertable//传递TRUE时进入alertable_wait(可警告等待)状态(第22章)返回值     //返回值减去常量WSA_WAIT_EVENT_0时,可以得到转变为signaled状态的事件对象句柄对 //应的素引,可以通过该索引在第二个参数指定的数组中查找句柄。如果有多个事件对象变为 //signaled状态,则会得到其中较小的值。发生超时将返回WSA_WAIT_TIMEOUT。

由于发生套接字事件,事件对象转为signaled状态后该函数才返回,所以它非常有利于确认事件发生与否。但由于最多可传递64个事件对象,如果需要监视更多句柄,就只能创建线程或扩展保存句柄的数组,并多次调用上述函数。

对于WSAWaitForMultipleEvents函数,各位可能产生如下疑问:"WSAWaitForMultipleEvents函数如何得到转为signaled状态的所有事件对象句柄的信息?”

答案是:只通过1次函数调用无法得到转为signaled状态的所有事件对象句柄的信息。通过该函数可以得到转为signaled状态的事件对象中的一个(按数组中的保存顺序)索引值。但可以利用“事件对象为manual-reset模式”的特点,通过如下方式获得所有signaled状态的事件对象。

int posInfo,startIdx,i;
.....
posInfo=WSAWaitForMultipleEvent(numOfSock,hEventArray,FALSE,WSA_INFINITE,FALSE);
startIdx=posInfo-WSA_WAIT_EVENT_0;
.....
for(i=stratIdx;i<numOfSock;i++){int sigEventIdx=WSAWaitForMultipleEvents(1,&hEventArray[i],TRUE,0,FALSE);.....
}

注意观察上述代码中的循环。循环中从第一个事件对象到最后一个事件对象逐一依序验证是否转为signaled状态(超时信息为0,所以调用函数后立即返回)。之所以能做到这一点,完全是因为事件对象为manual-reset模式,这也解释了为何在异步通知I/O模型中事件对象必须为manual-reset模式。

区分事件类型

既然已经通过WSAWaitForMultipleEvents函数得到了转为signaled状态的事件对象,最后就要确定相应对象进入signaled状态的原因。为完成该任务,我们引入如下函数。调用此函数时,不仅需要signaled状态的事件对象句柄,还需要与之连接的(由WSAEventSelect函数调用引发的)发生的套接字句柄。

#include<winsock2.h>
int WSAEnumNetworkEvent(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvent);
//成功时返回0,失败时返回SOCKET_ERROR。s              //发生事件的套接字句柄。hEventObject   //与套接字相连的(由WSAEventSelect函数调用引发的)signaled状态的件对象句柄。lpNetworkEvents//保存发生的事件类型信息和错误信息的WSANETWORKEVENTS结构体变量地址值。

上述函数将manual-reset模式的事件对象改为non-signaled状态,所以得到发生的事件类型后,不必单独调用ResetEvent函数。下面介绍与上述函数有关的WSANETWORKEVENTS结构体。

typedef struct _WSANETWORKEVENTS{long lNetworkEvents;int iErrorCode[FD_MAX_EVENTS];
}WSANETWORKEVENTS,* LPWSANETWORKEVENTS;

上述结构体的INetworkEvents成员将保存发生的事件信息。与WSAEventSelect函数的第三个参数相同,需要接收数据时,该成员为FD_READ;连接请求时,该成员为FD_ACCEPT。因此,可通过如下方式查看发生的事件类型。

WSANETWORKEVENTS netEvents;
.....
WSAEnumNetworkEvents(hSock,hEvent,&netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT){
//FD_ACCEPT事件的处理
}
if(netEvents.lNetworkEvents & FD_READ){
//FD_READ事件的处理
}
if(netEvents.lNetworkEvents & FD_CLOSE){
//FD_CLOSE事件的处理
}

另外,错误信息将保存到声明为成员的iErorCode数组(发生错误的原因可能很多,因此用数组声明)。验证方法如下。

□如果发生FD_READ相关错误,则在iErrorCode[FD_READ_BIT]中保存除0以外的其他值。

□如果发生FD_WRITE相关错误,则在iErrorCode[FD_WRITE_BIT]中保存除0以外的其他值。

可以通过以下的描述来理解上述内容。

“如果发生FD_XXX相关错误,则在iErorCode[FD_XXX_BIT]中保存除0以外的其他值"

因此可以用如下方式检查错误。

WSANETWORKEVENTS netEvents;
......
WSAEnumNetworkEvents(hSock,hEvent,&netEvents);
if(netEvents.iErrorCode[FD_READ_BIT] != 0){
//发生FD_READ事件相关错误
}

以上就是异步通知I/O模型的全部内容,下面通过这些知识编写示例。

利用异步通知I/O模型实现回声服务器端

由于代码较长,所以将分成多个部分进行介绍。

#include<stdio.h>
#include<string.h>
#include<winsock2.h>#define BUF_SIZE 100void CompressSockets(SOCKET hSockArr[],int idx,int total);
void CompressEvents(WSAEVENT hEventArr,int idx,int total);
void ErrorHandling(char *msg);int main(int argc,char *argv[]){WSADATA wsaData;SOCKET hServSock,hClntSock;SOCKADDR_IN serAdr,clntAdr;SOCK hSockArr[WSA_MAXIMUM_WAIT_EVENTS];WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];WSAEVENT newEvent;WSANETWORKEVENTS netEvents;int numOfClntSock=0;int strLen,i;int posInfo,startIdx;int clntAdrLen;char msg[BUF_SIZE];if(argc!=2){printf("Usage: %s <port>\n",argv[0]);exit(1);}if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)ErrorHandling("WSAStartup() error!");

以上是初始化和声明代码,没有什么要特别说明的。

hServSock=socket(PF_INET,SOCK_STREAM,0);
memset(&servAdr,0,sizeof(servAdr));
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));if(bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)ErrorHandling("bind() error");if(listen(hServSock,5)==SOCK_ERROR)ErrorHandlind("listen() error");newEvent=WSACreateEvent();
if(WSAEventSelect(hServSock,newEvent,FD_ACCEPT)==SOCKET_ERROR)ErrorHandling("WSAEventSelect() error");hSockArr[numOfClntSock]=hServSock;
hEventArr[numOfClntSock]=newEvent;
numOfClntSock++;

上述代码创建了用于接收客户端连接请求的服务器端套接字。为了完成监听任务,针对FD_ACCEPT事件调用了WSAEventSelect函数。此处需要注意如下2条语句。

hSockArr[numOfClntSock]=hServSock;
hEventArr[numOfClntSock]=newEvent;

这段代码把通过WSAEventSelect函数连接的套接字和事件对象的句柄分别存人hSockArr和hEventArr数组。也就是说,应该可以通过hSockArr[idx]找到连接到套接字的事件对象,反之,也可以通过hEventArr[idx]找到连接到事件对象的套接字。因此,该示例将套接字和事件对象句柄保存到数组时统一了保存位置。也就有了下列公式。

□ 与hSockArr[n]中的套接字相连的事件对象应保存到hEventArr[n]。
□与hEventArr[n]中的事件对象相连的套接字应保存到hSockArr[n]。

接下来是while循环部分,之前学习的大部分内容都在此。

while(1){posInfo=WSAWaitForMultipleEvents(numOfClntSock,hEventAdrr,FALSE,WSA_INFINITE,FALSE);startIdx=posInfo-WSA_WAIT_EVENT_0;for(i=startIdx;i<numOfClntSock;i++){int sigEventIdx=WSAWaitForMultipleEvents(1,&hEventArr[i],TRUE,0,FALSE);if((sigEventIdx==WSA_WAIT_FAILED||sigEventIdx==WSA_WAIT_TIMEOUT)){continue;}else{sigEventIdx=i;WSAEnumNetworkEvents(hSockArr[sigEventIdx],hEventArr[sigEventIdx],&netEvent);if(netEvents.lNetworkEvents& FD_ACCEPT){//判定为请求连接的IO通知if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0){puts("Accept Error");break;}clntAdrLen=sizeof(clntAdr);hClntSock=accept(hSockArr[sigEventIdx],(SOCKADDR*)&clntAdr,clntAdrLen);newEvent=WSACreateEvent();//创建对应客户端连接的事件对象WSAEventSelect(hClntSock,newEvent,FD_READ|FD_CLOSE);hEventArr[numOfClntSock]=newEventhSockArr[numOfClntSock]=hClntSock;numOfClntSock++;puts("connected new client...");}if(netEvents.lNetworkEvents &FD_READ){//判定为需要读取的IO通知if(netEvents.iErrorCode[FD_READ_BIT]!=0){puts("Read Error");break;}strLen=recv(hSockArr[sigEventIdx],msg,sizeof(msg),0);send(hSockArr[sigEventIdx],msg,strLen,0);}if(netEvents.lNetworkEvents &FD_CLOSE){//判定为结束连接的IO通知if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0){puts("Close Error");break;}WSACloseEvent(hEventArr[sigEventIdx]);closesocket(hSockArr[sigEventIdx]);numOfClntSock--;CompressSockets(hSockArr,sigEventIdx,numOfClntSock);CompressEvents(hEventArr,sigEventIdx,numOfClntSock);}}}
}
WSACleanup();
return 0;
}

最后给出上述代码调用的两个函数CompressSockets和CompressEvents的函数声明。

void CompressSockets(SOCKET hSockArr[],int idx,int total){int i;for(i=idx;i<total;i++)hSockArr[i]=hSockArr[i+1];//覆盖已经不用的套接字句柄
}void CompressEvents(WSAEVENT hEventArr[],int idx,int total){int i;for(i=idx;i<total;i++)hEventArr[i]=hEventArr[i+1];//覆盖已经不用的事件对象句柄
}void ErrorHandling(char *msg){fputs(msg,stderr);fputc('\n',stderr);exit(1);
}

断开连接并从数组中删除套接字及与之相连接的事件对象时同时调用上述两个函数才能维持套接字和事件对象之间的关系。

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

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

相关文章

php 进程间通信:管道、uds

1、管道 1.1、管道概念 管道是单向的、先进先出的&#xff0c;它把进程的输出和另一个进程的输入连接在一起。一个进程往管道写入数据&#xff0c;另一个进程从管道读取数据。数据被从管道中读取出来之后&#xff0c;将被删除&#xff0c;其他进程无法在读取到相应的数据。管…

格式工厂5.10.0版本安装

目前格式工厂有很多&#xff0c;大多都可以进行视频转换 之前遇到一个用ffmpeg拉流保存的MP4在vlc和迅雷都无法正常播放的问题&#xff0c;发现视频长度不对&#xff0c;声音也不对&#xff0c;最后换到了格式工厂的格式播放器是可以正常播放的 格式工厂下载之家的地址 http…

每天五分钟计算机视觉:单卷积层的前向传播过程

什么是单卷积层? 一张图片(输入)经过多个卷积核卷积就会得到一个输出,而这多个卷积核的组合就是一个单卷积层。 这些卷积核可能大小是不一样的,但是他们接收同样大小是输入,他们的输出必须是一般大小,所以不同的卷积核需要具备不同的步长和填充值。 单层卷积网络前向传…

你们公司的【前端项目】是如何做测试的?字节10年测试经验的我这样做的...

前端项目也叫web端项目&#xff08;通俗讲就是网页上的功能&#xff09;是我们能够在屏幕上看到并产生交互的体验。 前端项目如何做测试&#xff1f; 要讲清楚这个问题&#xff0c;先需要你对测试流程现有一个全局的了解&#xff0c;先上一张测试流程图&#xff1a; 测试流程…

旧版Xcode文件较大导致下载总是失败但又不能断点续传重新开始的解决方法

问题&#xff1a; 旧版mac下载旧版Xcode时需要进入https://developer.apple.com/download/all/?qxcode下载&#xff0c;但是下载这些文件需要登录。登录后下载中途很容易失败&#xff0c;失败后又必须重新下载。 解决方案&#xff1a; 下载这里面的内容都需要登录&#xff0…

开发一个RISC-V上的操作系统(二)—— 系统引导程序(Bootloader)

目录 文章传送门 一、什么是Bootloader 二、简单的启动程序 三、上板测试 文章传送门 开发一个RISC-V上的操作系统&#xff08;一&#xff09;—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统&#xff08;二&#xff09;—— 系统引导…

机器学习深度学习——多层感知机

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——感知机 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助 上一节…

Python代码实现题型

1.写出print()输出值 a = [1, 2, 3, 4, 5] # [1, 3, 5] print(a[::2]) # [4, 5] print(a[-2:]) 2.写出调用函数的输出结果 def fn(x, lst=[]):for i in range(x):lst.append(i * i)print(lst)# [0, 1] fn(2)# [3, 2, 1, 0, 1, 4] fn(3, [3,2,1])# [0, 1, 0, 1, 4] fn(3) 3.…

Git时间:版本控制工具进阶

Git时间&#xff1a;版本控制工具进阶 忽略文件 Git允许用户将指定的文件或目录排除在版本控制之外&#xff0c;它会检查代码仓库的目录下是否存在一个名为.gitignore的文件&#xff0c;如果存在&#xff0c;就去一行行读取这个文件中的内容&#xff0c;并把每一行指定的文件…

瀚高数据库中,对象不存在问题的处理思路及解决方案

**瀚高数据库 目录 环境 文档用途 详细信息 环境 系统平台&#xff1a;N/A 版本&#xff1a;6.0,4.5 文档用途 使用瀚高数据库可能会遇到应用系统中报对象等不存在的问题&#xff0c;可参照本文处理。 详细信息 1、常见报错信息展示&#xff1a; msgid “table “%s” does …

MySQL 读写分离

目录 一、什么是读写分离&#xff1f; 二、为什么要读写分离呢&#xff1f; 三、什么时候要读写分离&#xff1f; 四、主从复制与读写分离 五、MySQL 读写分离原理 六、企业 使用MySQL 读写分离场景 1&#xff09;基于程序代码内部实现 2&#xff09;基于中间代理层实现…

你说你会Java手动锁,但你会这道题吗???

按照这个格式输出你会吗&#xff1f;&#xff1f;&#xff1f; 你说你不会&#xff0c;接下来认真看认真学了。 1.首先引入原子类。AtomicInteger num new AtomicInteger(0); 什么是原子类&#xff1f; 就是可以保证线程安全的原子操作的数据类型。 有什么作用&#xff1f;…

在Debian 12 上安装 PHP 5.6, 7.4

环境&#xff1a;Debian 12 Debian 12 默认的PHP版本为 8.2 如果直接安装php7.4就出现下面的报错&#xff1a; sudo apt-get install libapache2-mod-php7.4 php7.4 php7.4-gd php7.4-opcache php7.4-mbstring php7.4-xml php7.4-json php7.4-zip php7.4-curl php7.4-imap p…

postgresql备份和恢复

实际工作中会对数据库进行备份和还原&#xff0c;备份主要有三种格式 .bak 即压缩的二进制 .sql 即明文存储 .tar 即tarball压缩格式 数据库备份分单数据库备份&#xff0c;使用 pg_dump 命令&#xff1b;所有数据库备份&#xff0c;使用 pg_dumpall 命令 pg_dump 常用选项…

Unity TextMeshPro 富文本-文本水平对齐

资料 文档 文本水平对齐 对齐方式&#xff1a;左对齐&#xff0c;右对齐&#xff0c;居中,Justified,Flush 使用&#xff1a;<alignleft>左对齐</align> &#xff0c;可赋值left,right,center,flush,justified 该标签会覆盖默认的对齐方式。标签范围内的文本受影…

Java简化MongoDB编解码器的两种方法

介绍&#xff1a; 在与MongoDB进行数据交互时&#xff0c;有时候会遇到找不到类的编解码器&#xff08;codec&#xff09;的错误。为了解决这个问题&#xff0c;一种常见的方法是创建自定义编解码器来处理特定的类。然而&#xff0c;对于一些开发者来说&#xff0c;这样的方法…

导出为PDF加封面且分页处理dom元素分割

文章目录 正常展示页面导出后效果代码 正常展示页面 导出后效果 代码 组件内 <template><div><div><div class"content" id"content" style"padding: 0px 20px"><div class"item"><divstyle"…

Ubuntu Server版 之 mysql 系列

Ubuntu 分 桌面版 和 服务版 桌面版 &#xff1a;有额外的简易界面 服务版&#xff1a;是纯黑框的。没有任何UI界面的可言 安装mysql 安装位置 一般按照的位置存放在 /usr/bin 中 sudo apt-get install mysql-server查看mysql的状态 service mysql status mysql 安全设置…

使用xtcp映射穿透指定服务

使用xtcp映射穿透指定服务 管理员Ubuntu配置公网服务端frps配置service自启(可选) 配置内网服务端frpc配置service自启(可选) 使用者配置service自启(可选) 通过frp实现内网client访问另外一个内网服务器 管理员 1&#xff09;配置公网服务端frps2&#xff09;配置内网服务端…

FS32K144官方提供串口Bootloader对接Matlab串口烧写程序

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 前言 Bootloader升级工具&#xff1a;可用TTL、232、485&#xff08;硬件收发模式&#xff09;,其中的一种&#x…