《信息安全系统设计基础》——预备作业:实验楼教程学习

《信息安全系统设计基础》——预备作业:实验楼教程学习

————————CONTENTS————————


正则表达式基础

一、学习笔记

『正则表达式特殊符号』:

[:digit:]   Only the digits 0 to 9    
            匹配数字  
[:alnum:]   Any alphanumeric character 0 to 9 OR A to Z or a to z.    
            字母和数字  
[:alpha:]   Any alpha character A to Z or a to z.   
            字母A-Z, a-z  
[:blank:]   Space and TAB characters only.    
            匹配空格和 tag  
[:xdigit:]  Hexadecimal notation 0-9, A-F, a-f.  
            16进制数字  
[:punct:]   Punctuation symbols . , " ' ? ! ; : # $ % & ( ) * + - / < > = @ [ ] \ ^ _ { } | ~  
            标点符号  
[:print:]   Any printable character.  
            可打印字符  
[:space:]   Any whitespace characters (space, tab, NL, FF, VT, CR). Many system abbreviate as \s.  
            任何产生空白的字符  
[:graph:]   Exclude whitespace (SPACE, TAB). Many system abbreviate as \W.  
            除了空格和tag外的其他按键  
[:upper:]   Any alpha character A to Z.   
            大写  
[:lower:]   Any alpha character a to z.  
            小写  
[:cntrl:]   Control Characters NL CR LF TAB VT FF NUL SOH STX EXT EOT ENQ ACK SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC IS1 IS2 IS3 IS4 DEL.  
            代表键盘上的控制键

例如,新建文本并编辑:

查找全部小写字母:

查找全部数字:

『语系对正则表达式的影响』:

由于不同语系的编码数据不同,所以造成不同语系的数据选取结果有所差异。以英文大小写为例,zh_CN.big5 及 C 这两种语系差异如下:

  • LANG=C 时: 0 1 2 3 4....ABCDE...Zabcde...z
  • LANG=zh_CN 时:0 1 2 3 4...aAbBcCdD.....zZ

在使用正则表达式[A-Z]时, LANG=C 的情况下,找到的仅仅是大写字符 ABCD..Z。而在 LANG=zh_CN 情况下,会选取到 AbBcCdD.....zZ 字符。因此在使用正则表达式时要特别留意语系。

由于我们一般使用的兼容与 POSIX 的标准,因此使用 C 语系。

『grep命令常用参数』:

  • -a :将 binary 档案以 text 档案的方式搜寻数据

  • -c :计算找到 '搜寻字符串' 的次数

  • -i :忽略大小写的不同,所以大小写视为相同

  • -n :输出行号

  • -v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行

『字符组匹配』:

[]中包含的任意一个字符。只能是一个。

字符组支持由连字符“ - ”来表示一个范围。当“ - ”前后构成范围时,要求前面字符的码位小于后面字符的码位。

[^...] 排除型字符组。排除后面的字符。

  • [abc] :表示“a”或“b”或“c”
  • [0-9] :表示 0~9 中任意一个数字,等价于[0123456789]
  • [\u4e00-\u9fa5] :表示任意一个汉字
  • [^a1<] :表示除“a”、“1”、“<”外的其它任意一个字符
  • [^a-z] :表示除小写字母外的任意一个字符

例如,查找“on”或“in”:

查找不含“-”的字符串:

『行首符: ^ 与行尾符: $』:

查找行首为大写字母的所有行:

  • '[1]' 表示以大写字母开头。

  • '[^A-Z]' 表示除了大写字母 A-Z 的所有字符。

『任意一个字符: " . "(小数点)与重复字符 “ * ”(星号)』:

例如:查找符合“.n”的字符串(小数点表示任意一个字符,一个小数点只能表示一个未知字符)

  • *(星号):代表重复前面 0 个或者多个字符。
  • e*: 表示具有空字符或者一个以上 e 字符。
  • ee*,表示前面的第一个 e 字符必须存在。第二个 e 则可以是 0 个或者多个 e 字符。
  • eee*,表示前面两个 e 字符必须存在。第三个 e 则可以是 0 个或者多个 e 字符。
  • ee*e :表示前面的第一个与第三个 e 字符必须存在。第二个 e 则可以是 0 个或者多个 e 字符。

『限定连续字符范围{ }』:

{ }可限制一个范围区间内的重复字符数。举个例子,若要找出 2~5 个 o 的连续字符串,如何做? 此时便要用到{}了。由于 { 与 } 在 shell 中有特殊意义,需要用到转义字符\。

例如:查找连续的两个“l”

『总结』:

  • ^word :表示带搜寻的字符串(word)在行首

  • word$ :表示带搜寻的字符串(word)在行尾

  • .(小数点) :表示 1 个任意字符

  • \ :表示转义字符,在特殊字符前加\会将特殊字符意义去除

  • * :表示重复 0 到无穷多个前一个 RE(正则表达式)字符

  • [list] :表示搜索含有 l,i,s,t 任意字符的字符串

  • [n1-n2] :表示搜索指定的字符串范围,例如[0-9] [a-z] [A-Z]等

  • [^list] :表示反向字符串的范围,例如[0-9]表示非数字字符,[A-Z]表示非大写字符范围

  • {n,m} :表示找出 n 到 m 个前一个 RE 字符

  • {n,} :表示 n 个以上的前一个 RE 字符

『sed 工具命令』:

sed 是非交互式的编辑器。它不会修改文件,除非使用 shell 重定向来保存结果。默认情况下,所有的输出行都被打印到屏幕上。

sed 编辑器逐行处理文件(或输入),并将结果发送到屏幕。具体过程如下:首先 sed 把当前正在处理的行保存在一个临时缓存区中(也称为模式空间),然后处理临时缓冲区中的行,完成后把该行发送到屏幕上。sed 每处理完一行就将其从临时缓冲区删除,然后将下一行读入,进行处理和显示。处理完输入文件的最后一行后,sed 便结束运行。sed 把每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。

如果要修改原文件,可使用-i 选项。

  • 将 regular_express.txt 的内容列出并打印行号,同时,将第2行删除显示:

  • 在原文件中删除第1行并显示:$ sed -i '1d' regular_express.txt
  • a表示在行后加上字符串,i表示在行前添加字符串:

  • 将 2-5 行内容取代为 No 2-5 number(c为替换内容选项):$ nl regular_express.txt | sed '2,5c No 2-5 number'

  • 列出 regular_express.txt 内第 5-7 行:$ nl regular_express.txt |sed '5,7p';sed 命令中-n 为安静模式选项:$ nl regular_express.txt |sed -n '5,7p'

  • 替换字符串:sed 's/被替换字符串/新字符串/g'

『扩展正则表达式』:

grep 默认仅支持基础正则表达式,如果要使用扩展性正则表达式,可以使用 grep - E。grep -E 与 egrep 相当于命令别名关系。

    • :表示重复一个或一个以上的前一个 RE 字符:$ egrep -n 'go+d' regular_express.txt
  • ? : 表示重复零个或一个前一个 RE 字符:$ egrep -n 'go?d' regular_express.txt
  • | :表示用或的方式找出数个字符串:$ egrep -n 'gd|good' regular_express.txt
  • () : 表示找出群组字符串:$ egrep -n 'g(la|oo)d' regular_express.txt,也就是搜寻glad或 good 这两个字符串
  • ()+ : 多个重复群组判别:$ echo 'AxyzxyzxyzxyzC'|egrep 'A(xyz)+C',找开头是 A 结尾是 C 中间有一个以上的‘xyz’的字符串

二、参考资料

返回目录


Vim编辑器

学习笔记

一、基础知识

『vim的几种基本模式』:

  • 普通模式(Normal mode):在普通模式中,用的编辑器命令,比如移动光标,删除文本等等。这也是Vim启动后的默认模式。Vim强大的编辑能来自于其普通模式命令。普通模式命令往往需要一个操作符结尾。例如普通模式命令dd删除当前行,但是第一个"d"的后面可以跟另外的移动命令来代替第二个d,比如用移动到下一行的"j"键就可以删除当前行和下一行。另外还可以指定命令重复次数,2dd(重复dd两次),和dj的效果是一样的。用户学习了各种各样的文本间移动/跳转的命令和其他的普通模式的编辑命令,并且能够灵活组合使用的话,能够比那些没有模式的编辑器更加高效的进行文本编辑。在普通模式中,有很多方法可以进入插入模式。比较普通的方式是按a(append/追加)键或者i(insert/插入)键。
  • 插入模式(Insert mode):在这个模式中,大多数按键都会向文本缓冲中插入文本。大多数新用户希望文本编辑器编辑过程中一直保持这个模式。在插入模式中,可以按ESC键回到普通模式。
  • 可视模式(Visual mode):这个模式与普通模式比较相似。但是移动命令会扩大高亮的文本区域。高亮区域可以是字符、行或者是一块文本。当执行一个非移动命令时,命令会被执行到这块高亮的区域上。Vim的"文本对象"也能和移动命令一样用在这个模式中。
  • 选择模式(Select mode):这个模式和无模式编辑器的行为比较相似(Windows标准文本控件的方式)。这个模式中,可以用鼠标或者光标键高亮选择文本,不过输入任何字符的话,Vim会用这个字符替换选择的高亮文本块,并且自动进入插入模式。
  • 命令行模式(Command line mode):在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。在命令执行之后,Vim返回到命令行模式之前的模式,通常是普通模式。
  • Ex模式(Ex mode):这和命令行模式比较相似,在使用:visual命令离开Ex模式前,可以一次执行多条命令。

