sz/rz实现及cat binary文件时乱码问题

摘要:
让我们回顾一下cat显示二进制文件的情况。事实上,cat程序不知道它显示的字节流是什么,所以它不遵循这个假设。不合规的结果是无法预测的行为。最直观的现象是显示乱码。

一、嵌入式系统中文件传输
这个工具之前还的确是没有使用到过,可能的原因是因为之前一直使用桌面系统fedora core发行版本,开发主要使用busybox文件系统,而这两种版本中都没有自带sz/rz工具。它们的作用是通过串口来发送和接收文件,虽然说是串口,所有的支持串口协议的软件或者链路都可以,例如使用telnet/ssh之类的远程链接工具,主机之间的通讯使用网卡来实现,而服务器端的主机通过伪终端来模拟一个串口链路,但是这两者结合就可以方便的进行文件传递。
这里工具的使用也就不多说了,因为大部分情况下大家使用也就是一个sz file 或者rz两个命令,而这两种格式是可以满足我们绝大部分的需求的。但是这个工具很有意思的是它无论中间经过了多少个telnet的中转,最后它都可以直达最为原始的接入端,也就是我们最为常见的windows下使用的secureCRT客户端,这一点在网络泛滥的今天乍一看还是有些神奇的。为什么一个请求可以翻山越岭、跋山涉水的直接到达连接发起的客户端而没有在中间的任何一种中转停留,一个报文是如何知道自己发向合入?当执行rz/sz的时候,这两个任务是不知道自己即将连接的客户端在哪里。
二、实现原理
事实上sz/rz使用的并不是我们常见的网络协议,而是使用了最为原始的基于串口的字节流协议。关于这一点,我在之前的一些日志中大致说明了一下在终端中经常使用的ESC序列,它们常见的功能就是控制终端中文字的显示颜色功能,例如通过ls默认显示压缩文件为红色的功能就是基于这种协议实现的。大致的说,就是我们常见的字节编码就是ASCII编码,这个编码中前32个字符是控制字符,它们在终端中是不会被显示的,而是用来控制显示。和冯诺依曼编码相同,它直接复用了链路中的字节流编码,同一个链路中,同样的字节单位,有的是代码,有的是数据。这些控制字符随着GUI的横行而越来越少的被大家使用和关注,所以可能很多人甚至都没有注意过ASCII码中前32个字节的存在。
这种风格对大家入学就学习《计算机网络》的同学来说可能有些不可思议,因为这是一种比较强的耦合关系,需要对同一个输出中的数据做不同的解释,看来总是有些简陋和诡异,但是这就是当时计算机的现实,这些传统不管在现在看来多么不可思议,它依然还是在影响着我们现在的计算机规范,一如那些历史课本中那些看似尘封的往事依然在影响着我们现在的生活一样。
关于字符集的编码总结,在赵炯《Linux内核完全剖析(第一版)》第10.4.3.3一节中有比较详细的描述,对于理解这个内容应该是很好的资料,当然最好的资料就是相关实现的源代码,不过代码这种东西很多人没有时间看,另外还有网络上一个比较好的解释,只是一个问题的恢复中的,贴出地址为https://bbs.archlinux.org/viewtopic.php?pid=423358#p423358。
这里串口通讯的基础假设是确定的:串口上所有的数据都是按照控制字符和可显字符严格确认,并且假设串口的所有使用者都明白并遵守这个假设。我们回头看一下cat显示二进制文件时候的情况,事实上一个cat程序并不知道自己显示的字节流是什么内容,所以说它是不遵守这个假设的,不遵守的结果就是造成了不可预料的行为,最为直观的现象就是显示的乱码问题。
三、串口乱码问题由来
对于控制字符来说,最为原始的32字符是不够用的,因为一个中断上颜色显示、光标移动、翻页等各种参数设置和查询需要很多的指令,此时就需要控制字符可以扩展。这里描述的所有扩展字符控制都是通过esc开始(ascii码为0x1b,八进制033),这其实相当于一个编码树,根节点为esc,然后生长出诸多的节点表示不同的序列和意义。
对于我们的乱码显示问题,这里涉及到字符集的选择问题。对于同样的内码,它虽然是可显示内容,但是它具体显示为什么内容却可以有不同的解释。对于字符集的装载是通过SCS指令序列(Select Character Set)来设定,这里的序列为 esc ( Ps和 Esc } Ps,其中前者用来选择G0(通俗的说就是内码小于128的显示字符),后者选择G1(内码大于128小于256的显示字符),而Ps的可选值有5个,分别为 A B 0 1 2分别表示UK字符集,US字符集,0 图形字符集,1另选ROM字符集、2另选ROM特殊字符集。任意时刻,一个终端可以有两个活动字符集,并且可以通过标准单字节控制字SI(Shift In)和SO(Shift Out)进行切入和切出。
对于上面的说明,可以看一下内核的实现,同样是在vt.c中,这个是对伪终端的一种模拟而不是真正的终端,所以它的实现和这里描述的有些不同,具体更加详细的原因和文档就不得而知了。
linux-2.6.21driverscharvt.c
static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
    case 14://(shift out)
        vc->vc_charset = 1;
        vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
        vc->vc_disp_ctrl = 1;
        return;
    case 15://(shift in)
        vc->vc_charset = 0;
        vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
        vc->vc_disp_ctrl = 0;
        return;

