跳转至

网络应用相关的shell命令

上一章我们讲了计算机网络必要的前置知识,并且介绍了查看计算机上网络设置和5层结构中下面4层的相关信息的命令ip。但是对于我们来说,我们接触到计算机网络最多的,还是最上面的应用层,所以这一章我们将关注应用层,介绍一些在应用层常用的命令。

使用命令行向服务器发送请求——curl

我们要介绍的下一个命令是一个应用层的软件——curl。如果你第一次使用这个软件,你可能需要安装它。如果你使用的是Ubuntu/Debian可以使用下面这个命令。

sudo apt update
sudo apt install curl
如果你使用的其他的linux发行版或者MacOS,那么请你自己去搜索一下如何安装curl

使用curl发送http请求

curl最常见的用法就是用来发送http请求和接受http响应。

http协议简介

这里我们先简单介绍一下什么是http协议。http协议是一种用于在互联网上交换信息的协议。http协议基于tcp协议并且它默认使用80号端口。它主要分为请求(request)和响应(response)两个部分。

http请求通常都是客户端(client),比如我们的浏览器等,向服务器发送的。http请求通常包含http请求方法,http请求路径,http版本,http请求头和http请求体。其中我们要重点关注的是http请求类型。http请求方法有很多种类型,每种类型的请求方法通常都有一种应用场景。例如,GET请求用于向服务器请求数据,POST请求用于向服务器提交数据。之后的的http请求路径就是我们要请求的资源在服务器当中的位置,例如我们请求的图片的路径等等。不过有的时候,这个路径并不是一个实际的文件路径。当我们需要向服务器发送数据的时候,数据放在http请求体中,比如发送POST方法的请求的时候,http请求体中就要包含我们要发送的数据。http请求头中则是包含一些客户端的数据等信息,这个不是我们要关注的重点。

http响应则是服务器在收到客户端的请求之后返回的响应。http响应通常包含http版本,http状态码,http状态信息,http响应头和http响应体。其中我们关注的重点是http状态码。这个状态码是一个数字,表示http相应的状态。常见的一些响应码例如:

  • 200:表示请求成功。

  • 403: 表示客户端没有权限。

  • 404: 表示请求的资源不存在

如果你想要了解更多状态码的信息,可以查看RFC2616第10节

http状态信息是用一个简短的英文对这个http响应状态进行描述,例如当状态码为200时,这个描述通常是OK。http响应体则是服务器返回的客户端请求的数据。http响应头中也是包含一些有关信息的,这个不是我们要关注的重点。

最后再额外提一句,https协议是基于http协议的一个更加安全的协议,现在https更受推荐,但是它的原理比较复杂,这里就不做介绍了。curl也可以发送https协议的请求和接收https协议的响应,和http的操作方式没什么区别。

使用curl发送一个GET请求

我们简单了解了什么是http协议之后,我们终于可以开始尝试使用curl发送一个http请求了。使用下面这个命令就可以向百度发送一个http请求。