这其中常用到就是普通模式插入模式命令行模式,因此将重点学习。其他几种模式了解即可。

『三种常用模式的切换』:

  • vim启动进入普通模式
  • 插入/命令行模式→普通模式:按Esc或者Ctrl+[
  • 普通模式→插入模式:按i(插入)或a(附加)键
  • 普通模式→命令行模式:按:
  • 命令行模式中输入wq回车后保存并退出vim

『进入vim』:

  • 直接打开vim编辑器:$ vim
  • 打开已有文件或新建并编辑文件:$ vim 文件名
  • 进入命令行模式后输入:e 文件路径同样可以打开相应文件。

『进入插入模式』:

  • i:在当前光标处进行编辑
  • I:在行首插入
  • A:在行末插入
  • a:在光标后插入编辑
  • o:在当前行后插入一个新行
  • O:在当前行前插入一个新行
  • cw:替换从光标所在位置后到一个单词结尾的字符

『退出vim』:

  • :q!:强制退出,不保存
  • :q:退出
  • :wq!:强制保存并退出
  • :w <文件路径>:另存为
  • :saveas 文件路径:另存为
  • :x:保存并退出
  • :wq:保存并退出

普通模式下输入Shift+zz即可保存退出vim。

『删除文本』:

进入普通模式,使用下列命令可以进行文本快速删除:

  • x:删除游标所在的字符
  • X :删除游标所在前一个字符
  • Delete: 同x
  • dd :删除整行
  • dw :删除一个单词(不适用中文)
  • d$或D :删除至行尾
  • d^ :删除至行首
  • dG :删除到文档结尾处
  • d1G :删至文档首部

二、Vim文档编辑

『vim重复命令』:

1、重复执行上次命令:在普通模式下,.(小数点)表示重复上一次的命令操作。例如:普通模式下输入x,删除第一个字符,输入.(小数点)会再次删除一个字符,除此之外也可以重复dd的删除操作.

2、执行指定次数相同的命令:普通模式下,可以使用次数+操作来执行一定次数的某操作。例如:输入10x,删除10个连续字符;输入3dd,将会删除3行文本。

在普通模式下,还可以使用dw或者daw(delete a word)删除一个单词,所以可以很容易的联想到dnw(n替换为相应数字)表示删除n个单词。

『游标的快速跳转』:

1、行间跳转

  • nG(n Shift+g):游标移动到第 n 行(如果默认没有显示行号,请先进入命令模式,输入:set nu以显示行号)
  • gg:游标移动到到第一行
  • G(Shift+g):到最后一行

2、行内跳转:普通模式下使用下列命令在行内按照单词为单位进行跳转

  • w:到下一个单词的开头
  • e:到下一个单词的结尾
  • b:到前一个单词的开头
  • ge:到前一个单词的结尾
  • 0或^:到行头
  • $:到行尾
  • f<字母>:向后搜索<字母>并跳转到第一个匹配的位置(非常实用)
  • F<字母>:向前搜索<字母>并跳转到第一个匹配的位置
  • t<字母>:向后搜索<字母>并跳转到第一个匹配位置之前的一个字母(不常用)
  • T<字母>:向前搜索<字母>并跳转到第一个匹配位置之后的一个字母(不常用)

『复制粘贴和剪切』:

1、复制及粘贴文本

  • 普通模式中使用y复制
    • 普通模式中,yy复制游标所在的整行(3yy表示复制3行)
    • 普通模式中,y^ 复制至行首,或y0。不含光标所在处字符。
    • 普通模式中,y$ 复制至行尾。含光标所在处字符。
    • 普通模式中,yw 复制一个单词。
    • 普通模式中,y2w 复制两个单词。
    • 普通模式中,yG 复制至文本末。
    • 普通模式中,y1G 复制至文本开头。
  • 普通模式中使用p粘贴
    • 普通模式中,p(小写)代表粘贴至光标后(下)
    • 普通模式中,P(大写)代表粘贴至光标前(上)

2、剪切及粘贴

dd删除命令就是剪切,每次dd删除文档内容后,便可以使用p来粘贴。

三、查找和替换

1、替换和撤销(Undo)命令

  • r+<待替换字母> :将游标所在字母替换为指定字母
  • R :连续替换,直到按下Esc
  • cc :替换整行,即删除游标所在行,并进入插入模式
  • cw :替换一个单词,即删除一个单词,并进入插入模式
  • C(大写) :替换游标以后至行末
  • ~ :反转游标所在字母大小写
  • u{n} :撤销一次或n次操作
  • U(大写) :撤销当前行的所有修改
  • Ctrl+r :redo,即撤销undo的操作

2、快速缩进

  • 普通模式下输入>> 整行向右缩进
  • 普通模式下输入<< 整行向左回退
  • 普通模式下输入:进入命令行模式下对shiftwidth值进行设置可以控制缩进和回退的字符数,例如::set shiftwidth=10即设置缩进为10个字符
  • 命令行模式下输入:ce(center)命令使本行内容居中
  • 命令行模式下输入:ri(right)命令使本行文本靠右
  • 命令行模式下输入:le(left)命令使本行内容靠左

3、查找

(1)快速查找

  • 普通模式下输入/然后键入需要查找的字符串 按回车后就会进行查找。
  • /功能相同,只不过是向上而/是向下查找。
  • 进入查找之后,输入nN可以继续查找
  • n表示继续查找,N反向查找

(2)高级查找

  • 普通模式下输入\*寻找游标所在处的单词
  • 普通模式下输入\#同上,但 \# 是向前(上)找,\*则是向后(下)找
  • 普通模式下输入g\*\* ,但部分符合该单词即可
  • 普通模式下输入g\#\# ,但部分符合该单词即可

四、高级功能入门

1、多文件编辑

(1)使用vim编辑多个文件:编辑多个文件有两种形式,一种是在进入vim前使用的参数就是多个文件。另一种就是进入vim后再编辑其他的文件。

  • 同时创建两个新文件并编辑:$ vim 1.txt 2.txt,默认进入1.txt文件的编辑界面
  • 命令行模式下输入:n编辑2.txt文件,可以加!即:n!强制切换,之前一个文件的输入没有保存,仅仅切换到另一个文件
  • 命令行模式下输入:N编辑1.txt文件,可以加!即:N!强制切换,之前文件内的输入没有保存,仅仅是切换到另一个文件

(2)进入vim后打开新文件

  • 命令行模式下输入:e 3.txt 打开新文件3.txt
  • 命令行模式下输入:e# 回到前一个文件
  • 命令行模式下输入:ls可以列出以前编辑过的文档
  • 命令行模式下输入:b 2.txt(或者编号)可以直接进入文件2.txt编辑
  • 命令行模式下输入:bd 2.txt(或者编号)可以删除以前编辑过的列表中的文件项目
  • 命令行模式下输入:e! 4.txt,新打开文件4.txt,放弃正在编辑的文件
  • 命令行模式下输入:f 显示正在编辑的文件名
  • 命令行模式下输入:f new.txt,改变正在编辑的文件名字为new.txt

(3)恢复文件

如果因为断电等原因造成文档没有保存,可以采用恢复方式,vim -r进入文档后,输入:ewcover 1.txt来恢复

2、可视模式

  • 在普通模式下输入v(小写),进入字符选择模式,就可以移动光标,光标走过的地方就会选取。再次按下v会后就会取消选取。
  • 在普通模式下输入Shift+v(小写),进入行选择模式,按下V之后就会把整行选取,您可以上下移动光标选更多的行,同样,再按一次Shift+v就可以取消选取。
  • 在普通模式下输入Ctrl+v(小写),这是区域选择模式,可以进行矩形区域选择,再按一次Ctrl+v取消选取。
  • 在可视模式下输入d删除选取区域内容
  • 在可视模式下输入y复制选取区域内容

3、视窗操作

vim可以在一个界面里打开多个窗口进行编辑,这些编辑窗口称为vim的视窗。

打开方法有很多种,例如可以使用在命令行模式下输入:new 打开一个新的vim视窗,并进入视窗编辑一个新文件(普通模式下输入Ctrl+w也可以,但是Ctrl+w在chrome下会与chrome关闭标签页的快捷键冲突,所以使用该快捷键你可以在IE或其它浏览器进行练习),除了:new命令,下述列举的多种方法也可以在命令模式或普通模式下打开新的视窗:

  • 命令行模式下输入:sp 1.txt 打开新的水平分屏视窗来编辑1.txt
  • 命令行模式下输入:vsp 2.txt 打开新的垂直分屏视窗来编辑2.txt
  • 普通模式下Ctrl-w s 将当前窗口分割成两个水平的窗口
  • 普通模式下Ctrl-w v 将当前窗口分割成两个垂直的窗口
  • 普通模式下Ctrl-w q即 :q 结束分割出来的视窗。如果在新视窗中有输入需要使用强制符!即:q!
  • 普通模式下Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗
  • 普通模式下Ctrl-w j 移至下面视窗
  • 普通模式下Ctrl-w k 移至上面视窗
  • 普通模式下Ctrl-w h 移至左边视窗
  • 普通模式下Ctrl-w l 移至右边视窗
  • 普通模式下Ctrl-w J 将当前视窗移至下面
  • 普通模式下Ctrl-w K 将当前视窗移至上面
  • 普通模式下Ctrl-w H 将当前视窗移至左边
  • 普通模式下Ctrl-w L 将当前视窗移至右边
  • 普通模式下Ctrl-w - 减小视窗的高度
  • 普通模式下Ctrl-w + 增加视窗的高度

ps:在练习过程中,渐渐体会到vim命令键组合和全键盘操作的方便之处。不过为什么要使用hjkl这四个作为方向键呢?直接使用上下左右键不是更好吗?

之后去查找维基百科上对于ADM-3A的介绍,发现初创者的键盘构造与现在的不完全相同,键盘上并没有上下左右键。而hjkl是右手停留的位置,这大概就是选择这四个键的原因吧:)

