[C# 网络编程系列]专题十二:实现一个简单的FTP服务器

引言:

休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务器。在我们平时的上网过程中,一般都是使用FTP的客户端来对商家提供的服务器进行访问(上传、下载文件),例如我们经常用到微软的SkyDrive网盘,115网盘等,然而我们经常用到的都是网页版本的,网页版本和客户端版本的不同,网页版本的FTP客户端,它与服务器的交流是使用HTTP协议发出对服务器的请求的,而客户端版本采用的是FTP协议发出命令对服务器进行请求。然后我们接触到FTP服务器却很少的, 所以本专题中将和大家介绍下如何实现一个FTP服务器(不要觉得服务器很深奥一样的,大家可以简单的认为服务器也是一个程序,该程序是对客户端发来的请求做处理的,请求大家可以简单理解为字符串,从这个角度看, 服务器程序就是一个对字符串解析的过程。),也是为后面的一个专题做一个铺垫,因为后面专题将和大家介绍下FTP客户端——文件上传下载器,有了自己自定义的FTP服务器后, 自定义的FTP客户端就可以对自定义的FTP服务器进行访问,使两者形成一个完整的软件,从而也让大家对基于FTP协议的工具有一个初步的了解。

 

一、基于FTP协议的客户端和服务器是如何"沟通的"?

FTP客户端和FTP服务器之间的“沟通”分为四个阶段的:

1. 启动FTP

客户通过FTP客户端软件,发起FTP交互式的命令,就是告诉服务器(也就是一台电脑,服务器上与一个程序(FTP服务)会接收命令,并解析发来的命令,然后发出回复信息)说:“我想和你聊聊天,可以吗?”

2. 建立控制连接

客户端TCP层根据客户给出的服务器IP地址,向服务器提供FTP服务的21号端口发出主动建立连接的请求,服务器接收到请求后,通过3次握手之后,客户端和服务器之间就建立一个TCP连接(就是一条通道,就好比生活中马路,有了马路之后车才能够在两地之间运送东西),之后,所有用户发出的FTP命令和服务器的回应都是通过该连接来传送的, 所以也把这个TCP连接叫做控制连接,控制连接在用户退出之前一直存在。

3. 建立数据连接和进行文件传输

现在客户端和服务器端已经建立聊天的通道了(控制连接),但是两者聊天过程中如果互相想赠送礼物要怎么办呢?(这里形象的把客户端和服务器端文件的传输比喻两个人通过聊天后互相赠送礼物的过程),此时我们就需要另外一条马路(数据连接)来进行“礼物的赠送”了,具体赠送礼物的过程如下:

  1.  客户端通过控制连接向服务器发送一个上传文件的命令时,会自己分配一个临时的TCP端口号。
  2.  客户端通过控制连接向服务器发送一个命令(下面将会介绍的PORT命令)来告诉服务器自己的IP地址和临时的端口号,然后再发送一条上传文件的命令(可以理解为——客户端要送礼物给服务器时,实际上不是简单的发送一个送礼物命令的,在这之前还需要发送一条自我介绍命令(就是告诉服务器自己的IP地址和端口号)来告诉服务器自己就是刚刚和它聊天的那位,这也很符合我们日常送礼物的流程的,一般大家接到礼物都要弄明白送礼物的人是谁,是不是自己认识的)
  3. 服务器接收到客户端的IP地址和临时端口号后,以这个IP地址和端口号为目标,使用服务器上的20端口(该端口是用来传输数据的端口)向客户端发出主动建立连接的请求。
  4.  客户端收到请求后,通过3此握手后就与服务器之间建立了另外一条TCP连接——数据连接,即用来互相送礼物的通道。
  5.  客户端在自己的文件系统中选择要赠送(上传)的文件
  6.  客户端将文件写入到文件传输进程中(写入网络流中)
  7.  服务器端将传输来的文件在服务器端的文件系统中进行存储
  8.  文件传输完成后,由服务器主动关闭该数据的连接

4 关闭FTP

当用户退出FTP时,通关客户端发送退出命令,之后控制连接被关闭,FTP服务结束。

二、从上面的沟通过程中你明白了什么?

从上面客户端与服务器端的沟通过程中,这里可以概括几点:

(1)客户端与服务器端进行交互过程中,传输层使用的是TCP协议而不是其他传输层协议

(2)沟通过程有两条TCP连接——一条是控制连接,即传输命令和响应信息的通道,另一条是数据连接,即传输文件的马路,并且必须先有控制连接才能建立数据连接,因为要进行文件传输首先必须知道客户的IP地址和端口号,这个过程就是通过控制连接传送的命令来告知服务器客户端的IP地址和端口号,之后再在两者之间建立数据连接来传输文件

(3)在服务器端,控制连接(端口号为21)和数据连接(端口号 为20)使用了不同的端口号

三、赠送礼物的方式?——文件传输模式

客户端与FTP服务器建立数据连接之后,首先需要告诉服务器采用哪种文件传输模式,FTP提供了两种文件传输模式,一种是主动(Port)模式,另一种是被动(Passive)模式。

主动模式——服务器向客户端发起数据连接请求,被动模式——客户端向服务器发起数据请求。

然而两种模式有什么相同点和不同点呢?

两种模式的相同点: 服务器都使用21号端口进行用户验证和管理

不同点: 传送文件数据的方式不一样,主动模式的FTP服务器数据端口固定在20,而被动模式的FTP服务器数据端口则在1025~65535之间的随机数。

3.1 主动模式

主动模式——服务器主动连接客户端,然后传输文件,在这种模式下,FTP客户端先用一个端口N(N>1024)向服务器的21号端口发起控制连接,连接成功后,在发出PORT N+1命令告诉服务器自己监听的端口为N+1;服务器接受到该命令后,用一个新的数据端口(20号端口)与客户端的端口N+1建立连接,然后进行文件传输,而客户端则通过监听N+1端口接受文件数据。

注意: 采用主动模式存在一个问题,如果客户端安装了防火墙或在内网时,由于防火墙一般不允许接受外部发起的标准端口以外的连接请求,因此外部FTP服务器就无法使用主动模式穿过防火墙主动连接客户端(这里与客户端连接的端口为N+1(N>1024),非标准端口),从而造成无法传送文件数据,此时就需要采用被动模式传送文件了。

3.2 被动模式

被动模式——服务器被动接受客户端连接请求,即控制连接请求和数据连接请求都是由客户端发起,在这种模式下,FTP客户端先随机开始一个端口N向服务器的21号端口发起控制连接,然后向服务器发送PASV命令。服务器收到该命令后,会用一个新的端口P(P>1024)进行监听,同时将该端口号告诉客户端,客户端接受到响应命令后,再通过新的端口N+1连接服务器的端口P,然后进行文件数据传输。

注意:采用被动模式与主动模式也存在相同的问题,如果服务器安装了防火墙,客户端同样可能无法与服务器端的端口P建立数据请求,因为该请求可能会被防火墙过滤掉。在实际应用中,服务器一般指定一个端口范围,允许客户端与该范围内的端口建立数据连接,而不再这个范围内的端口会被服务器的防火墙过滤掉,从而在一定程度上消除了针对服务器的恶意攻击。

四、 FTP协议中有哪些命令的?

协议简单说就是一个规范,就好比打牌一样,制定一个大家都能明白的规则,斗地主的规则被大家都认可的,但是私下我们也可以自定义规则来玩的(例如说三个只能带一个等这样的规则),同样FTP规则也是大家都认可的一个协议,我们当然也可以自定义协议。

由于.Net平台下目前还没有提供对FTP服务器端开发的类库,因此要实现一个FTP服务器端的应用程序,就必须了解FTP协议的详细内容。

4.1 FTP命令有哪些?

FTP 协议中规定了一些大家都认识的命令和组成。FTP协议中的命令都由3~4个字母组成,命令与参数之间用空格隔开,每个命令用回车换行结束。

(1)访问命令

(1)访问命令有:

USER命令——格式为:USER <username>, 指定登录的用户名,以便服务器进行身份验证。这个命令通常是控制连接后第一个发出的命令

PASS命令——格式为:PASS <password>, 指定用户密码,该命令必须跟在登录用户名命令之后。

REIN命令——格式为:REIN, 表示重新初始化用户信息,该命令终止当前USER的传输,同时终止正在传输的数据,然后重置所有参数,并打开控制连接,以便客户端再次发生USER命令。

QUIT命令——格式为:QUIT,关闭与服务器的连接

(2)模式设置命令:

PASV命令——格式为:PASV,该命令告诉FTP服务器,让FTP服务器在指定的数据端口进行监听,被动接受客户端的请求。如果未指定任何模式,FTP服务器默认使用PASV模式

PORT命令——格式为:PORT <address>,该命令告诉FTP服务器,客户端监听的端口号是address,让FTP服务器采用主动模式连接客户端。

TYPE命令——格式为: TYPE <data type>,该命令指定要传输的数据类型,有ASCII和BINARY两种类型。

MODE命令——格式为:MODE <mode>,该命令指定传输模式,S表示流,B表示块,C表示压缩。

(3)文件管理命令

CWD命令——格式为:CWD <directory>,该命令是用户可以在不同的目录或数据集下工作而不用改变登录信息,directory一般是目录名或与系统相关的文件集合。

PWD命令——格式为:PWD,该命令返回当前工作目录。

MKD命令——格式为:MKD <directory>,该命令表示在指定路径下创建新目录,directory 表示特定目录的字符串。

CDUP命令——格式为:CDUP,该命令表示回到上层目录

RMD命令——格式为:RMD <directory>,删除指定目录,directory表示特定目录的字符串。

LIST命令——格式为:LIST <name>,该命令返回指定路径下的子目录及文件列表,name 为路径。省略路径时,返回当前路径下的文件列表。

NLIST命令——格式为:NLIST <directory>,该命令返回指定路径下的目录列表,省略路径时,返回当前目录。

RNFR命令——格式为:RNFR <old path>,该命令表示重新命名文件,该命令的下一条命令用RNTO指定新的文件名。

RNTO命令——格式为:RNTO <new path>,该命令和RNFR命令共同完成对文件的重命名。

DELE命令——格式为:DELE <filename>,该命令表示删除指定路径下的文件

(4)文件传输命令:

RETR命令——RETR <filename>,表示下载指定路径的文件

STOR命令——STOR <filename>,表示上传一个指定的文件,并将其存储在指定的位置,如果文件已存在,原文件将被覆盖,如果文件不存在,则创建新文件。

(5)其他命令

SYST命令——格式为:SYST,该命令返回服务器使用的操作系统。

4.2 FTP响应码

客户端发送FTP命令后,服务器需要返回FTP响应码,响应码即是回答,我们平常聊天中别人问了说了话或者问了问题,另外一方就需要回答,FTP协议中定义以响应码的形式来作为回答,FTP响应码由ASCII编码的3位数字开头,后面接一行文本提示信息,数字和提示信息中有一个空格,如XXX 接收请求。

每个响应码同样以回车换行结束。

FTP响应码的3位数字每位都有特定的意义,具体见下表:

响应码

表示

1

1XX

表示信息已被服务器正确接收,但尚未被处理

2XX

表示信息已被服务器正确处理完毕

3XX

彪西信息已被服务器正在接受,并正在处理中

4XX

表示信息处理错误(暂时)

5XX

表示信息处理错误(永久)

2

X0X

表示语法错误

X1X

表示系统状态与信息

X2X

表示与FTP服务器系统连接状态

X3X

表示与用户认证有关的信息

X4X

表示未定义

X5X

表示与文件系统有关的信息

 下表列出了常用的响应码所代表的意义:

响应码

意义

响应码

意义

110

重新启动标记应答

332

登陆是需要账户信息

120

服务在指定时间内准备好

350

请求的文件操作需要进一步命令

125

数据连接打开——开始传输

421

服务关闭

150

文件状态良好,将要打开数据连接

425

不能打开数据连接

200

命令成功

426

关闭连接,终止传输

202

命令没有执行

450

文件不可用

211

系统状态回复

451

中止请求操作:有本地错误

212

目录状态回复

452

磁盘空间不足

213

文件状态回复

500

无效命令

214

帮助信息回复

501

语法错误

215

系统类型回复

502

命令未执行

220

服务就绪

503

命令顺序错误

221

服务关闭控制连接,可以退出登陆

504

无效命令参数

225

数据连接打开,无传输正在进行

530

未登陆

226

关闭数据连接,请求的文件操作成功

532

存储文件需要账户信息

227

进入被动模式

550

未执行请求操作

230

用户已登陆

551

请求操作终止:页类型未知

250

请求的文件操作完成

552

请求文件操作终止:超过存储分配

257

创建路径名

553

为执行请求的操作:文件名不合法

331

用户名正确,需要口令

 

 

五、实现自定义的FTP服务器

相信大家看完上面的介绍对FTP协议以及FTP客户端和FTP服务器的交互过程有一定的理解的,这时候大家知道理论后就一定很想知道知道这些之后可以做什么的?答案就是可以制作一个简单的FTP服务器,大家可以根据代码来进一步理解FTP协议。下面是程序中一些核心代码片段:

View Code
// 启动服务器private void btnFtpServerStartStop_Click(object sender, EventArgs e){if (myTcpListener == null){listenThread = new Thread(ListenClientConnect);listenThread.IsBackground = true;listenThread.Start();lstboxStatus.Enabled = true;lstboxStatus.Items.Clear();lstboxStatus.Items.Add("启动Ftp服务...");btnFtpServerStartStop.Text = "停止";}else{myTcpListener.Stop();myTcpListener = null;listenThread.Abort();lstboxStatus.Items.Add("Ftp服务已停止!");lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1;btnFtpServerStartStop.Text = "启动";}}// 监听端口,处理客户端连接private void ListenClientConnect(){myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text));// 开始监听传入的请求
            myTcpListener.Start();AddInfo("启动成功!");AddInfo("Ftp服务运行中...[单机”停止“退出]");while (true){try{// 接收连接请求TcpClient tcpClient = myTcpListener.AcceptTcpClient();AddInfo(string.Format("客户端({0})与本机({1})建立Ftp连接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint));User user = new User();user.commandSession = new UserSeesion(tcpClient);user.workDir = tbxFtpRoot.Text;Thread t = new Thread(UserProcessing);t.IsBackground = true;t.Start(user);}catch{break;}}}// 处理客户端用户请求private void UserProcessing(object obj){User user = (User)obj;string sendString = "220 FTP Server v1.0";RepleyCommandToUser(user, sendString);while (true){string receiveString = null;try{// 读取客户端发来的请求信息receiveString = user.commandSession.streamReader.ReadLine();}catch(Exception ex){if (user.commandSession.tcpClient.Connected == false){AddInfo(string.Format("客户端({0}断开连接!)", user.commandSession.tcpClient.Client.RemoteEndPoint));}else{AddInfo("接收命令失败!" + ex.Message);}break;}if (receiveString == null){AddInfo("接收字符串为null,结束线程!");break;}AddInfo(string.Format("来自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString));// 分解客户端发来的控制信息中的命令和参数string command = receiveString;string param = string.Empty;int index = receiveString.IndexOf(' ');if (index != -1){command = receiveString.Substring(0, index).ToUpper();param = receiveString.Substring(command.Length).Trim();}// 处理不需登录即可响应的命令(这里只处理QUIT)if (command == "QUIT"){// 关闭TCP连接并释放与其关联的所有资源
                    user.commandSession.Close();return;}else{switch (user.loginOK){// 等待用户输入用户名:case 0:CommandUser(user, command, param);break;// 等待用户输入密码case 1:CommandPassword(user, command, param);break;// 用户名和密码验证正确后登陆case 2:switch (command){case "CWD":CommandCWD(user, param);break;case "PWD":CommandPWD(user);break;case "PASV":CommandPASV(user);break;case "PORT":CommandPORT(user, param);break;case "LIST":CommandLIST(user, param);break;case "NLIST":CommandLIST(user, param);break;// 处理下载文件命令case "RETR":CommandRETR(user, param);break;// 处理上传文件命令case "STOR":CommandSTOR(user, param);break;// 处理删除命令case "DELE":CommandDELE(user, param);break;// 使用Type命令在ASCII和二进制模式进行变换case "TYPE":CommandTYPE(user, param);break;default:sendString = "502 command is not implemented.";RepleyCommandToUser(user, sendString);break;}break;}          }}       }

 

程序演示截图:

 首先在F:\盘下新建文件夹MyFtpServerRoot,在其中创建目录结构并放一些文件资源,例如图片,文档等,程序中演示的目录结构如下图:

 这样,本地的FTP服务站点就已经建好了,运行FTP服务器程序,然后点击“启动”按钮后就启动了FTP服务器,运行结果如下图所示:

然后配合上个专题中实现的FTP客户端来完成与FTP服务器的“聊天”演示,因为FTP服务器程序中已经初始化用户名和密码(都为admin),所以FTP客户端中取消选择“匿名复选框”,直接输入用户名和密码为admin后点击“登录”按钮后就完成了用户验证的过程,并与FTP服务器建立了控制连接和数据连接。运行结果如下图:

当然用户可以通过"上传"、“下载”和删除按钮来对FTP服务器上的文件进行操作,这里就不贴出运行图片了, 大家可以下载源码来测试下的。

 

六、内容的结尾,说说后面的计划吧

这个专题介绍完后,我这个C#网络编程系列也就介绍完了,这个系列中主要介绍网络编程的一些入门知识,对于朋友在留言中经常提到的“打洞”技术以及一些网络编程中一些更难的内容还大家一起努力来学习的,同时我也会在后面和大家分享下一些实际开发过程中的网络编程的内容(在后面的文章打算和大家分享一个下载器的实现),最后,希望这个系列可以让大家对网络协议有一个最初的入门,这样在实际的开发过程中才知道这些实现背后的原理。之后我总结下我这个系列的所有文章的索引,以便让大家更好的阅读和查找关于这个系列的所有文章。

 

源码下载:http://files.cnblogs.com/zhili/FtpServer.zip,大家如果觉得不错的话,还请大家推荐下,谢谢大家的支持

 用来演示的服务器目录:http://files.cnblogs.com/zhili/MyFtpServerRoot.zip

 上个专题FTP文件上传下载器源码:http://files.cnblogs.com/zhili/FTPUpDownloader.zip

 

 

转载于:https://www.cnblogs.com/zhili/archive/2012/10/18/FTPServer.html

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

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

相关文章

vue 2 使用 Bus.js 实现兄弟 (非父子) 组件通信 简单案例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 vue2中废弃了$dispatch和$broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信&#xff0c;可…

jenkins自动化部署

jenkins自动化部署 github地址 首先设置源码地址&#xff0c;jenkins会从仓库中拉取最新代码 拉取代码后运行shell脚本自动进行编译 cd mediaService cmake -S . -B cmake-build-release-hisi3531 -DCMAKE_C_COMPILER/opt/hisi-linux/x86-arm/arm-hisiv500-linux/target/bin…

学开车不能急于求成,心急上路

我发现很多人开始学开车以后就急着要上路&#xff0c;觉得开车是很简单的事情&#xff0c;个人觉得开车虽然不难&#xff08;相对于会开车的人来说&#xff09;&#xff0c;但是&#xff0c;虽然不是很难&#xff0c;也不是一学会开车起步就能上路去潇洒的。急于求成的人很容易…

valgrind检测libevent内存泄露

valgrind检测libevent内存泄露 github地址 在使用封装好的http库时&#xff0c;遇到了如下的内存泄露&#xff0c;一开始在definitely处还存在泄露&#xff0c;这里就不贴图了&#xff0c;已经被淹没了。 根据提示定位出错代码位置&#xff0c;如下图&#xff1a; 这里提示ev…

大佬(概率期望DP)

首先根据数据范围&#xff0c;可以判断基本上是n^2的复杂度 通过分析我们发现每一次都可以从m个数中任意选&#xff0c;既然任意选&#xff0c;那么此时的概率的分母就是不变的&#xff0c;然而题中涉及的是某一段的最大值&#xff0c;所以我们按套路假设 f[i][j]表示第i天&…

高效的数据压缩编码方式 Protobuf

高效的数据压缩编码方式 Protobuf github地址 目录 ProtocolBuffers 是什么为什么要发明 ProtocolBuffersproto3 定义 Message 分配字段编号保留字段默认字段规则各个语言标量类型对应关系枚举枚举中的保留值允许嵌套枚举不兼容性更新 Message未知字段Map 类型JsonMapping p…

解决 VUE前端项目报错:RangeError: Maximum call stack size exceeded

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 我点击菜单按钮报错&#xff1a; RangeError: Maximum call stack size exceeded 2. 原因&#xff1a;参数传递有问题或者方法调用有…

新手必须掌握的学车技巧-上坡起步

我们知道&#xff0c;做什么事情都是万事开头难&#xff0c;新手们在学车方面更能体会到这一点&#xff0c;正确掌握学车技巧对于新手来说是非常重要的事情&#xff0c;今天&#xff0c;平安学车网&#xff08;www.paxcw.com&#xff09;就会大家探讨一下我们学车时必须掌握的是…

高效的序列化/反序列化数据方式 Protobuf

高效的序列化/反序列化数据方式 Protobuf github地址 目录 protocolBuffers 序列化 Int32StringMapslice序列化小结 protocolBuffers 反序列化 Int32StringMapslice序列化小结 序列化/反序列化性能最后 protocolBuffers序列化 上篇文章中其实已经讲过了 encode 的过程&…

解决 VUE前端项目报错: Uncaught ReferenceError : initPage is not defined (initPage 方法是有的,依旧报错找不到)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 明明代码中定义了 initPage 这个方法&#xff0c;&#xff0c;却一直报找不到这个方法&#xff1a; Uncaught ReferenceError: init…

掌握新手学车技巧对于新手来说是非常重要的

刚开始学车的时候对于新手来说很多操作不知道从哪里下手&#xff0c;这个时候&#xff0c;如果按照相关的学车技巧来学习的话&#xff0c;对于新手来说是非常有好处的。下面我们就来学习一下让新手们可以快速进入开车状态的学车技巧吧&#xff01;基本上驾校的教练都会教学员把…

iView学习笔记(三):表格搜索,过滤及隐藏列操作

iView学习笔记(三)&#xff1a;表格搜索&#xff0c;过滤及隐藏某列操作 1.后端准备工作 环境说明 python版本&#xff1a;3.6.6 Django版本&#xff1a;1.11.8 数据库&#xff1a;MariaDB 5.5.60 新建Django项目&#xff0c;在项目中新建app&#xff0c;配置好数据库 api_test…

Jenkins自动编译库并上传服务器

Jenkins自动编译库并上传服务器 github地址 首先添加 git 地址&#xff1a; 再添加定时构建&#xff0c;每天夜里构建一次&#xff1a; 执行 shell 脚本进行构建 cd networklayerecho "build json x86" cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPERele…

解决:The “data“ option should be a function that returns a per-instance value in component definitions

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 只是想定义一个变量&#xff0c;方便页面上调用 。 报错&#xff1a; The "data" option should be a function that re…

gdb 调试 TuMediaService

gdb 调试 TuMediaService github地址 起因 首先需要有 armgdb 环境运行 ./armgdb ./TuMediaService 进入 gdb 模式b hi3531_trcod_interface.cc:98 打断点r 运行程序print this->vdec_config_path_ 打印关键值 在这里我们关注的值已经被修改&#xff0c;由于程序中没有刻…

PyQt安装和环境配置

PyQt安装和环境配置 github地址 首先安装Pycharm 新建一个空的 python 工程&#xff0c;找到 setting 安装第三方模块 PyQT5 , 点加号&#xff0c;先安 PyQT5 , 再安装 pyqt5-tools &#xff0c;后面包含 qtdesinger 以上模块都安完&#xff0c;设置扩展工具的参数找到 sett…

HZOJ 大佬(kat)

及其水水水的假期望&#xff08;然而我已经被期望吓怕了……&#xff09;。 数据范围及其沙雕导致丢掉5分…… 因为其实每天的期望是一样的&#xff0c;考虑分开。 f[i][j]表示做k道题&#xff0c;难度最大为j的概率。 则f[i][j](f[i-1][j])*(j-1)*temq[j]*tem;q为前缀和&#…

F12 界面:请求响应内容 Preview 和 Response 不一致、接口返回数据和 jsp 解析到的内容不一致

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 情况描述&#xff1a; 我有一个接口只是简单的查询列表数据并返回给前端作一个表格展示。 接口返回的 userId 数据为&#xff1a;…

为什么新手开车起步总是熄火

最近&#xff0c;深圳市民陈小姐年前考完驾照就买了一辆新车&#xff0c;在过完年后上班的第一天&#xff0c;几乎每次等红绿灯的路口起步时汽车都会熄火&#xff0c;导致身后的司机非常不满狂按车喇叭催她“别挡路”&#xff0c;陈小姐自己也急得冒汗。就像陈小姐这样的新手很…

MSCRM日志配置

之前有很多人问我在MSCRM上日志怎么做&#xff0c;具体的如&#xff08;登录日志&#xff0c;操作日志&#xff09;。个人认为操作日志确实比较难做&#xff08;不过我可以给一个思路可以用触发器或者plugin来实现&#xff0c;不过比较麻烦&#xff0c;对系统压力也比较大&…