最近看到一位博主通过脚本语言直接连上了php-fpm,实现了和nginx一样的转发功能。觉得好玩,我自己也尝试了一遍,确实有惊喜。代码如下:
1 | import socket |
然而很多莫名其妙的转码、移位等操作让人觉得晦涩难懂,虽然照葫芦画瓢谁都能写出来,但是终究不是自己的,所以我重新学习了一遍TCP/IP协议,很多之前模糊不清的概念都找到了答案。
难理解的点
为什么不能自己伪造IP访问外网?
答:伪造的IP在TCP第一次握手的时候没问题,但是第二次握手的时候服务端的数据会发到伪造的IP上,你本地根本接收不到,所以伪造IP无法发送Http请求。因此要想访问网站必须要有IP。
为什么TCP要三次握手?
其实4次,5次,无数次握手会更安全。选三次是一个安全连接建立的最小次数。比如吴蜀伐魏,第一次握手是蜀国派诸葛亮去邀请吴国,第二次握手是吴国派鲁肃和诸葛亮一起回蜀国表示愿意联合,第三次握手是鲁肃再回吴国告诉孙权蜀国已经要出兵了。这三次握手缺一不可,如果鲁肃不回吴国,孙权就容易猜忌蜀国是不是反悔了。
为什么TCP要四次挥手?
还是举三国的例子,吴蜀伐魏后期,第一次握手是蜀国派遣使者告诉吴国汉中我打下来了,可以收兵了。第二次握手是吴国收到消息后派遣使者告诉蜀国,我知道了,你先不要收兵,继续牵制魏国,等我打下襄阳再收兵。第三次握手是吴国再派遣使者告诉蜀国,我打下襄阳了,已经收兵,你也快收兵吧。第四次握手是蜀国收到吴国的消息后也收兵了,并派遣使者告诉吴国我已经收兵了。这四次握手缺一不可,双方都需要告诉彼此准备收兵和收兵结束的两个状态。
TCP详解
TCP协议其实也没有那么复杂,简单的讲就是维护着一个状态机,不同的状态组合需要做不同的处理,具体如下:
三次握手
端 | SYN | ACK | SEQ_DATA | ACK_DATA | established |
---|---|---|---|---|---|
客户端 | 1 | 0 | 9527(随机数) | null | 0 |
服务端 | 1 | 1 | 1234(随机数) | 9528(9527+1) | 1 |
客户端 | 0 | 1 | null | 1235(1234+1) | 1 |
SYN和ACK是状态机的标志位,SEQ_DATA和ACK_DATA是唯一的数字编码,用来校验连接的顺序和唯一性。established也是一个状态,来表示是否能传输数据。
数据传输
端 | SYN | ACK | SEQ_DATA | ACK_DATA |
---|---|---|---|---|
客户端 | 1 | 1 | 9528(9527+1) | 1235(1234+1) |
服务端 | 0 | 1 | null | 9529(9527+2) |
四次挥手
端 | FIN | ACK | SEQ_DATA | ACK_DATA |
---|---|---|---|---|
客户端 | 1 | 1 | 9529(9527+2) | 1235(1234+1) |
服务端 | 0 | 1 | null | 9530(9527+3) |
服务端 | 1 | 0 | 1235(1234+1) | null |
客户端 | 0 | 1 | null | 1236(1234+2) |
SYN和ACK、FIN等标志位通过0,1的组合实现了三次握手,数据传输,4次挥手的所有状态。而且状态机占用的空间非常小,这让TCP非常精简,但是又异常的强悍。
Socket
最后说说SOCKET,Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。socket()、bind()、listen(),accept(),read()、write(),close();