计算机网络入门
https://studygolang.com/articles/11586#google_vignette
https://www.oneyearago.me/2019/06/14/learn_gwf/
https://www.topgoer.com/网络编程/socket编程/TCP编程.html
草率的入门,或许需要从不同的地方获取常识。
前置
一来就看 7 层模型给我整蒙了。
首先网络是有线的。
然后协议的意思其实就是大家都遵守的规定,比如我们规定大括号不换行,比如我们规定我们连接的时候必须我发出申请,你回应,我再回应才可以建立连接。那么大家都按照这个规定这个社会就和谐了。
网络模型
最底层我们叫物理层,就是 bit 和电信号的关系,直接通过物理传输。
那么全球 \(n\) 台机器我们需要 \(n^2\) 条线,这显然不优
往上一层叫链路层,也叫 mac 层。
这里引入集线器这个设备,每个设备一条线连向它,要传输时发送到集线器,集线器发送到每个设备。
然后我们引入 mac 地址这个概念来区分每台设备,这样只要在数据前后包上一层头,其中包含目标的 mac 地址,然后就只有目标设备收到时才接受。
简单的交换机可以替换掉集线器,交换机上有个表,表里存交换机的端口对应的机器的 mac 地址,我们通过广播(向所有设备询问)的方式逐渐完善这张表,使得可以一对一发消息。
这一层我们就实现 \(O(n)\) 了,不妨联想到线段树优化建图,但全球的设备都连在一个集线器显然不优
再往上一层叫网络层,也叫 ip 层。
显然只有地域内部分设备可以在上一层连上,我们称这些设备形成一个网络,然后全球就会有很多网络,这些网络内部可以通过链路层的协议相连,但是网络之间不能。
所以我们引入路由器这个设备,路由器通过连接到多个网络来连接网络,同时,为了标记每个设备在哪个网络下,我们引入 ip 地址这一概念。
每个路由器在每个他连接到的网络都分别有一个 ip 地址。同一个路由器在不同网络下的 ip 地址是不同的。
两个设备在不同网络间的通信需要用到路由算法,而路由算法比较复杂,现在也在更新中,所以不再介绍。
再往上一层叫传输层。
此时我们的全球已经连通了,但是我们只把电脑与电脑连通了,实际上,我们知道我们每个应用需要用到的信息是不同的,比如你的电子邮箱和浏览器需要的数据不能混在一起发给你。
因此,在不再需要新增物理设备的情况下,我们开始注意协议,也就是规定。我们引入端口这一概念,每个机器有若干端口。对于服务器,有的端口用于固定的协议或应用,例如 80 用于 http, 430 用于 https。对于客户机,从 49152 到 65535 的端口在应用程序启动时随机分配。这样我们便能指明应用到应用的路径了。
那么传输层的主要内容就是传输的方式,也就是 TCP 协议和 UDP 协议。这两个协议的实现方式是在内核中运行软件,目的是保证传输的质量。
TCP:三次握手,确认机制,重传机制,序列号,滑动窗口机制,拥塞控制算法,四次挥手
UDP:无连接(不需要握手),不可靠连接(顺序,重传,确认)
这些就是协议的主要内容,我们在传输时会在数据前添加传输层的头,其中包含实现这些协议所需的信息。
我本来通过和 copilot 的友好互动中把主要的内容写了下来,但是关闭时没有保存。
再往上叫应用层。
这里其实合并了会话层,表示层和应用层。
和 copilot 的聊天记录也没留下来。大概就是会话层负责建立,管理,终止两个应用的会话。表示层负责数据加密,以及格式化,包括 png,mp3,以及 ASCII 都是这一层的。应用层直接与用户交互,提供各种网络服务,包括电子邮件,文件传输,远程登陆等。
公网 ip 和私网 ip
公网 ip 就是可以在互联网上访问到的,私网 ip 就是只能在局域网里用的。
公网 ip 并不多,当你的设备在上网时,通常由机构给你的设备用到的宽带分配一个公网 ip,所以很多时候根据 ip 我们能定位到省但是定位不到个人
私网 ip 包括 10.-.-.-,172.16-31,-,- , 192.168.-.- 这三类。
同时还有特殊 ip 包括 127.0.0.1,127.0.0.2,255.255.255.255 等。
以及,我们还能通过 NAT 打洞来实现通过私网 ip 访问到另一网络的设备。
网络编程
原来和网络是不一样的捏
GO 的 net 包提供了一个简便的接口,包括 TCP/IP、UDP、域名解析和 Unix 域套接字。
大多数客户端只需要 Dial,Listen 和 Accept 函数,以及相关的 Conn 和 Listener.
让我们来阅读一下 net.conn
type Conn interface {
// Read reads data from the connection.
// Read can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetReadDeadline.
Read(b [][byte](https://pkg.go.dev/builtin#byte)) (n [int](https://pkg.go.dev/builtin#int), err [error](https://pkg.go.dev/builtin#error))
// Write writes data to the connection.
// Write can be made to time out and return an error after a fixed
// time limit; see SetDeadline and SetWriteDeadline.
Write(b [][byte](https://pkg.go.dev/builtin#byte)) (n [int](https://pkg.go.dev/builtin#int), err [error](https://pkg.go.dev/builtin#error))
// Close closes the connection.
// Any blocked Read or Write operations will be unblocked and return errors.
Close() [error](https://pkg.go.dev/builtin#error)
// LocalAddr returns the local network address, if known.
LocalAddr() [Addr](https://pkg.go.dev/net#Addr)
// RemoteAddr returns the remote network address, if known.
RemoteAddr() [Addr](https://pkg.go.dev/net#Addr)
// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail instead of blocking. The deadline applies to all future
// and pending I/O, not just the immediately following call to
// Read or Write. After a deadline has been exceeded, the
// connection can be refreshed by setting a deadline in the future.
//
// If the deadline is exceeded a call to Read or Write or to other
// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
// The error's Timeout method will return true, but note that there
// are other possible errors for which the Timeout method will
// return true even if the deadline has not been exceeded.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful Read or Write calls.
//
// A zero value for t means I/O operations will not time out.
SetDeadline(t [time](https://pkg.go.dev/time).[Time](https://pkg.go.dev/time#Time)) [error](https://pkg.go.dev/builtin#error)
// SetReadDeadline sets the deadline for future Read calls
// and any currently-blocked Read call.
// A zero value for t means Read will not time out.
SetReadDeadline(t [time](https://pkg.go.dev/time).[Time](https://pkg.go.dev/time#Time)) [error](https://pkg.go.dev/builtin#error)
// SetWriteDeadline sets the deadline for future Write calls
// and any currently-blocked Write call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means Write will not time out.
SetWriteDeadline(t [time](https://pkg.go.dev/time).[Time](https://pkg.go.dev/time#Time)) [error](https://pkg.go.dev/builtin#error)
}
好的,还是边写边学了。。
我们来学一学 HTTP。
首先 Web Page 是由对象组成的,一个对象可以是 HTML 文件,JPEG 图片,Java 程序。一个 Web Page 一般有一个 HTML 基本文件以及几个引用文件。HTML 基本文件通过对象的 URL 地址引用其他对象。一个 URL 地址由存放对象的服务器主机名和对象的路径名组成。主机名大概就是 ip 地址或者域名,比如 http://www.someSchool.edu/someDepartment/picture.gif 其中 www.someSchool.edu 就是主机名,“someDepartment/picture.gif” 是路径名。Web 浏览器就是客户端,服务器端由 Web 服务器实现。
HTTP 定义了 Web 客户向 Web 服务器请求 Web 页面的方式,以及服务器向客户端传送 Web 页面的方式。
用户请求 Web 页面时(点击超链接),浏览器向服务器发出对该页面中所包含的对象的 HTTP 请求报文,服务器接收请求并用包含这些对象的 HTTP 响应报文进行响应。
HTTP 使用 TCP 作为它的支撑运输协议。
HTTP 是一个无状态协议 (stateless protocol),即不记录和保存客户的信息,一旦接收就做出响应。
当客户和服务器长时间通信,客户端发出随机的若干次请求。此时应用程序的研制者需要决定每个请求是单个 TCP 连接还是全部经相同的 TCP 连接。这被称为(非)持续连接。HTTP 默认使用持续连接,也能配置成非持续连接。简单来说,非持续连接的握手会浪费一些时间,更多的 TCP 需要更多的缓冲区并保持 TCP 变量,这会增大服务器的负担。
HTTP 报文包括请求报文和响应报文。
一个典型的 HTTP 请求报文如下
GET /somedir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
User-agent:Mozilla/5.0
Accept-language: fr
值得注意的是报文是用 ASCII 写的。
第一行叫请求行(request line),后面叫首部行(header line)。
请求行的三个字段为 方法字段,URL 字段 和 HTTP 版本字段。
方法字段有 GET,POST,HEAD,PUT,DELETE。绝大多数 HTTP 请求报文使用 GET 方法。
可以看到的首部行有 Host,这指明对象所在的主机,在 Web 代理高速缓存时是需要的。
Connection close 是要求不用持续连接。
User-agent 指明用户代理,及发送请求的浏览器类型,Mozilla/5.0 即 Firefox 浏览器。
Accept-language fr 说明想要得到该对象的法语版本。
一个请求报文的通用格式为
请求行(方法+URL+版本)
首部行(首部字段名+值)
空行
实体体(entity body)
其中 GET 的实体体为空。例如提交表单时使用 POST 方法,则实体体的内容为用户的输入值。当然 HTML 表单也会使用 GET 方法,并在所请求的 URL 中包括输入的数据,比如 www.somesite.com/animalsearch?monkeys&bananas 。
首部行可被浏览器,Web 服务器和 Web 缓存服务器插入。根据浏览器的类型,协议版本,用户配置等都可能会影响首部行。
一个典型的响应报文如下
HTTP/1.1 200 OK
Connection: close
Date: Tue,18 Aug 2015 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html
(data data data ...)
第一行为状态行(status line),若干首部行,以及实体体。
状态行三个字段为 协议版本字段,状态码和相应状态信息。
Date 是发送报文的时间。
Server 类似请求报文的 User-agent,说明这是一台 Apache Web 服务器。
Last-Modified 是对象最后修改的时间。
Content-Type 指定了对象类型。
常见的状态码有
200 OK 请求成功,信息在返回的响应报文中
301 Moved Permanently 对象被转移,新的 URL 在 Location: 首部行中
400 Bad Request 通用差错代码,不能理解
404 Not Found 被请求的文档不在服务器上
505 HTTP Version Not Supported
然后还要看 HTTPS 协议(
因为动态域名安全策略(HSTS),所以浏览器上 http 的连接会转成 https。
服务器响应头:服务器通过在HTTP响应头中包含一个 Strict-Transport-Security
头来启用HSTS。例如:
Strict-Transport-Security: max-age=31536000; includeSubDomains
- `max-age`:指定HSTS策略在客户端缓存中的有效期(以秒为单位)。在此期间,客户端将强制使用HTTPS。
- `includeSubDomains`:可选参数,指示HSTS策略适用于该域名及其所有子域名。
客户端缓存:当客户端(如浏览器)收到包含 Strict-Transport-Security
头的响应时,它会将该策略缓存下来,并在指定的 max-age
期间内强制对该域名及其子域名的所有请求使用HTTPS。
重定向到HTTPS:在HSTS策略有效期内,如果客户端尝试通过HTTP访问该域名,浏览器会自动将请求重定向到HTTPS,而不会发送任何HTTP请求。
然后我们来看 HTTPS 协议。
HTTPS 协议其实是在 HTTP 协议的基础上,在 HTTP 和 TCP 之间添加了 SSL/TSL 层。浏览器首先在 scheme 里标明 https 和 http,然后在端口上选择 443 或 80,然后需要通过不同于 HTTP 报文格式的报文进行 TSL 握手,通过公钥和私钥使得通信能够全程加密。握手结束后发送的 HTTP 报文已在浏览器加密,更加安全。
实际上,我们仍然使用 HTTP 的报文。但是在实现代理时,需要处理 CONNECT 方法,这个方法其实就是要让你的代理返回消息,让浏览器知道你用的代理成功了。在这之后的握手部分我并没有特殊处理,当作 GET 方法的 HTTP 报文也成功了。等下研究下。
要让你的服务器端成功运行在 VPS,首先 VPS 的系统不要太老,不然配环境挺麻烦。然后需要注意 VPS 的防火墙以及你的云服务器商的安全组可能墙掉了你编的端口。