附:参考资料

返回目录


TCP/IP网络协议基础

学习笔记

一、TCP/IP简介

『TCP/IP 背景和介绍』:

  • TCP/IP(Transmission Control Protocol/Internet Protocol)是传输控制协议和网络协议的简称,它定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。

  • TCP/IP 不是一个协议,而是一个协议族的统称,里面包括了 IP 协议、ICMP 协议、TCP 协议、以及 http、ftp、pop3 协议等。网络中的计算机都采用这套协议族进行互联。

1、网络协议栈架构

TCP/IP 被分为 4 层,每层承担的任务不一样,各层的协议的工作方式也不一样,每层封装上层数据的方式也不一样:

(1)应用层:应用程序通过这一层访问网络,常见 FTP、HTTP、DNS 和 TELNET 协议;

(2)传输层:TCP 协议和 UDP 协议;

(3)网络层:IP 协议,ARP、RARP 协议,ICMP 协议等;

(4)网络接口层:是 TCP/IP 协议的基层,负责数据帧的发送和接收。

『预备知识』:

1、IP 地址

  • 网络上每一个节点都必须有一个独立的 IP 地址,通常使用的 IP 地址是一个 32bit 的数字,被 . 分成 4 组,例如,255.255.255.255 就是一个 IP 地址。有了 IP 地址,用户的计算机就可以发现并连接互联网中的另外一台计算机。

  • 在 Linux 系统中,可以用 ifconfig -a 命令查看自己的 IP 地址:

2、域名

  • 用 12 位数字组成的 IP 地址很难记忆,在实际应用时,用户一般不需要记住 IP 地址,互联网给每个 IP 地址起了一个别名,习惯上称作域名。

  • 域名与计算机的 IP 地址相对应,并把这种对应关系存储在域名服务系统 DNS(Domain Name System) 中,这样用户只需记住域名就可以与指定的计算机进行通信了。

  • 常见的域名包括 com、net 和 org 三种顶级域名后缀,除此之外每个国家还有自己国家专属的域名后缀(比如我国的域名后缀为 cn)。目前经常使用的域名诸如百度(www.baidu.com)、Linux 组织(www.lwn.net)等等。

我们可以使用命令 nslookup 或者 ping 来查看与域名相对应的 IP 地址,由于实验楼网络限制,我们可以使用ping github.com查看。

3、MAC 地址

  • MAC(Media Access Control)地址,或称为物理地址、硬件地址,用来定义互联网中设备的位置。

  • 在 TCP/IP 层次模型中,网络层管理 IP 地址,链路层则负责 MAC 地址。因此每个网络位置会有一个专属于它的 IP 地址,而每个主机会有一个专属于它 MAC 地址。

4、端口号

  • IP 地址是用来发现和查找网络中的地址的,但是不同程序如何互相通信呢,这就需要端口号来识别了。

  • 常用协议对应端口号:

    • SSH 22
    • FTP 20 和 21
    • Telnet 23
    • SMTP 25
    • TFTP 69
    • HTTP 80
    • SNMP 161
    • Ping 使用ICMP,无具体端口号

5、封装和分用

  • 封装:当应用程序发送数据的时候,数据在协议层次当中从顶向下通过每一层,每一层都会对数据增加一些首部或尾部信息,这样的信息称之为协议数据单元(Protocol Data Unit,缩写为PDU),在分层协议系统里,在指定的协议层上传送的数据单元,包含了该层的协议控制信息和用户信息。

    • 物理层(一层)PDU指数据位(Bit)
    • 数据链路层(二层)PDU指数据帧(Frame)
    • 网络层(三层)PDU指数据包(Packet)
    • 传输层(四层)PDU指数据段(Segment)
    • 第五层以上为数据(data)
  • 分用:当主机收到一个数据帧时,数据就从协议层底向上升,通过每一层时,检查并去掉对应层次的报文首部或尾部,与封装过程正好相反。

6、RFC

RFC(Request for Comment)文档是所有以太网协议的正式标准,并在其官网上面公布,由 IETF 标准协会制定。大量的 RFC 并不是正式的标准,出版的目的只是为了提供信息。RFC 的篇幅不一,从几页到几百页不等。每一种协议都用一个数字来标识,如 RFC 3720 是 iSCSI 协议的标准,数字越大说是 RFC 的内容越新或者是对应的协议(标准)出现的比较晚。

二、链路层介绍

『简介』:

  • 网络层协议的数据单元是 IP 数据报 ,而数据链路层的工作就是把网络层交下来的 IP 数据报 封装为 帧(frame)发送到链路上,以及把接收到的帧中的数据取出并上交给网络层。

  • 为达到这一目的,数据链路必须具备一系列相应的功能,主要有:

    • 将数据封装为帧(frame),帧是数据链路层的传送单位;
    • 控制帧的传输,包括处理传输差错,调节发送速率与接收方相匹配;
    • 在两个网络实体之间提供数据链路通路的建立、维持和释放的管理。
  • 数据帧的结构是这样的:

『控制帧的传输』:

1、差错控制

通信系统必须具备发现差错的能力,并采取措施纠正之,使差错控制在所能允许的尽可能小的范围内,这就是差错控制过程,也是数据链路层的主要功能之一。

  • 反馈重发

接收方通过对差错编码(奇偶校验码或 CRC 码)的检查,可以判定一帧在传输过程中是否发生了差错。一旦发现差错,一般可以采用反馈重发的方法来纠正。这就要求接受方收完一帧后,向发送方反馈一个接收是否正确的信息,使发送方据此做出是否需要重新发送的决定。发送方仅当收到接收方已正确接收的反馈信号后才能认为该帧已经正确发送完毕,否则需要重发直至正确为止。

  • 计时器

如果某一帧发送出现问题,一直不能发送成功,为了避免传输过程停滞不前,通常引入 计时器 (Timer) 来限定接收方发回反馈消息的时间间隔。当发送方发送一帧的同时也启动计时器,若在限定时间间隔内未能收到接收方的反馈信息,即计时器超时(Timeout),则可认为传出的帧以出错或丢失,就要重新发送。

  • 序号

由于同一帧数据可能被重复发送多次,就可能引起接收方多次收到同一帧并将其递交给网络层的情况。为了防止这种情况,可以采用对发送的帧编号的方法,即赋予每帧一个序号,从而使接收方能从该序号来区分是新发送来的帧还是重发的帧,以此来确定要不要将接收到的帧递交给网络层。

2、流量控制

由于收发双方各自使用的设备工作速率和缓冲存储空间的差异,可能出现发送方的发送能力大于接收方接收能力的现象,此时若不对发送方的发送速率做适当的限制,前面来不及接收的帧将被后面不断发送来的帧“淹没”,从而造成帧的丢失而出错。

由此可见,流量控制实际上是对发送方数据流量的控制,使其发送速率不超过接收方的速率。所以需要一些规则使得发送方知道在什么情况下可以接着发送下一帧,而在什么情况下必须暂停发送,以等待收到某种反馈信息后再继续发送。这就是流量控制。

『以太网』:

以太网(Ether-net)是指 DEC 公司、Intel 公司和 Xerox 公司在 1982 年联合公布的一个标准,这个标准里面使用了一种称作 CSMA/CD 的接入方法。而 IEEE802 提供的标准集 802.3(还有一部分定义到了 802.2 中)也提供了一个 CSMA/CD 的标准。

这两个标准稍有不同,因此链路层数据帧的的封装格式也有所不同(数据帧中的地址为 MAC 地址):

『PPP(点对点协议)』:

PPP(点到点协议)是为在同等单元之间传输数据设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据。设计目的主要是用来通过 拨号或专线 方式建立 点对点 连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。

点对点协议(PPP)为在点对点连接上传输多协议数据包提供了一个标准方法。PPP 最初设计是为两个对等节点之间的 IP 流量传输提供一种封装协议。在 TCP/IP 协议集中它是一种用来同步调制连接的数据链路层协议。

『SLIP 与 PPP』:

1、SLIP 协议

SLIP 的全称为 Serial Line IP(串行线路 IP)。它是一种对 IP 数据报进行封装的简单形式。

SLIP 协议规定的帧格式规则:

  • IP 数据报以一个称作 END(0xc0)的特殊字符结束。同时为了防止数据报传输之前的线路噪音被误认为是数据报内容,在数据报开始处添加一个 END 字符;
  • 如果 IP 数据报中含有 END 字符,就连续传输 0xdb 和 0xdc 来取代它。0xdb 是 SLIP 的 ESC 字符,但它的值与 ASCⅡ码中的 ESC(0x1b)不同;
  • 如果 IP 数据报中含有 ESC 字符,就连续传输 0xdb 和 0xdd 来取代它。

