基础层:物理层(Physical)、数据链路层(Datalink)、网络层(Network)。
传输层(Transport):TCP-UDP协议层、Socket。
高级层:会话层(Session)、表示层(Presentation)、应用层(Application)。
计算机编程,如果不调用任何API函数,可能什么都做不了。
如果不相信,请你写段程序告诉我们
1 + 1 =?
几乎每个可以工作的程序,都是在使用现有的API在工作,比如:
#include <stdio.h>
int main() {
printf("1+1=2n");
return 0;
}
通过加亮的printf函数,可以将结果打印在屏幕上,或者其它函数打印到文件里。
调用已有的API接口函数来编程,这是业界的共识,这样可以大大提高生产效率。
一位超级牛人,是否可以不调用任何接口函数,将1+1=2 这个结果打印到屏幕上来?
可以的,需要花费很多很多力气来编程,最终确实可以成功,但是生产效率特别低下。
如果将字符打印到屏幕这段最原始的代码封装成printf(),分享出来,别人直接就call一下,是不是就太简单了?
计算机太复杂了,编程亦然。TCP/IP为了让千千万万程序员日子好过一点,分享了两件秘密武器。
秘密武器之一:关系集/仓库
不仅拥有收packet的仓库,还有发packet的仓库区,更维护着千丝万缕的关系网。它的名字其实准确应该叫“关系网大全”。西方计算机科学家对关系网理解显然没有东方民族深刻,故给它起一个非常抽象的名字,Socket。
当你使用socket()这个接口成功申请仓库,你会得到这个仓库的编号,比如007。记住了,007号仓库就是你与这个世界(Internet)通信的关系网入口。
如果你用UDP申请的007仓库,你可以直接sendto(),发送你已经打包好的packet了,在sendto()里要记住带上仓库的编号007。你的packet就发出去了,甚至都没有在仓库里呆上0.1毫秒。简单吧?
这个sendto() 就是
秘密武器之二:操作集(Operations)
TCP/IP会根据007编号,找到对应的关系网,并将目的IP、目的端口临时记在这个关系网里。凭借这个关系网,可以完成流水线的call chain,最终packet从网卡流出。
当返程的packet到来时,TCP/IP依赖于这个packet的五元组
UDP + Source IP + Destination IP + Source Port + Destination Port
计算一个hash,然后用这个hash到hash表查询,得到仓库编号=007,于是就将packet放入007号仓库接收区。
内部关系网还维系着007号仓库关联的进程ID,于是唤醒对应的进程ID,进程的read()由于进程的唤醒而退出阻塞状态,从而将007号仓库的packet读出。
上文的sendto(), read()都是操作集的成员。
但是,如果用户你申请的仓库不是UDP,而是TCP型号的仓库。通过socket()申请成功后,你不能立马用sendto()来发送packet。
如果你执意要那么做,对不起,会出错的,系统不会睬你。
假如你成功申请到了008号仓库,接下来的所有操作都要带上008号仓库的编号,因为操作集的所有操作都需要008号关系网来维系。
需要先用connect()将本地的关系网,与接收端的关系网,逻辑上勾连起来,就是耳熟能详的建立TCP连接。
如果connect()成功返回,接下来你就可以用008号仓库来收发packet了。
上文的007号仓库,你可以将货物packet发到世界各地(Any),没有任何限制。
但是008号仓库,你只能发给一个目的地,即你用connect()连接的服务器的IP,记住了,这个IP地址是唯一的,(Only one)。
从007号仓库发出的packet,只是名义上的,因为packet从来没有进入过007号仓库,而是直接从用户进程直接离开进入UDP/IP/NIC。
从008号仓库发出的packet,必须要进入008号仓库的发送缓冲区,然后由TCP来决定是否发送,何时发送,发送多少。
在发送的时刻,还会启动重传定时器,如果定时器响了,对方的ACK还没有到来。TCP会从008仓库将packet取出再次发送。
如果运气好,收到对方的ACK,由TCP将packet从发送区移走,并释放其占用的内存资源。
如果运气非常不好,网络断了,重传次数到达上限,TCP会Reset这个TCP连接。
Reset的潜台词是,将008好仓库维系的关系网全部释放。并发出Reset报文。如果对方收到Reset报文,无条件释放对应的关系网。
如果网络一直断着,接收方压根无法收到Reset报文,那么它就一直以为这个TCP连接依然健在。直到下次发送packet,才会触发对端的再次Reset。
当008号仓库所有的packet都发送完毕,还需要断开这个TCP连接,可以使用shutdown(),close()来完成。
一旦双方都close(),先发出FIN报文的一方需要逗留time_wait,确保TCP连接这个逻辑通道上所有的packet都排尽了,才最终释放仓库。
由于008仓库是客户端,一般都是主动发出FIN报文的,故需要逗留time_wait,才能释放008号仓库所维系的关系网。
而服务器在自己的FIN报文被ACK之后,可以直接释放自己的关系网,将内存资源完全释放。
回顾一下
通过上文的描述,TCP连接是由用户通过connect()操作完成的。
但是,用户必须先用socket()成功申请到TCP型号的仓库。然后拿着申请到的008号仓库(关系集)去做connect()、send()、recv()操作集。
Socket,关系集。没有它寸步难行。
socket()、connect()、send()、recv()、close(),操作集。没有它们,什么也干不了。
原文始发于微信公众号(车小胖谈网络):为什么说一个tcp连接是会话层session的通道,不是通过socket来连接的吗?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论