case ESsetG0:
        if (c == '0')
            vc->vc_G0_charset = GRAF_MAP;
        else if (c == 'B')
            vc->vc_G0_charset = LAT1_MAP;
        else if (c == 'U')
            vc->vc_G0_charset = IBMPC_MAP;
        else if (c == 'K')
            vc->vc_G0_charset = USER_MAP;
        if (vc->vc_charset == 0)
            vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
        vc->vc_state = ESnormal;
        return;
    case ESsetG1:
        if (c == '0')
            vc->vc_G1_charset = GRAF_MAP;
        else if (c == 'B')
            vc->vc_G1_charset = LAT1_MAP;
        else if (c == 'U')
            vc->vc_G1_charset = IBMPC_MAP;
        else if (c == 'K')
            vc->vc_G1_charset = USER_MAP;
        if (vc->vc_charset == 1)
            vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
        vc->vc_state = ESnormal;
        return;
其中set_translate使用了四张表,根据不同的参数来使用不同的映射方法。如果cat 一个二进制文件的时候,这个二进制文件很可能会包含这种修改系统字符集的操作,而修改之后显示的现象就是乱码。这里恢复有两种方式一种是使用用户态的reset命令完成恢复,另一种时使用终端内置的 esc c序列来完成恢复,内核代码为
    switch(vc->vc_state) {
    case ESesc:
……
        case 'c':
            reset_terminal(vc, 1);
            return;
我们这里可以测试一下,由于乱码无法复制出来,这里抓个图,可以看到在执行了单字符的SI之后,已经开始出现乱码,最后盲打执行 echo -e '

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇libusb-win32 在visual studio2008中成功编译回忆录启动tomcat时 一闪而过解决方法(2)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python--“re”详解

一、什么是正则表达式? 正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。 正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。  二、正则表达式的基本语法。 1.备选字符集  语法:[可选字符...

编一程序,将两个字符串连接起来,不要用strcat函数

编一程序,将两个字符串连接起来,不要用strcat函数 【答案解析】 直接将s2中的字符逐个拷贝到s1的末尾即可,用户需要保证s1中能存的下s2中的字符 获取s1末尾的位置 将s2中的字符逐个拷贝到s1中 【代码实现】 #include<stdio.h> int main() { char s1[100] = {0}; char s2...

Linux下的shell与make

Linux下的shell与make 一、shell 1.1 什么是shell ● 用户与Linux的接口 ● 命令解释器 ● 支持多用户 ● 支持复杂的编程语言 ● Shell有很多种,如:csh,tcsh,pdksh,ash,sash,zsh,bash等。Linux的缺省Shell为bash(Bourne Again Shell)。 Shell是用户和操...

MySQL学习笔记:字符串前后补全0

  遇到一个需求:不足6位的需要自动补全6位,使用函数LPAD()和RPAD()补全。   LPAD(str, len, padstr)   用字符串padstr对str进行左边填充补全直至它的长度达到len个字符,返回str。 一、前补0(左补0) SELECT LPAD(id,6,0) AS TIME FROM test;   结果:    二、后补0...

Qt正则表达式类QRegExp(转)

QRegExp是Qt的正则表达式类.Qt中有两个不同类的正则表达式.第一类为元字符.它表示一个或多个常量表达式.令一类为转义字符,它代表一个特殊字符.一.元字符.匹配任意单个字符.例如,1.3可能是1.后面跟任意字符,再... QRegExp是Qt的正则表达式类.Qt中有两个不同类的正则表达式.第一类为元字符.它表示一个或多个常量表达式.令一类为 转义字符...

使用EC20模组进行GNNS地理定位(AT命令)

最近公司想进行一个终端产品的研发工作,涉及到智能设备的地理定位,采用的GPRS模块是由深圳市有方科技有限公司生产的。 该地理定位用到了该模块的GNNS功能。该功能的说明手册为Quectel_EC20_GNSS_AT_Commands_Manual_V1.1.pdf。  经过研究该文档得出使用该功能的如下步骤。 1:设置该功能的输出模式  (AT+QGPSC...