SLIP 的缺陷:

  • 每一端必须知道对端的 IP 地址,没有办法把本端 IP 地址传递给对端;
  • 数据帧中无类型字段,当一条串行线路使用 SLIP 时则不能使用其他协议;
  • SLIP 数据帧中无 checksum,只能依靠上层协议来发现和纠正错误。

2、PPP 协议

PPP 协议修改了 SLIP 协议中的缺陷,包括以下三个部分:

  • PPP 封装 IP 数据报既支持数据为 8 位和无奇偶校验的异步模式,又支持面向比特的同步链接;
  • 通过 LCP(链路控制协议)允许双方进行协商;
  • 通过 NCP(网络控制协议)允许双方在网络层上进行协商。

PPP 协议的字符规则与 SLIP 有所不同:

  • PPP 帧以标志字符 0x7e 开始和结束,紧接着是一个值为 0xff 的地址字节,然后是一个值为 0x03 的控制字节;
  • 由于标志字符是 0x7e,当它出现在信息字段中时,需要连续传送 0x7d 和 0x5e 来替代它;
  • 当在信息字段中遇到 0x7d 时,需要连续传送 0x7d 和 0x5d 来替代它。
  • 默认情况下,如果字符的值小于 0x20,需要连续传送 0x7d 和 0x21 来替代它。

PPP 与 SLIP 相比具有下列优点:

  • PPP 支持在单根串行线路上运行多种网络层协议;
  • 每一帧都有 CRC 校验;
  • 通信双方可以用 NCP 进行 IP 地址的动态协商;
  • 可以类似于 CSLIP 对 TCP 和 IP 首部进行压缩;
  • LCP 可以对多个数据链路选项进行设置。

『MTU』:

为了提供足够快的响应时间,以太网和 IEEE802.3 对数据帧长度都有限制,其最大值分别为 1500 字节和 1492 字节,链路层的这个特性称作 MTU ,即** 最大传输单元 **。

当网络层传下来一个 IP 数据报,并且其长度比链路层的 MTU 大,那么网络层就需要对数据报进行分片,使每一片都小于 MTU。

MTU 分为接口 MTU 和路径 MTU:接口 MTU 是所指定的接口所允许发送的最大数据长度;路径 MTU 指两台通信主机路径中最小的 MTU 值。路径 MTU 是不对称的,它在两个方向上不一定一致。

用命令 netstat -in 可以查看网络接口的 MTU:

三、IP 网际协议

IP 协议位于网络层,它是 TCP/IP 协议族中最为核心的协议,所有的 TCP、UDP、ICMP 及 IGMP 数据都以 IP 数据报格式传输。IP 协议提供的是 不可靠无连接 的数据报传送服务。

  • 不可靠(unreliable):IP 协议不能保证数据报能成功地到达目的地,它仅提供传输服务。当发生某种错误时,IP 协议会丢弃该数据报。传输的可靠性全由上层协议来提供。
  • 无连接(connectionless):IP 协议对每个数据报的处理是相互独立的。这也说明, IP 数据报可以不按发送顺序接收。如果发送方向接收方发送了两个连续的数据报(先是 A,然后是 B),每个数据报可以选择不同的路线,因此 B 可能在 A 到达之前先到达。

『IP 数据报』:

如上图所示,普通的 IP 数据报的报头长度 20 字节(除非有选项字段),各个部分的作用:

  • 版本号 :4 位,用于标明 IP 版本号,0100 表示 IPv4,0110 表示 IPv6。目前常见的是 IPv4。
  • 首部长度 :4 位,表示 IP 报头长度,包括选项字段。
  • 服务类型(TOS) :分别有:最小时延、最大吞吐量、最高可靠性、最小花费 4 种服务,如下图所示。4 个标识位只能有一个被置为 1 :

  • 总长度 :16 位,报头长度加上数据部分长度,便是数据报的总长度。IP 数据报最长可达 65535 字节。
  • 标识 :16 位,接收方根据分片中的标识字段相不相同来判断这些分片是不是同一个数据报的分片,从而进行分片的重组。通常每发送一份报文它的值就会加 1。
  • 标志 :3 位,用于标识数据报是否分片。其中的第 2 位是不分段(DF)位。当 DF 位被设置为 1 时,则不对数据包进行分段处理;第 3 位是分段(MF)位,除了最后一个分段的 MF 位被设置为 0 外,其他的分段的 MF 位均设置为 1。
  • 偏移 :13 位,在接收方进行数据报重组时用来标识分片的顺序。
  • 生存时间(TTL) :8 位,用于设置数据报可以经过的最多的路由器个数。TTL 的初始值由源主机设置(通常为 32 或 64),每经过一个处理它的路由器,TTL 值减 1。如果一个数据报的 TTL 值被减至 0,它将被丢弃。
  • 协议 :8 位,用来标识是哪个协议向 IP 传送数据。ICMP 为 1,IGMP 为 2,TCP 为 6,UDP 为 17,GRE 为 47,ESP 为 50。
  • 首部校验和 :根据 IP 首部计算的校验和码。
  • 源 IP 和目的 IP :数据报头还会包含该数据报的发送方 IP 和接收方 IP。
  • 选项 :是数据报中的一个可变长、可选的信息,不常用,多用于安全、军事等领域。

『IP 地址分类』:

为了便于寻址以及层次化构造网络,每个 IP 地址可被看作是分为两部分,即 网络号主机号 。同一个区域的所有主机有相同的网络号(即 IP 地址的前半部分相同),区域内的每个主机(包括路由器)都有一个主机号与其对应。

IP 地址被分为 A,B,C,D,E 五类,其中 A 类给大型网络或政府机构等,B 类分配给中型网络、跨国企业等,C 类分配给小型网络,D 类用于多播,E 类用于实验,各类可容纳的地址数目不同。其中我们最常见的为 A,B,C 这三类。

IP 地址用 32 位二进制数字表示的时候,A,B,C 类 IP 的网络号长度分别为 8 位、16 位、24 位:

  • A 类地址

    • A 类地址网络号范围:1.0.0.0---127.0.0.0
    • A 类 IP 地址范围:1.0.0.0---127.255.255.255
    • A 类 IP 的私有地址范围:10.0.0.0---10.255.255.255 (所谓的私有地址就是在互联网上不使用,而被用在局域网络中的地址)
    • 127.X.X.X 是保留地址,用做循环测试用的
    • 因为主机号有 24 位,所以一个 A 类网络号可以容纳 2^24-2=16777214 个主机号
  • B 类地址

    • B 类地址网络号范围:128.0.0.0---191.255.0.0
    • B 类 IP 地址范围:128.0.0.0---191.255.255.255
    • B 类 IP 的私有地址范围:172.16.0.0---172.31.255.255
    • 169.254.X.X 是保留地址;191.255.255.255 是广播地址
    • 因为主机号有 16 位,所以一个 B 类网络号可以容纳 2^16-2=65534 个主机号
  • C 类地址

    • C 类地址网络号范围:192.0.0.0---223.255.255.0
    • C 类 IP 地址范围:192.0.0.0---223.255.255.255
    • C 类 IP 的私有地址范围:192.168.0.0---192.168.255.255
    • 因为主机号有 8 位,所以一个 C 类网络号可以容纳 2^8-2=254 个主机号

『子网划分』:

IP 地址如果只使用 ABCDE 类来划分,会造成大量的浪费:一个有 500 台主机的网络,无法使用 C 类地址。但如果使用一个 B 类地址,6 万多个主机地址只有 500 个被使用,造成 IP 地址的大量浪费。

因此,可以在 ABC 类网络的基础上,进一步划分子网:占用主机号的前几个位,用于表示子网号

这样 IP 地址就可看作 IP =** 网络号** + 子网号 + 主机号

子网号的位数没有硬性规定,于是我们用 子网掩码 来确定一个 IP 地址中哪几位是主机号,具体使用方法如图:

子网掩码中的 1 标识了 IP 地址中相应的网络号,0 标识了主机号。将 IP 地址和子网掩码进行 逻辑与运算 ,结果就能区分网络号和子网号。

『IP 路由选择』:

如果发送方与接收方直接相连(点对点)或都在一个共享网络上(以太网),那么 IP 数据报就能直接送达。

而大多数情况则是发送方与接收方通过若干个路由器(router)连接,那么数据报就需要经过若干个路由器的转发才能送达,它是怎么选择一个合适的路径来"送货"的呢?

IP 层在内存中有一个路由表(输入命令 route -n 可以查看路由表),当收到一份数据报并进行发送时,都要对该表进行搜索:

  • 1、搜索路由表,如果能找到和目的 IP 地址完全一致的主机,则将 IP 数据报发向该主机;
  • 2、搜索路由表,如果匹配主机失败,则匹配同子网的路由器(这需要子网掩码的协助)。如果找到路由器,则将 IP 该数据报发向该路由器;
  • 3、搜索路由表,如果匹配同子网路由器失败,则匹配同网络号路由器,如果找到路由器,则将该 IP 数据报发向该路由器;
  • 4、如果以上都失败了,就搜索默认路由,如果默认路由存在,则发报;
  • 5、如果都失败了,就丢掉这个包;
  • 6、接收到数据报的路由器再按照它自己的路由表继续转发,直到数据报被转发到目的主机;
  • 7、如果在转发过程中,IP 数据报的 TTL(生命周期)已经被减为 0,则该 IP 数据报就被抛弃。

『NAT 技术』:

