此系列文章分为三篇,本文对应CSAPP的第三卷:程序间的交流与通信。现代程序不再是单纯的独立执行,而是要进行协作,这其中就涉及到了系统内通信,系统间通信,并发控制
第一卷:程序结构与执行——信息表示、指令、处理器、性能优化、储存层次
第二卷:在系统上运行程序——链接、异常控制流、虚拟内存
第三卷:程序间的交流与通信——系统级IO、网络编程、并发编程
Unix系统中,接口分为三类:
Unix中文件具有高度的统一性:
文件的目录按照树状结构组织,通过路径名逐层访问。每个进程有一个cwd,即工作目录。
注意,Unix的文件操作都是系统调用。
不足值发生的情况:
Unix打开文件的流程如下:
共享文件有两种方式:
父子进程这种共享很合理,而且和父子进程资源独立并不冲突。虽然子进程复制了一份文件描述符表,但是指向的确是同样的位置,所以子进程其实只是复制了一份指针表,指向的空间还是共享的。
C编程中,常用freopen将txt文件的内容重定向到stdin里,取代手工输入。Linux中,通过dup函数实现重定向。
dup函数修改文件描述符表,dup(a,b)是将a写入到b中。
下图,调用dup(4,1)后,原来的1就被4覆盖。
举个例子。下图中,进程打开了三次文件,生成三个打开文件,此时三个文件指针都指向文件开头互不干扰。之后,使用dup(fd2,fd3),则将fd3覆盖为fd2,此时,原来的fd3和fd2同时指向一个打开文件,相当于公用一个文件指针了。
读取fd1,输出a
读取fd2,fd3,其实都是在读取fd2,所以输出ab。
下图展示了父子进程的文件共享。可以看到,父子进程指向的空间是公用的。s的不同会导致父子进程其中一个休眠。
因为两个进程是共享文件的,所以三次读取,一定是abc。
标准IO打开的文件都称作流(stream)。这是文件描述符和内存缓冲区的抽象。
(此处是我的个人理解:IO流和Unix有什么区别呢?Unix读取是读取到char数组中的,而C语言IO流默认输入就是字节的形式,不去管你怎么编码的,就顺着读。那怎么知道要读多少呢,读的又是什么东西?通过fscanf和fprintf的格式控制符来实现即可。所以,流读写比Unix的IO更加方便,形式多样)
同Unix一样,C程序也会有默认的打开文件,分别是stdin,stdout,stderr,但是这三个文件已经是流了。
标准IO的基础是Unix的IO,也就是说,即使你只用一个getc函数读取一个字符,你也是进行了一次系统调用。系统调用要消耗1w个时钟周期,是非常费时的。反而是系统调用以后的IO比较快。所以,为了加速,应该尽可能减少系统调用的次数。
标准IO采用缓冲机制,读的时候,先一次性读取一个比较大的字节块到IO缓冲区,然后用户输入再从缓冲区读你想要的东西。当缓冲区没有的时候,再进行Unix系统调用读取一个字节块。
写的时候,也是先写到IO缓冲区,最后一次性输出。可以使用fflush手动刷新缓冲区,当然,在碰到"\n"的时候也会自动刷新。
在大一的时候,写C语言会碰到各种读写的问题,很多都是缓冲区在作祟,比如缓冲区里剩下一个换行符,你得手动刷新才不影响下次读取。
RIO是一组特殊封装的IO函数。与Unix的读写类似,但是具有高度的健壮性,特别适合从网络套接字读取文本行。
Unix IO:
标准IO:
三种IO各有千秋,要根据情况进行选自:
最开始是ARPA,这是互联网的前身。ARPA中有IMP,通过简单的网络协议通信。后来发展到了大学之间的组网。此后发展非常迅速,很快遍布全球。
当节点数量够多的时候,僵尸网络就出现了。有人被不知不觉中控制,成为肉鸡,用于实行ddos攻击。
网络不是凭空传递的,无论是最开始还是现在,通过线缆是最稳定的方法,所以现在世界上有大量的海底线缆。
客户-服务器事务。
这个就是我们当时写C++大作业,做简化版QQ的时候,用的通讯模型。
主机和客户可以在一台电脑上。
硬件是网络的基础,而网络还是依附于原有的计算机架构的。
注意,适配器其实就是网卡,挂在计算机总线上。(我自己装的台式机,主板上用PCIE-x1总线就可以接网卡)
网络,本质上就是若干节点之间通过电线链接。这种链接是分层的,比如我家里几台设备连到一个路由器上,然后这个路由器挂到网线上,我家里的设备就是更底层的。按照层次来说,有如下级别:
在LAN和WAN之间,其实还有不同的层级,但是都类似。
以太网是房间,楼层级别的。
多个主机,通过集线器,链接到一个hub上,一个hub用一个port标识。
线缆速度不高,但是对一台机器来说够用,也有100Mb/s了。
每一个以太网适配器(对应一台主机上的网卡)都有一个唯一的mac地址。
主机以帧为单位向其他主机发送bit,一个主机向集线器发送,其他所有主机都可以看到。
集线器是过时的东西。现在是通过网桥(交换机、路由器)来链接的。
hub可以覆盖的距离非常短,通常就是一个房间。后面出现了网桥连接(此时是交换机),就可以跨越房间,楼层,建筑物了。
集线器不具有选择性,发出去都可以收到。而网桥具有选择性:
前面的以太网和桥接网络都是局域网,为了便于描述,局域网都用下面这种模式描述:一根线上面搭了若干主机。
局域网之间通过广域网链接。
不同的局域网,往往并不兼容,所以在局域网之间的交换设备要更加复杂,即路由器:
到了路由这一层,网络的链接关系就比较混乱了,也就是所谓的自组织互联,实际上就是没有固定的拓扑结构,广域网通信要通过若干个节点多次跳转。
因此,从一个局域网通过广域网发送到另一个局域网的路径是多种多样的,如何选择最优的道路(路由),是很多人研究的课题。
局域网和广域网之间互不兼容,如何发送信息,就需要制定一个统一的标准,这就是网络协议。协议由软硬件协作构成,主要解决两个问题:
举个例子:
现在的网络协议是TCP/IP协议族:
以上的传输,在软件上要通过混合Unix文件IO和套接字接口函数来访问。
从宏观的角度来说,互联网应用有三层组织:
一组:内网ip,外网ip
域名解析:字符解析成IP
进程可以通过网络通信
IPV4,分层,所以还没有爆满
32位4字节,以大端法储存在内存中。
常用点分十进制记法。
分层结构:
一级域名都是公益的,往下分支。有一个公益组织,维护三级域名。
北京的局域网出口是清华。比如北理工校园网,先要连到清华,之后清华才连接到第三级域名上。VPN软件是跳过了清华,直接连到了第三级别域名上。
IPV6,128位,未来要替代。
DNS可以将字符串映射为IP,DNS只是为了方便实用,所以实际使用其实不一定要DNS,比如校园网的10.0.0.55。
这个映射,物理上基于一个巨大的全球分布式DNS数据库。从逻辑上说,DNS可以简单理解为一张表,里面有上百万个主机字符串与IP的映射条目。
通过工具可以查看IP和域名之间的映射关系:
可以看到,多个域名可以映射到同一个IP。更加复杂的情况下,可以把一个域名池映射到一个IP池中,用于负载均衡,在解析的时候,优先解析负载比较轻的IP,让系统均摊流量。
还有一种是,有合法域名但是没有IP映射。可能是预留的域名,也可能是隐藏了。
通常是用TCP进行客户端和服务器链接。TCP的特点:
使用TCP协议编程的时候,要构建对应于某个IP的套接字,套接字格式为IP:port
对。
在网络通信中,端口是16位整数,用于表示进程。
有的是临时端口,用于普通的通信,内核自动分配。有的是预留端口,与特定服务有关,比如80端口是web服务器端口,22是SSH端口,21是ftp文件传输端口。
套接字这个名字取得很形象。客户端有一个ip:port对,服务器也有ip:port对,就像是两方各给一个接口,两个接口可以套在一起,形成一个套接字对。
下图说明了,端口与进程一一对应。切换不同的端口可以访问一台主机上的不同任务。
套接字是一组与Unix IO结合的系统级函数,用于在Client级别编写通信程序。套接字起源很早,适用于现在所有系统。
具体编程基本就是下面的流程,思想很朴素,代码就不帖了,到时候学计网自然会学:
逻辑上的视图如下:
前面是套接字的宏观逻辑,这里讲更精细的。套接字有两种,底层的数据是一样的,只不过是两种不同的视图,所以可以互相强制转换:
这章不学了,就看懂几个公式,猜出来是什么意思就行:
在2022年12月28日的晚上23:48分,明天考试。但是,该睡觉了,也学不完了,后面也没必要学了。
故本章略。
明早起来看看例题,就准备考试了。