curl http://www.baidu.com/
首先,curl命令接受一个位置参数表示要请求的URL,这里http://表示使用http协议,www.baidu.com是网站的域名,/是在服务器上的路径,这里表示请求百度网站的根目录。如果没有其他的参数,curl默认向这个地址发送一个http的GET请求。因为GET请求只是请求数据,不发送任何数据,所以它的http请求中没有请求体,所以也不需要在参数上添加其他任何东西。如果命令执行正常,我们应该会收到这样的输出,
> GET http://www.baidu.com/ HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.81.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Type: text/html
< Date: Sun, 10 Nov 2024 06:44:16 GMT
< Keep-Alive: timeout=4
< Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
< Pragma: no-cache
< Proxy-Connection: keep-alive
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
其中这一段就是我们发送的http请求
> GET http://www.baidu.com/ HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.81.0
> Accept: */*
> Proxy-Connection: Keep-Alive
最前面是请求的方法GET。然后是我们的请求路径,也就是百度网站(www.baidu.com)的根目录(注意最后面的那个/)。一般网站的根目录都是它的主页。然后是我们的http版本是1.1。后面的部分都是请求头。

下面这一段和它后面的内容是http响应。

< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Type: text/html
< Date: Sun, 10 Nov 2024 06:44:16 GMT
< Keep-Alive: timeout=4
< Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
< Pragma: no-cache
< Proxy-Connection: keep-alive
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
最前面是http版本。然后是http状态码和http状态描述。这里表示我们的请求成功。下面部分直到我这里截取的部分为止是http的请求头。再后面的部分直到结尾是http的请求体,我们会发现请求体的内容是百度首页的html

值得一提,http的请求头/响应头和http请求体/响应体之间是用一个空行来间隔的,并且http协议中规定换行用CRFL也就是回车符+换行符表示换行。

curl发送http请求的其他功能

当然,curl关于发送http请求还有很多其他的功能,例如可以使用它来发送POST请求和其他种类的请求,发送的请求头可以附加其他信息,可以将http响应体的内容保存到文件中等等。受限于篇幅,这些功能就请大家自己去探索了。

curl的其他功能

curl的功能也不只是发送http请求,它支持的协议包括SMTP(用于发送电子邮件), POP3(由于接收电子邮件), FTP(用于传输文件)等。你可以尝试使用下面这命令来接收你的电子邮箱中的一封邮件。

curl -X "RETR 1" --user USR:PWD pop3://MAIL
你需要将命令中的USR替换成你的邮箱地址,PWD替换成你的邮箱登录密码,MAIL替换成你的邮箱服务器地址。这里的邮箱服务器地址通常是你登录邮箱时的网址,比如你使用163邮箱,你的邮箱服务器地址就是mail.163.com

warning

小心!你的账号和密码可能是被明文传输的,存在密码被泄露的风险。

从网络上下载文件——wget

wget是一个非交互式的网络下载器,你可以使用http/https/ftp协议下载文件。如果你是第一次使用这个软件,那么你需要安装一下,如果你使用的是Ubuntu/Debian,可以使用下面这个命令。

sudo apt update
sudo apt install wget

基础用法

wget接受一个位置参数表示要下载文件的url,例如下面这个命令:

wget https://baidu.com/
这个命令会将百度的首页的html文件下载下来并且保存在当前文件夹下的index.html文件中。这个命令会自动选取下载的文件名。

其他用法

你可以使用选项参数--output-document后面接文件路径来指定下载的文件的名称,如果这文件已经存在,下载的内容会接在文件的后面。这里可以使用-表示标准输出流,也就是将文件的内容打印的屏幕上。

你可以使用参数--limit-rate=xxx限制下载速度,其中xxx表示下载的速度,单位是字节每秒,你可以可以使用k, M, G等后缀。

你可以使用参数--tries=x表示出错的话重试的次数。

你可以使用--user=xxx--password=xxx,指定登录的用户名和密码,如果你的下载的文件需要登录的话。

wget还有很多其他功能,这里就不更多介绍了。

向服务器发送命令——telnet

telnet不只是一个软件的名称,也是一个应用层协议的名称。telnet协议的作用就是让用户可以远程操控主机。telnet协议基于tcp协议并且默认使用23号端口。

telnet命令的作用就是使用telnet协议连接一个服务器。它的用法很简单,它接受两个位置参数,第一个表示要连接的主机的地址(IP地址或者域名),第二表示使用的端口号。如果没有指定端口号则使用23号端口。建立连接之后,telnet会在名目上输出一个prompt,在其中你可以输入命令,这命令将会被传给服务器被执行,然后服务器执行的结果将会返回并打印在屏幕上,就像我们在本地使用shell那样。不过因为安全问题的原因,现在几乎没有人使用telnet协议了。你仍然可以使用telnet命令来发送电子邮件,不过我不打算在这里详细介绍具体的方法,如果你好奇的话,你去网上搜索一下就能很容易得到答案。

更安全地向服务器发送命令——ssh

因为telnet协议存在安全风险,所以人们提出了更加安全的ssh(Secure Shell Protocol)协议。同样的,ssh不只是一个软件的名称,它也是一个应用层协议的名称。它的作用从名字来看也很容易能想到,可以允许用户更安全地操作服务器的shellssh协议基于tcp协议并且默认使用22号端口。(不过ssh是否绝对安全目前存疑)

ssh的身份验证

使用ssh的时候,我们通常会遇到的第一个问题就是身份验证。ssh通过身份验证来保证只有被授权的用户可以操作服务器,这是安全性的一部分。

ssh使用非对称加密的方式来进行身份验证。非对称加密的原理简单来说是这样的。加密使用公钥(\(c\))和私钥(\(d\))两个部分。可以把它们都看做是一个函数。它们满足这样的两个性质:

  1. \(d(c(x)) = x\), 也就是\(d = c^{-1}\)

  2. 在已知\(c\)的情况下,\(c(x)\)很好求但是\(c^{-1}(x)\)很难求

满足这样性质的一个简单的例子,\(c\)是求大质数的乘积,\(d\)是从合数中除去这个质数(已知)。这都是比较快的操作。但是在不知道\(d\)的情况下,我们想要求\(c^{-1}\)需要做质因数分解,并且这质因数很大。这对于计算机来说非常的慢。

现在利用公钥和私钥就可以加密传输数据了。

  1. 客户端手中有私钥和公钥,并且它将公钥传递到服务器上。(这个过程中公钥被窃取了也没关系,因为性质2)

  2. 服务器要传输数据\(x\)的时候,首先求出\(c(x)\),并把\(c(x)\)传给客户端。(这过程中\(c(x)\)被窃取了也没关系,因为性质2)

  3. 客户端收到\(c(x)\)之后求\(d(c(x))\)。由于性质1,\(d(c(x)) = x\),于是客户端就正确收到了消息。

如果你觉得上面的介绍没能完全理解,我们可以再形象地解释一下。公钥就像是一个开着的锁,私钥就是打开锁的钥匙。开始的时候客户端将一把开着的锁寄给服务器。然后服务器把数据放进保险柜用这把锁锁起来(一般来说锁的设计是扣一下就能关锁不需要钥匙)。然后服务器把这个保险柜寄回给客户端,客户端用自己的钥匙就能打开锁然后拿出其中的数据。这个过程中如果中间有坏人复制了锁也没有用,它不能用锁打开保险柜;它复制了整个上锁的保险柜也没有用,因为它也无法打开保险柜。

用这样的方式传递信息是安全的,一个重要的前提是私钥从没有暴露过。也就是使用非对称加密的时候请不要以任何方式传递你的私钥,除非你的服务器被别人使用了也没关系或者你确信你用来传递私钥的途径是绝对安全的。

如何生成和使用密钥

首先,ssh提供了一个工具可以用于生成密钥——ssh-keygen。这个工具的基础用法很简单,直接输入ssh-keygen就可以交互式地生成一个密钥。首先你需要根据提示输入你的密钥的保存路径和保存私钥的文件名,如果不输入就会自动生成在默认地址。如果你的默认地址已经有密钥了,说明你曾经生成过密钥,你也可以直接使用这个密钥。这种情况下如果再次把密钥生成在默认地址会将之前的那个密钥覆盖。这可能导致你之前能连接的服务器不再能连接了,所以这一步选择默认地址的话要小心一点。通常我们把用于ssh的密钥放在~/.ssh/文件夹下(~表示用户的主目录,还记得吗?)。然后它会提示你输入一个密码,这个密码用来在使用密钥的时候辅助验证身份,也就是只有正确输入密码才能使用你生成的这密钥。对我们自己的普通使用场景,可以省略这个密码。

完成之后,我们可以在我们一开始输入的那个路径下面找到两个文件,一个有.pub后缀,这个就是公钥,另一个同名的没有后缀的文件则是私钥。我们需要把这里生成的公钥(也就是.pub结尾的文件的内容)复制到我们的主机上。通常我们在使用云服务器或者使用github的ssh连接的时候平台上都会有让我们创建密钥的地方,在那里根据提示复制公钥的内容就可以了。通常一个服务器可以使用多个密钥,所以如果不止你一个人要使用这台服务器的话,每个人都应该创建一个自己的密钥。

danger

再次提示,不要将私钥以任何形式发到网上。

另一个很有用的命令行选项是-R。当我们使用ssh连接过一个主机之后,这个主机的信息就会被记录在known_hosts文件中,这样下次我们连接这个主机的时候就会自动使用上一次使用的密钥。但是当主机的密钥发生变化的时候就会连接失败了。这个时候就可以使用下面这个命令清除相关的记录

ssh-keygen -R xxx
其中xxx的位置是这个主机的地址(域名或者ip地址)。

如何使用ssh连接主机

我们现在了解了关于ssh如何进行身份验证,我们终于可以使用ssh来连接服务器了。可以使用下面这个命令

ssh USR@HOST
其中USR的位置是你的连接使用的用户的名称,HOST的位置是你要连接的服务器的地址(域名或者ip地址)。如果你目前没有可以用来尝试的远程主机的话,你可以搜索一个云服务器供应商(比如华为云,阿里云等),他们通常会给新用户提供比较优惠的(甚至免费的)试用云服务器的机会。(如果你是北大的学生的话,可以使用Linux俱乐部提供的clab)

如果你使用的不是默认路径下的密钥的话,可以使用-i选项来指定密钥的路径,就像这样

ssh -i /path/to/key USR@HOST
这里/path/to/key的位置就是你的私钥的路径。

如果你连接的服务器使用的不是默认端口号(22)的话,可以使用-p选项来指定端口号。

ssh的配置文件

上面我们介绍了如何使用命令和命令行参数来进行ssh连接。如果你要连接的主机没有域名或者域名比较复杂的话,每次都要输入ip地址或者复杂的域名;如果你的密钥不在默认路径下的,你每次都要使用-i选项如果你使用的端口不是默认端口的话,你每次都要使用-p选项,这些都会让你每次使用ssh的时候很不方便。这时候就可以使用配置文件了。你可以在ssh的配置文件中添加你的主机,设置每次连接的时候使用的选项并且为这个主机取一个更有意义的名称。

ssh的配置文件在~/.ssh/config路径下,这个文件下每个主机之间用空行隔开,每个主机的配置类似下面这样(注意缩进和空格)

Host testHost
    HostName HOST
    User USR
    Port 22
    IdentityFile /path/to/key
    PasswordAuthentication no
这里,第一行Host后面是你给这个主机取的名字,第二行Hostname后面HOST的位置是这个主机的地址,第三行User后面USR的位置是连接的时候使用的用户名,第四行Port后面是连接使用的端口号,第五行IdentityFile后面/path/to/key的位置是这主机连接的时候使用的私钥的地址,第六行表示是否使用密码登录(ssh也支持使用用户名和密码登录,但是这样不安全所以现在几乎不用了)。这些项的顺序不重要并且保持默认的项可以省略。

与服务器之间传递文件——scp

和上面介绍过的命令一样,scp同时也是一个协议的名称。这个协议是一种基于ssh协议的文件传输协议。因此scp也是一个可以与服务器传递文件的软件。因为这个命令是基于ssh协议的,所以这个命令也使用和ssh完全相同的方式进行身份验证。

基础用法

使用这个命令的方式也很简单,这个命令接收两个位置参数。按顺序前一个位置参数表示要传输的文件的路径,后一个位置参数表示文件要传输到的目标地址,如果指定了新的文件名称,那么文件在传输之后会用新的文件名称。这个命令既可以将文件从本地上传到服务器,也可以将文件从服务器下载到本地。

很明显,这个指令的源地址和目标地址中有一个是本地文件路径,有一个是服务器上的文件路径。那么要如何表示服务器上的文件路径呢?采用下面这样的形式

USR@HOST:/path/on/server
其中USR的位置是你在服务器上的用户名,HOST的位置上是你的服务器的地址,然后:后面就是在服务器的文件系统上的文件路径。

其他用法

和之前我们介绍过的很多命令一样,如果你想要传输的是文件夹,那么可以使用-r选项。

ssh一样,如果你想要指定使用的私钥地址,可以使用-i选项。

ssh有一点不同的是,如果你想要指定端口号要使用-P选项。因为小写的-p被用来表示在文件传输的时候保留文件的修改时间,访问时间和模式位信息。

在两个服务器之间传递文件

这个命令的一个比较有趣的用法是,你可以以你自己的电脑作为中转在两个服务器之间传递文件。如果你有多个服务器,你想在多个服务器之间传递文件,但是你又不想在服务器上放置密钥的话(比较麻烦而且可能不安全),你可以选择用自己的电脑作为中转。这个功能使用-3选项。后面的位置参数和前面介绍的一样,有两个位置参数,前一个是源地址,后一个是目标地址,只是这两个地址都是服务器上的地址。

与服务器建立tcp连接——netcat

前面我们介绍的命令都是在tcp协议的基础之上,使用其他的应用层协议实现了某种功能的应用。但是netcat有一些不同,这个软件不基于任何应用层协议。我们既可以把它看作是应用层的软件,也在一定程度上可以把它看做是网络层的软件。这个软件的功能很简单,就是直接接受和发送tcp和udp数据。

建立tcp连接并发送数据

使用netcat最基础的用法就是与某个服务器的某个进程建立tcp连接并发送数据,这里netcat需要两个位置参数,前一个是服务器地址,后一个是端口号,例如:

netcat smtp.163.com 25
netcat也可以简写成nc,就像这样
nc smtp.163.com 25
这里我们连接了163邮箱的服务器,并连接了smtp协议(用于发送邮件)的端口。如果连接成功了,netcat应该会返回一条从服务器发回的消息,然后会继续等待你的输入。你可以尝试输入下面的内容和邮箱服务器打招呼。
HELO relay.162.com
按回车之后,netcat就会通过tcp协议发送这条数据,然后你应该能看到来自服务器的回应。最前面的数字是smtp协议规定的回应状态码。你还可以根据smtp协议的规定继续和服务器通信并发送邮件,具体细节这里就不介绍了。实际上netcat的这个功能和之前介绍过的telnet很像。

侦听tcp连接

netcat除了可以主动和服务器建立tcp连接以外,还可以侦听来自其他计算机其他进程的连接建立请求。netcat可以使用-l选项指定侦听的端口号,像下面这样

netcat -l 8080
这个命令让netcat侦听本机的8080端口,如果有其他人尝试和我们的电脑的8080端口建立tcp连接,那么netcat就会接受,并且将其通过tcp传输过来的数据打印到屏幕上。

利用上面两个命令我们可以尝试自己电脑上的两个进程互相连接。首先我们打开两个终端,在其中一个终端使用netcat命令侦听12345端口,然后我们打开另一个终端然后使用下面这个命令和刚才的netcat进程建立tcp连接,例如

nc 127.0.0.1 12345
这里使用的127.0.0.1是一个特殊的ip地址,表示自己这台计算机,这里就是和自己计算机上的12345端口建立连接。

然后我们可以在两个终端分别尝试输入各种文字,然后回车,我们会看到另外一个终端上显示相同的文字,这说明我们的数据通过tcp连接被传输给了另一个进程。并且如果我们使用Ctrl-C终止侦听的一端,另一端不会终止,但是如果如果我们主动终止建立连接的一端,侦听一端也会终止。

使用udp协议

在上面介绍过的用法中添加-u选项,我们就可以让netcat使用udp协议连接。

小结

这一章我们介绍了shell中常用的几个网络应用命令的用法,这其中的一些命令我们可能经常会使用。比如我们写了一个http服务器,当我们想要测试服务器的时候可能需要使用curl命令。当我们想要从网站下载一些文件的时候,我们可以使用wget命令。现在云服务器正在逐渐成为趋势,在远程操作云服务器的时候,ssh命令和scp命令通常是必不可少的。对于其他使用了tcp协议的服务器,想要测试的时候,则可以使用telnetnetcat