当你用 ifconfig 查看 IP 地址时,有时你会发现自己的 IP 地址是这样的———192.168.X.X 或 172.16.X.X
这是 C 类网和 B 类网的私有地址,这就是俗称的内网 IP。这是因为你的路由器采用了 NAT 技术。

NAT(Network Address Translation,网络地址转换)是 1994 年提出的。当在专用网内部的一些主机本来已经分配到了内网 IP 地址,但现在又想和因特网上的主机通信时,NAT 技术将其内网 IP 地址转换成全球 IP 地址,然后与因特网连接,也就是说,内网的数台主机使用了同一个全球 IP 地址在上网。

NAT 技术实现了宽带共享,而且有助于缓解 IP 地址空间枯竭的问题。

『IPv6』:

IPv6 的地址长度是 128 位,通常将这 128 位的地址按每 16 位划分为一个段,将每个段转换成十六进制数字,并用冒号隔开,比如:2000:0000:0000:0000:0001:2345:6789:abcd 就是一个 IPv6 地址。

单从数量级上来说,IPv6 所拥有的地址容量是 IPv4 的约 8×10^28 倍,达到 2^128(算上全零的)个。这不但解决了网络地址资源数量的问题,同时也为除电脑外的设备连入互联网在数量限制上扫清了障碍。

四、网络层其它协议

网络层不仅有 IP 协议,还有其它如 ARP、ICMP、IGMP、ARAR 等其它协议。

『ARP(Address Resolution Protocol)地址解析协议』:

1、功能:当主机通过数据链路发送数据的时候, IP 数据报 会先被封装为一个 数据帧 ,而 MAC 地址 会被添加到数据帧的 报头 。ARP 便是在这个过程中通过目标主机的 IP 地址,查询目标主机的 MAC 地址。

2、原理:

在电脑和路由器中都有一个 ARP 缓存表 ,其中保存的是近期(20 分钟)与自己有过通信的主机的 IP 地址与 MAC 地址的对应关系。

ARP 缓存表使用过程:

  • 当主机要发送一个 IP 数据报的时候,会首先查询一下自己的 ARP 缓存表;
  • 如果在 ARP 缓存表中找到对应的 MAC 地址,则将 IP 数据报封装为数据帧,把 MAC 地址放在帧首部,发送数据帧;
  • 如果查询的 IP-MAC 值对不存在,那么主机就向网络中广播发送一个 ARP 请求数据帧,ARP 请求中包含待查询 IP 地址;
  • 网络内所有收到 ARP 请求的主机查询自己的 IP 地址,如果发现自己符合条件,就回复一个 ARP 应答数据帧,其中包含自己的 MAC 地址;
  • 收到 ARP 应答后,主机将其 IP - MAC 对应信息存入自己的 ARP 缓存,然后再据此封装 IP 数据报,再发送数据帧。

你可以通过命令 arp -a 查看 ARP 缓存表(表项记录 20 分钟超时),这里还有其它 ARP 命令可以对缓存表做查看、修改:

3、ARP 代理

如果 ARP 请求是从一个网络上的主机发往另一个网络上的主机,那么连接这两个网络的路由器就可以回答该 ARP 请求,这个过程称作 代理 ARP(Proxy ARP)。

当连接这两个网络的路由器收到该 ARP 请求时,它会发现自己有通向目的主机的路径,随后它会将自己(路由器)的 MAC 地址回复给源主机。源主机会认为路由器的 MAC 地址就是目的主机的 MAC 地址,而对于随后发来的数据帧,路由器会转发到它后面真实 MAC 地址的目的主机。

两个物理网络之间的路由器可以使这两个网络彼此透明化,在这种情况下,只要路由器设置成一个 ARP 代理,以响应一个网络到另一个网络主机的 ARP 请求,两个物理网络就可以使用相同的网络号。

4、ARP 欺骗

地址解析协议是建立在网络中各个主机互相信任的基础上的,它的诞生使得网络能够更加高效的运行,但其本身也存在缺陷:

ARP地址转换表是依赖于计算机中高速缓冲存储器动态更新的,而高速缓冲存储器的更新是受到更新周期的限制的,只保存最近使用的地址的映射关系表项,这使得攻击者有了可乘之机,可以在高速缓冲存储器更新表项之前修改地址转换表,实现攻击。ARP请求为广播形式发送的,网络上的主机可以自主发送ARP应答消息,并且当其他主机收到应答报文时不会检测该报文的真实性就将其记录在本地的MAC地址转换表,这样攻击者就可以向目标主机发送伪ARP应答报文,从而篡改本地的MAC地址表。[5] ARP欺骗可以导致目标计算机与网关通信失败,更会导致通信重定向,所有的数据都会通过攻击者的机器,因此存在极大的安全隐患。

防御措施:

  • 不要把网络安全信任关系建立在IP基础上或MAC基础上(RARP同样存在欺骗的问题),理想的关系应该建立在IP+MAC基础上。
  • 设置静态的MAC-->IP对应表,不要让主机刷新设定好的转换表。
  • 除非很有必要,否则停止使用ARP,将ARP做为永久条目保存在对应表中。
  • 使用ARP服务器。通过该服务器查找自己的ARP转换表来响应其他机器的ARP广播。确保这台ARP服务器不被黑。
  • 使用“proxy”代理IP的传输。
  • 使用硬件屏蔽主机。设置好路由,确保IP地址能到达合法的路径(静态配置路由ARP条目),注意,使用交换集线器和网桥无法阻止ARP欺骗。
  • 管理员定期用响应的IP包中获得一个RARP请求,然后检查ARP响应的真实性。
  • 管理员定期轮询,检查主机上的ARP缓存。
  • 使用防火墙连续监控网络。注意有使用SNMP的情况下,ARP的欺骗有可能导致陷阱包丢失。
  • 若感染ARP病毒,可以通过清空ARP缓存、指定ARP对应关系、添加路由信息、使用防病毒软件等方式解决。

『RARP(Reverse Address Resolution Protocol)逆向地址解析协议』:

RARP 与 ARP 是相反的关系,用于将 MAC 地址转换为 IP 地址。对应于 ARP,RARP 请求以广播方式传送,而 RARP 应答一般是单播传送的。

某些设备,比如无盘机在启动时可能不知道自己的 IP 地址,它们可以将自己的 MAC 地址使用 RARP 请求广播出去,RARP 服务器就会响应并回复无盘机的 IP 地址。

『ICMP(Internet Control Message Protocol)控制报文协议』:

通信过程中的发生各种问题时,ICMP 将问题反馈,通过这些信息,管理者可以对所发生的问题作出诊断,然后采取适当的措施去解决它。

ICMP 报文由 8 位错误类型、8 位条件代码和 16 位校验和组成,被封装在一个 IP 数据报中:

报文的类型字段可以有 15 个不同的值,以便描述特定类型的 ICMP 报文,代码字段的值进一步描述不同的条件,各类型的报文及其处理方法如图所示:

也有一些出现差错而不产生 ICMP 报文的情况:

  • 1.ICMP 差错报文
  • 2.目的地址是广播或多播地址
  • 3.作为链路层广播的数据报
  • 4.不是 IP 分片的第一片
  • 5.源地址不是单个主机的数据报(源不能为零地址、环回地址、广播多播地址)

『ping 程序』:

ping程序和traceroute程序是两个常见的 基于 ICMP 协议 的工具。

1、ping 简介

ping 程序是对两台主机之间连通性进行测试的基本工具,它只是利用 ICMP 回显请求和回显应答报文,而不用经过传输层(TCP/UDP)。

ping 程序通过在 ICMP 报文数据中存放发送请求的时间值来计算往返时间,当应答返回时,用当前时间减去存放在 ICMP 报文中的时间值,即是往返时间。

ping 程序使用方法为 ping IP 地址 ,ping 命令还可以加上参数,实现更多的功能:

  • -n 只输出数值。
  • -q 不显示任何传送封包的信息,只显示最后的结果。
  • -r 忽略普通的 Routing Table,直接将数据包送到远端主机上。通常是查看本机的网络接口是否有问题。
  • -R 记录路由过程。
  • -v 详细显示指令的执行过程。
  • -c 数目:在发送指定数目的包后停止。
  • -i 秒数:设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次。
  • -t 存活数值:设置存活数值 TTL 的大小。

2、TTL值

TTL是 Time To Live的缩写,该字段指定 IP 包被路由器丢弃之前允许通过的最大网段数量。

TTL 是 IPv4 包头的一个8 bit 字段,它的作用是限制IP数据包在计算机网络中的存在的时间,即IP数据包在计算机网络中可以转发的最大跳数。

假如没有TTL字段,网络中的 IP 包将越来越多造成网络阻塞,TTL 避免 IP 包在网络中的无限循环和收发,节省了网络资源,并能使IP包的发送者能收到告警消息。

3、Ping 命令判断操作系统

ping 命令会返回一个 TTL 值,我们可以使用它来判断目标的操作系统类型。

常见操作系统缺省 TTL 值如下:

  • UNIX TTL: 255
  • Linux TTL: 64
  • WINDOWS 95/98 TTL: 32
  • Windows NT 4.0/2000/XP/2003/7/8/10 TTL:128

『traceroute 程序』:

1、traceroute

traceroute 程序是用来侦测主机到目的主机之间所经路由情况的重要工具。刚才 ping 程序中讲过,带 -R 参数的 ping 命令也可以记录路由过程,但是,因为 IP 数据报头的长度限制(最多能保存 9 个 IP 地址),ping 不能完全的记录下所经过的路由器,traceroute 正好就填补了这个缺憾。

2、traceroute 工作原理

  • 它发送一份 TTL 为 1 的 IP 数据报给目的主机,经过第一个路由器时,TTL 值被减为 0,则第一个路由器丢弃该数据报,并返回一份超时 ICMP 报文,于此得到了路径中第一个路由器的地址;

  • 然后再发送一份 TTL 值为 2 的数据报,便可得到第二个路由器的地址;

  • 以此类推,一直到到达目的主机为止,这样便记录下了路径上所有的路由 IP。

『IGMP(Internet Group Management Protocol)组管理协议』:

IGMP 是用于管理多播组成员的一种协议,它的作用在于,让其他所有需要知道自己处于哪个多播组的主机和路由器知道自己的状态。只要某一个多播组还有一台主机,多播路由器就会把数据传输出去,这样,接受方就会通过网卡过滤功能来得到自己想要的数据。

为了知道多播组的信息,多播路由器需要定时的发送 IGMP 查询,各个多播组里面的主机要根据查询来回复自己的状态。路由器来决定有几个多播组,自己要对某一个多播组发送什么样的数据。

五、传输层:UDP 协议

『传输层协议』:

从之前介绍的网络层协议来看,通信的两端是两台主机,IP 数据报首部就标明了这两台主机的 IP 地址。但是从传输层来看,是发送方主机中的一个进程与接收方主机中的一个进程在交换数据,因此,严格地讲,通信双方不是主机,而是主机中的进程。

主机中常常有多个应用进程同时在与外部通信(比如浏览器和 QQ 在同时运行),下图中,A 主机的 AP1 进程在与 B 主机的 AP3 进程通信,同时主机 A 的 AP2 进程也在与 B 主机的 AP4 进程通信。

两个主机的传输层之间有一个灰色双向箭头,写着“传输层提供应用进程间的逻辑通信”。 逻辑通信:看起来是数据似乎是沿着双向箭头在传输层水平传输的,但实际上是沿图中的虚线经多个协议层次而传输。

TCP/IP 协议栈传输层有两个重要协议——UDP 和 TCP,不同的应用进程在传输层使用 TCP 或 UDP 之一:

『端口』:

刚才的图中,AP1 与 AP3 的通信与 AP2 与 AP4 的通信可以使用同一个传输层协议来传输(TCP 或 UDP),根据 IP 地址或 MAC 地址都只能是把数据传到正确的主机,但具体需要传到哪一个进程,是通过端口来辨认的。比如同时使用浏览器和 QQ,浏览器占用 80 端口,而 QQ 占用 4000 端口,那么发送过来的 QQ 消息便会通过 4000 端口显示在 QQ 客户端,而不会错误地显示在浏览器上。

端口号有 0~65535 的编号,其中:

  • 编号 0~1023 为 系统端口号 ,这些端口号可以在网址 www.iana.org 查询到,它们被指派给了 TCP/IP 最重要的一些应用程序,以下是一些常见的系统端口号:
应用层协议: FTP TELNET SMTP DNS TFTP HTTP SNMP
系统端口号: 21 23 25 53 69 80 161
  • 编号 1024~49151 为 登记端口号 ,为没有系统端口号的应用程序使用,使用这类端口号必须在 IANA 按规定手续登记,以防止重复。

  • 编号 49152~65535 为 短暂端口号 ,是留给客户进程选择暂时使用的,使用结束后,这类端口号会被放开以供其它程序使用。

『UDP 概述』:

UDP(User Datagram Protocol)用户数据报协议,它只在 IP 数据报服务之上增加了很少一点功能,它的主要特点有:

  • (1).UDP 是无连接的,发送数据之前不需要建立连接(而 TCP 需要),减少了开销和时延。
  • (2).UDP尽最大努力交付,不保证交付可靠性。
  • (3).UDP 是面向报文的,对于从网络层交付下来的 IP 数据报,只做很简单的封装(8 字节 UDP 报头),首部开销小。
  • (4).UDP 没有拥塞控制,出现网络拥塞时发送方也不会降低发送速率。这种特性对某些实时应用是很重要的,比如 IP 电话,视频会议等,它们允许拥塞时丢失一些数据,因为如果不抛弃这些数据,极可能造成时延的累积。
  • (5).UDP 支持一对一、一对多、多对一和多对多的交互通信。

从应用层到传输层,再到网络层的各层次封装:

『UDP 报文』:

UDP 数据报可分为两部分:UDP 报头和数据部分。其中数据部分是应用层交付下来的数据。UDP 报头总共 8 字节,而这 8 字节又分为 4 个字段:

  • (1)源端口 2 字节 在对方需要回信时可用,不需要时可以全 0;
  • (2)目的端口 2 字节 必须,也是最重要的字段;
  • (3)长度 2 字节 长度值包括报头和数据部分;
  • (4)校验和 2 字节 用于检验 UDP 数据报在传输过程中是否有出错,有错就丢弃。

『tcpdump 抓取 UDP 报文』:

以下程序会向 IP 地址 192.168.1.1 的 7777 端口 发送一条 "hello" 消息。

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>

int main(void)
{
        int sockfd;
        struct sockaddr_in server;
        char msg[20]={0};
        
        sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if (sockfd < 0) {
                perror("socket error!\n");
                exit(-1);
        }
        
        memset(&server,0,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr("192.168.1.1");
        server.sin_port = htons(7777);
        
        strncpy(msg,"hello",sizeof("hello"));
        
        printf("send message:%s\n",msg);
        
        if (sendto(sockfd,msg,20,0,(struct sockaddr *)&server,sizeof(server)) != 20) {
                perror("sendto error!\n");
                exit(-1);
        }        
 
        exit(0);
}

安装并运行 tcpdump,在另一终端编译运行以上程序:

返回刚才运行 tcpdump 的终端查看抓包结果:

六、传输层:TCP 协议

『概述』:

TCP 和 UDP 处在同一层——运输层,但是它们有很多的不同。TCP 是 TCP/IP 系列协议中最复杂的部分,它具有以下特点:

  • (1) TCP 提供 可靠的 数据传输服务,TCP 是 面向连接的 。应用程序在使用 TCP 通信之前,先要建立连接,这是一个类似“打电话”的过程,通信结束后还要“挂电话”。
  • (2) TCP 连接是 点对点 的,一条 TCP 连接只能连接两个端点。
  • (3) TCP 提供可靠传输,无差错、不丢失、不重复、按顺序。
  • (4) TCP 提供 全双工 通信,允许通信双方任何时候都能发送数据,因为 TCP 连接的两端都设有发送缓存和接收缓存。
  • (5) TCP 面向 字节流 。TCP 并不知道所传输的数据的含义,仅把数据看作一连串的字节序列,它也不保证接收方收到的数据块和发送方发出的数据块具有大小对应关系。

『TCP 报文段结构』:

TCP 是面向字节流的,而 TCP 传输数据的单元是 报文段 。一个 TCP 报文段可分为两部分:报头和数据部分。数据部分是上层应用交付的数据,而报头则是 TCP 功能的关键。

TCP 报文段的报头有前 20 字节的固定部分,后面 4n 字节是根据需要而添加的字段。如图则是 TCP 报文段结构:

20 字节的固定部分,各字段功能说明:

  • 1.源端口和目的端口:各占 2 个字节,分别写入源端口号和目的端口号。这和 UDP 报头有类似之处,因为都是运输层协议。

  • 2.序号:占 4 字节序,序号范围[0,2^32-1],序号增加到 2^32-1 后,下个序号又回到 0。

    TCP 是面向字节流的,通过 TCP 传送的字节流中的每个字节都按顺序编号,而报头中的序号字段值则指的是本报文段数据的第一个字节的序号。

  • 3.确认序号:占 4 字节,期望收到对方下个报文段的第一个数据字节的序号。

  • 4.数据偏移:占 4 位,指 TCP 报文段的报头长度,包括固定的 20 字节和选项字段。

  • 5.保留:占 6 位,保留为今后使用,目前为 0。

  • 6.控制位:共有 6 个控制位,说明本报文的性质,意义如下:

    • URG 紧急:当 URG=1 时,它告诉系统此报文中有紧急数据,应优先传送(比如紧急关闭),这要与紧急指针字段配合使用。
    • ACK 确认:仅当 ACK=1 时确认号字段才有效。建立 TCP 连接后,所有报文段都必须把 ACK 字段置为 1。
    • PSH 推送:若 TCP 连接的一端希望另一端立即响应,PSH 字段便可以“催促”对方,不再等到缓存区填满才发送。
    • RET 复位:若 TCP 连接出现严重差错,RST 置为 1,断开 TCP 连接,再重新建立连接。
    • SYN 同步:用于建立和释放连接,稍后会详细介绍。
    • FIN 终止:用于释放连接,当 FIN=1,表明发送方已经发送完毕,要求释放 TCP 连接。
  • 7.窗口:占 2 个字节。窗口值是指发送者自己的接收窗口大小,因为接收缓存的空间有限。

  • 8.检验和:2 个字节。和 UDP 报文一样,有一个检验和,用于检查报文是否在传输过程中出差错。

  • 9.紧急指针:2 字节。当 URG=1 时才有效,指出本报文段紧急数据的字节数。

  • 10.选项:长度可变,最长可达 40 字节。具体的选项字段,需要时再做介绍。

『连接的建立与释放』:

TCP 是面向连接的,在传输 TCP 报文段之前先要创建连接,发起连接的一方被称为客户端,而响应连接请求的一方被称为服务端,而这个创建连接的过程被称为 三次握手

  • (1) 客户端发出请求连接报文段,其中报头控制位 SYN=1,初始序号 seq=x。客户端进入 SYN-SENT(同步已发送)状态。
  • (2) 服务端收到请求报文段后,向客户端发送确认报文段。确认报文段的首部中 SYN=1,ACK=1,确认号是 ack=x+1,同时为自己选择一个初始序号 seq=y。服务端进入 SYN-RCVD(同步收到)状态。
  • (3) 客户端收到服务端的确认报文段后,还要给服务端发送一个确认报文段。这个报文段中 ACK=1,确认号 ack=y+1,而自己的序号 seq=x+1。这个报文段已经可以携带数据,如果不携带数据则不消耗序号,则下一个报文段序号仍为 seq=x+1。

至此 TCP 连接已经建立,客户端进入 ESTABLISHED(已建立连接)状态,当服务端收到确认后,也进入 ESTABLISHED 状态,它们之间便可以正式传输数据了。

当传输数据结束后,通信双方都可以释放连接,这个释放连接过程被称为 释放连接 :

  • (1) 此时 TCP 连接两端都还处于 ESTABLISHED 状态,客户端停止发送数据,并发出一个 FIN 报文段。首部 FIN=1,序号 seq=u(u 等于客户端传输数据最后一字节的序号加 1)。客户端进入 FIN-WAIT-1(终止等待 1)状态。
  • (2) 服务端回复确认报文段,确认号 ack=u+1,序号 seq=v(v 等于服务端传输数据最后一字节的序号加 1),服务端进入 CLOSE-WAIT(关闭等待)状态。现在 TCP 连接处于半开半闭状态,服务端如果继续发送数据,客户端依然接收。
  • (3) 客户端收到确认报文,进入 FIN-WAIT-2 状态,服务端发送完数据后,发出 FIN 报文段,FIN=1,确认号 ack=u+1,然后进入 LAST-ACK(最后确认)状态。
  • (4) 客户端回复确认确认报文段,ACK=1,确认号 ack=w+1(w 为半开半闭状态时,收到的最后一个字节数据的编号) ,序号 seq=u+1,然后进入 TIME-WAIT(时间等待)状态。

注意此时连接还没有释放,需要时间等待状态结束后(4 分钟) 连接两端才会 CLOSED。设置时间等待是因为,有可能最后一个确认报文丢失而需要重传。

『TCP 可靠传输的实现』:

  • (1) TCP 报文段的长度可变,根据收发双方的缓存状态、网络状态而调整。

(2) 当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。

(3) 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段,如果不能及时收到一个确认,将重发这个报文段。这就是稍后介绍的超时重传。

(4) TCP 将保持它首部和数据的检验和。如果通过检验和发现报文段有差错,这个报文段将被丢弃,等待超时重传。

(5) TCP 将数据按字节排序,报文段中有序号,以确保顺序的正确性。

(6) TCP 还能提供流量控制。TCP 连接的每一方都有收发缓存。TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。

可见超时重发机制是 TCP 可靠性的关键,只要没有得到确认报文段,就重新发送数据报,直到收到对方的确认为止。

『超时重传』:

TCP 规定,接收者收到数据报文段后,需回复一个确认报文段,以告知发送者数据已经收到。而发送者如果一段时间内(超时计时器)没有收到确认报文段,便重复发送:

为了实现超时间重传,需要注意:

  • 1.发送者发送一个报文段后,暂时保存该报文段的副本,为发生超时重传时使用,收到确认报文后删除该报文段。
  • 2.确认报文段也需要序号,才能明确是发出去的那个数据报得到了确认。
  • 3.超时计时器比传输往返时间略长,但具体值是不确定的,根据网络情况而变。

『连续 ARQ 协议』:

超时重传机制很费时间,每发送一个数据报都要等待确认。

在实际应用中的真实情况是,采用了流水线传输:发送方可以连续发送多个报文段(连续发送的数据长度叫做窗口),而不必每发完一段就停下来等待确认。

实际应用中,接收方也不必对收到的每个报文都做回复,而是采用累积确认方式:接收者收到多个连续的报文段后,只回复确认最后一个报文段,表示在这之前的数据都已收到。

这样,传输效率得到了很大的提升。

  • (1) 慢启动 :初始的窗口值很小,但是按指数规律渐渐增长,直到达到慢开始门限(ssthresh)。
  • (2) 加性增 :窗口值达到慢开始门限后,每发送一个报文段,窗口值增加一个单位量。
  • (3) 乘性减 :无论什么阶段,只要出现超时,则把窗口值减小一半。

参考资料

返回目录


Linux系统编程

学习笔记

一、gcc的使用

『编译一个C程序』:

以这个程序为例:

#include <stdio.h>

int summary(int n);

int main()
{
        int i,result;

        result = 0;
        for(i=1; i<=100; i++){
        result += i;
        }

        printf("Summary[1-100] = %d\n", result);
        printf("Summary[1-450] = %d\n",summary(450));

        return 0;
}

int summary(int n)
{
        int sum = 0;

        int i;
        for(i=1;i<=n;i++){
                sum += i;
        }

        return sum;
}

要编译一个C语言程序,只需要使用gcc命令后跟一个C源文件作为参数。

使用gcc summary.c编译上面的程序summary.c,编译后产生的可执行文件叫做a.out,位于当前目录下:

执行这个程序,得到:

a.out为默认文件名。gcc提供了-o选项让用户指定可执行文件的文件名。可以使用gcc -o sum summary.c命令,将summary.c

如果需要得到汇编代码等等,需要使用以下选项:

选项 功能
-c 只激活预处理、编译和汇编,生成扩展名为.o的目标代码文件
-S 只激活预处理和编译,生成扩展名为.s的汇编代码文件
-E 只激活预处理,并将结果输出至标准输出
-g 为调试程序(如gdb)生成相关信息

使用-E选项时,gcc会在标准输出显示其处理结果。所以应该使用重新定向将其输出到一个文件中,如:gcc -E summary.c > pre_sum

二、调试:gdb

『启动gdb』:

首先使用gcc -g命令生成带有调试信息的可执行程序,否则调试时看到的将是一堆汇编代码。

然后就可以使用gdb命令对生成的二进制文件进行调试了。

启动gdb的方法很简单,将二进制文件作为gdb的参数就可以了:

gdb首先会在屏幕上打印一些关于它的版本信息,随后显示提示符“(gdb)”等待接受用户指令。

『查看源代码』:

list命令(缩写为l)用于查看程序的源代码:

gdb会自动在源代码之前加上行号。第一次使用list命令时列出前10行,可以使用回车表示执行上一条命令。

可以给list命令指定行号,列出该行所在位置附近(10行)的代码:

search命令可以搜索特定的内容。只会显示符合条件的第一行,再次按回车键找到匹配的下一行代码。

可见,在20行之后就没有“int summary”这个字符串了。

需要注意的是,search只能朝向文件尾搜索,这一点与Vim的“/”命令一样,只是gdb的search命令不会在到达文件尾之后再从头开始搜索,而是提醒无法找到匹配模式。

使用reverse-search可以向文件头搜索。

search/reverse-search命令支持使用正则表达式进行搜索。

『设置断点』:

break命令(缩写为b)用于设置断点,此命令接受行号或者函数名作为参数。

以上设置了两个断点,这样,当程序运行到第10行及summary()函数的入口就会停下来,等待用户发出指令。使用info break命令可以查看已经设置的断点信息。

『单步调试』:

使用run命令(缩写为r)运行程序至断点。

使用next命令单步执行程序,此命令接受执行行数作为参数。

由于进入循环,执行两步后又回到了第11行。可以使用continue命令(缩写为c),直至遇到下一个断点。

『监视变量』:

可以使用print命令(缩写为p)要求gdb提供指定变量的值。

每次都使用print未免麻烦,gdb提供了watch命令,用于设置观察点。watch接受变量名(或者表达式)作为参数,一旦参数的值发生变化,就停下程序。

『临时修改变量』:

gdb允许用户在运行程序时改变变量的值,通过set var命令实现这一点。

『查看堆栈情况』:

每次程序调用一个函数,函数的地址、参数、函数内的局部变量都会被压入“栈”(Stack)中。使用bt命令可以看到当前运行时栈的情况。

『退出gdb』:

调试完毕,使用quit(缩写为q)命令退出gdb程序。如果程序还没有运行完,gdb会要求用户确认退出命令。

参考资料

返回目录


高级Bash脚本编程指南

学习笔记

一、第一个程序:Hello World!

以这个基本的Shell程序为例,该程序用于在屏幕上打印一行字符串“Hello World!”。

其中,#! /bin/bash用于指明运行这个脚本时应该使用哪个Shell程序;

以“#”开头的是注释,Shell会自动忽略“#”后面的所有内容;

Shell脚本会自动忽略空行,用空行分割一个程序中不同的任务代码是一个良好的变成习惯;

echo命令把其参数传递给标准输出,在这里就是显示器。如果参数是一个字符串的话,那么应该用双引号把它包含起来。echo命令最后会自动加上一个换行符。

要执行这个Shell脚本,首先应该为它加上可执行权限,完成后就可以运行了。

二、变量和运算符

『变量的赋值和使用』:

以下程序将一个字符串赋给变量,并输出:

运行结果:

在Shell中使用变量不需要事先声明。使用等号“=”将一个变量右边的值赋给这个变量时,直接使用变量名就可以了(注意在这里赋值变量时,“=”左右两边没有空格)。

当需要存取变量时,就要使用一个字符来进行变量替换。在BASH中,“$”用于对一个变量进行解析。Shell在碰到带有“$”的变量时会自动将其替换成这个变量的值。

变量只在其所在的脚本中有效,退出后将无法查询变量的值。

  • 使用source命令可以强行让一个脚本影响其父Shell环境。

  • 与之相反的命令是export。export让脚本可以影响其子Shell环境。

  • 使用unset命令可以手动注销一个变量。

『变量替换』:

如果需要输出“$”时,需要使用转义字符“\”。

Shell提供了花括号“{}”来限定一个变量的开始和结束。在紧跟变量输出字母后缀时,就必须使用这个功能。

『位置变量』:

Shell脚本使用位置变量来保存参数。传递给脚本文件的参数分别存放在“$”符号带有数字的变量中。也就是说,第一个参数存放在$1,第二个参数存放在$2...以此类推。当存取的参数超过十个的时候,就要用花括号把这个数字括起来,如${13},${20}等。

下面是这个程序的运行结果。因为没有第三个参数,因此$3的值是空的。

『BASH引号规则』:

在Shell脚本中可以使用的引号有如下三种:

  • 双引号:阻止Shell对大多数特殊字符(例如#)进行解释。但“$”、“`”和“””仍保持其特殊含义。
  • 单引号:阻止Shell对所有字符进行解释。
  • 倒引号:“`”位于Esc键下方。当用倒引号括起一个Shell命令时,这个命令将会被执行,执行后的输出结果将作为这个表达式的值。倒引号中的特殊字符一般都会被解释。

如以下脚本可以显示这3个引号的不同之处:

以下是运行结果。可以看到双引号也会对“`”作出解释。

三、表达式求值

观察以下例子:

可以看到,运行结果并不是3。Shell脚本语言是一种“弱类型”的语言,它并不知道变量num中保存的是一个数值,因此在遇到num=$num+2这个命令时,Shell只是简单地把$num和“+2”连在一起作为新的值赋给变量num。为了让Shell得到3,可以使用$ num=$[ $num + 1 ]

可以采用[base#]n来表示从二到三十六进制的任何一个n值,例如2#10就表示二进制数10(对应于十进制的2)。

注意:在“1”、“+”和“2”之间要有空格。

另一种指导Shell进行表达式求值的方法是使用let命令。更准确地说,let命令用于计算整数表达式的值。如:

四、脚本执行命令和控制语句

『if选择结构』:

if命令判断条件是否成立,进而决定是否执行相关的语句。如果不成立,直接跳过这段if结构(以fi作为结束标志),继续执行后面的脚本。

运行结果如下:

注意:这里用于条件测试的语句[ $password = "mypasswd" ],在[、$password、=和"mypasswd"之间必须存在空格。

下面这个例子展示了如何做出“如果...如果...否则...”这样的判断:

运行结果如下:

『case多选结构』:

case用于在一系列模式中匹配某个变量的值,这种结构的基本语法如下:

case word in
	pattern-1)
		commands-1
		;;
	pattern-2)
		commands-2
		;;
	...
	pattern-n)
		commands-n
		;;
	*)
		commands-n+1
		;;
esac

变量word逐一从pattern-1到pattern-n的模式进行比较,当找到一个匹配的模式后,就执行紧跟在后面的命令commands(可以是多条命令);如果没有找到匹配模式,case语句就什么都不做。

命令“;;”只在case结构中出现,Shell一旦遇到这条命令就跳转到case结构的最后。与此类似的是,C语言提供了break语句在switch结构中实现相同的功能。

星号(*)用于匹配所有的字符串,所以一般放在最后。

五、条件测试

『if判断的依据』:

需要注意的是,if语句本身并不执行判断。它实际上接受一个程序名作为参数,然后执行这个程序,并根据这个程序的返回值来判断是否执行相应程序。如果程序的返回值是0,就表示“真”,if语句进入对应的语句块;所有非0的返回值都表示“假”,if语句直接跳过对应的语句块

『test命令和空格的使用』:

既然if语句需要接受一个命令作为参数,那么像"$password" = "john"这样的表达式就不能直接放在if语句的后面。因此需要额外引入一个命令,用于判断表达式的真假。test命令的语法如下:test expr,如:test "$password" = "john"。如果两者相等,那么test命令返回0;否则返回1。

同样地,也可以使用方括号“[”进行条件测试,语法为:[ expr ]

注意:在Shell编程中,空格绝不仅仅是编程风格这么简单!对比以下三条命令:

password="john"
test "$password" = "john"
[ "$password" = "john" ]
  • 第一条是赋值语句,在password、=和"john"之间没有空格;
  • 第二条是条件测试命令,在"$password"、=和"john"之间均有空格;
  • 第三条是条件测试命令(是test命令的另一种写法),在[、"$password"、=、"john"和]之间均有空格。

如果在赋值语句的“=”两边加上空格,会导致语法错误。

这是因为,Shell首先是一个命令解释器,而不是一门编程语言。空格在Shell这个“命令解释器”中用于分隔命令和传递给它的参数(或者用于分隔命令的两个参数)。使用whereis命令查找test和“[”可以看到,这两个程序文件存放在/user/bin目录下。

因此在上面的例子中,"$password"、=和"john"都是test命令和[命令的参数,参数和命令、参数和参数之间必须用空格分隔。而赋值语句password="john"没有空格,因为password是变量名,不是可执行程序。

test和[命令可以对以下三类表达式进行测试:

  • 字符串比较
  • 文件测试
  • 数字比较

1、 字符串比较:test和[命令的字符串比较主要用于测试字符串是否为空,或者两个字符串是否相等。相关选项如下表:

选项 描述
-z str 当字符串str长度为0时返回真
-n str 当字符串str长度大于0时返回真
str1 = str2 当字符串str1和str2相等时返回真
str1 != str2 当字符串str1和str2不相等时返回真

2、文件测试:用于判断一个文件是否满足特定的条件。相关选项如下表:

选项 描述
-e 检查文件是否存在
-d 检查文件是否存在以及该文件是否是目录文件
-f 检查文件是否存在以及该文件是否是普通文件(不是目录)
-r 检查文件是否存在以及该文件是否可读
-w 检查文件是否存在以及该文件是否可写
-x 检查文件是否存在以及该文件是否可执行
-s 检查文件是否存在以及该文件是否大于0字节

3、数字比较:test和[命令在数字比较方面只能用来比较整数(包括负整数和正整数),基本语法为:test int1 option int2[ int1 option int2 ]。其中的option表示比较选项,常用的选项如下表:

选项 描述 英文全称
-eq 等于 equal
-gt 大于 greater than
-lt 小于 less than
-ne 不等于 not equal
-ge 大于等于 greater or equal
-le 小于等于 less or equal

4、复合表达式:组合使用几个条件表达式。test和[命令本身内建了操作符来完成条件表达式的组合,如下表:

操作符 描述
!expr “非”运算,当expr为假时返回真
expr1 -a expr2 “与”运算,当expr1和expr2同时为真时才返回真
expr1 -o expr2 “或”运算,当expr1或expr2为真时返回真

六、循环结构

Shell中的循环结构有:while、until和for

『while语句』:

while语句重复执行命令,直到测试条件为假。比如以下这个程序,计算1-100的和:

运行结果为:

『until语句』:

until语句是while语句的另一种写法,只是测试条件相反而已。

上面的脚本用until语句完成,应该为:

#!/bin/bash

sum=0
number=1

#while test $number -le 100
until ! test $number -le 100
do
	sum=$[ $sum + $number ]
	let number=$number+1
done

echo "The summary is $sum"

『for语句』:

如果希望从列表中逐一取一系列的值,此时使用while和until就显得不太方便。Shell提供了for语句,这个语句在一个值表上迭代执行。for的基本语法如下:

for variable [in list]
do
	commands
done

这里的“值表”是一系列以空格分割的值。Shell每次从这个列表中取出一个值,运行do/done之间的命令,直到取完列表中的所有的值。

运行结果如下:

如果列表中的数字特别多,则需要使用其他方法。Shell本身带了一个叫做seq的工具,该命令接受一个数字范围,并把它转换为一个列表。如果要生成1~9的数字列表,可以:

七、读取用户输入

read命令可以从标准输入接收一行信息。read命令接受一个变量名作为参数,把从标准输入接收到的信息存放在这个变量中。如果没有提供变量名,那么读取的信息将存放在变量REPLY中。比如下面这个例子:

可以给read命令提供多个变量名作为参数。在这种情况下,read命令会将接收到的行“拆开”分别赋予这些变量。默认情况下,Bash根据空格、制表符和换行符进行拆分。比如下面这个脚本将用户输入拆分为两个单词分别放在变量first和second中。

输出结果如下:

如果有多个空格,则按照第一个空格分隔:

参考资料

——————TO BE CONTINUED...——————


  1. A-Z ↩︎

posted @ 2017-08-01 22:25  0x14b7狄  阅读(633)  评论(4编辑  收藏  举报