六:高级内容简介:
接下来介绍的几个较高级的内容,便于我们更好的立理解Socket连接中消息传输的诸多场景(较少的一部分,其它的高级内容用到的同学自己研究吧,如套接字选项、连接复用、网络架构、线程池、混合模式等):
6.1 缓冲
有下面几个问题:
1)在一次调用中应该读/写多少数据?
2)如果write成功返回,是否意味着连接的另一端已经接收了数据?
3)是否应该将一个大数据量的write分割成多个小数据量进行多次写入?这样会造成怎样的影像?
我们会一一作出回答;
6.1.1 写缓冲
TCP连接上调用write进行写操作时究竟发生了什么?
1)当你调用write并返回时,就算无异常,也并不意味着数据已经通过网络顺利发送并被客户端套接字接收到;
write返回时,只是表明你已经将数据提交给了Ruby的IO系统和底层的操作系统内核;
2)在应用程序和实际网络硬件之间至少存在一个缓冲层;
在write返回时,数据交到了操作系统内核手中,他可以立即发送,也可以出于效率考虑暂时不发送,将其同别的数据进行合并;
TCP套接字默认将sync设置为true,这就跳过了Ruby的内部缓冲,否则就又要多处一个缓冲层了;
为何需要缓冲区:
所有的IO缓冲都是出于性能的考虑;
1)缓冲使得write调用可以立即返回,幕后再由内核将所有未执行的写操作汇总,在发送时分组及优化,在实现最佳性能的同时避免网络过载;
2)在网络层面上,发送大量小分组会引发客观的开销,因此内核会将多个小数据量的写操作合并成较大数据量的写操作;
6.1.2 该写入多少数据
那么,是否应该将一个大数据量的write分割成多个小数据量进行多次写入?
幸好我们有缓冲去,通常不需要考虑这个问题,获得最佳性能的方法是一口气写入所有的数据,让内核决定如何对数据进行结合;
如果你要做一个相当大数据量的write,比如文件或大数据写入,那最好将这些数据进行分割,避免全部载入内存中,显然,我们唯一需要做的就是优化我们的应用程序;
6.1.3 读缓冲
读操作同样会被缓冲;
如果调用read从TCP连接中读取数据并传给它一个最大读取长度,Ruby实际上可能会接收大于你指定长度的数据;
此时,“多处的”数据会被存储在Ruby内部的读缓冲区中,在下次调用read时,Ruby会先看看自己内部缓冲区中有没有未读数据,然后再通过操作系统内核请求更多的数据;
6.1.4 该读取多少数据
因为TCP提供的是数据流,无法得知发送方到底发送了多少数据,所以在决定读取长度时,只能猜;
一个较大的读取长度可以确保总是可以得到所有可用的数据,但同时,系统会为我们分配内存,如果用不着那么多,就会造成资源浪费;
一个较小的读取长度,就需要多次才能读取完全部的数据,这会导致每次系统调用引发的大量开销;
所以,需要根据应用程序所要接收的数据大小进行调优;
在各类使用套接字的Ruby项目中,多采用readpartial(1024*16),即16KB作为各自的读取长度;
你总可以通过调优服务器来适应当下的数据量以获得最佳性能,犹豫时,16KB是个不错的选择。
6.2 消息划分
现在又一个问题:如何将服务器与客户端之间交换的消息进行格式化?
目前位置,我们可以使用EOF(由close触发)来表明消息的终止,但是如果想在同一个TCP连接上发送多个消息,就需要确定一致的方式来划分消息的起始位置;
事实上,有无数种方法可以用于在消息间进行划分;一些很复杂,一些很简单;取决于你想如何格式化消息;
协议与消息:
消息和协议并不是一回事,比如,http协议既定义了消息边界(连续的新行),又定义了用于消息内容(涉及请求行、头部等)的协议;
协议定义了应该如何格式化消息;
6.2.1 使用新行
使用新行:
Unix系统中是\n,Windows系统中则是\r\n;
现实中实用新行划分消息的协议是HTTP;
6.2.2 使用内容长度
指定内容长度(content length):
使用这种方法,消息发送需要先计算出消息的长度;
1)使用pack将其转换为固定宽度的整数,后面跟上消息主体一并发送;
2)消息接收方首先读取(read)这个长度,然后unpack,获得这个长度值;
3)这样就知道了消息的大小,然后接收方严格读取长度值所指定的字节数,获取完成的消息;
客户端用pack将消息长度转换成一个与处理器要求相一致的整数,这点很重要,因为他确保了任何给定的整数都会被转化为同样数量的字节;
6.3 超时
超时其实就是忍耐;你愿意在套接字连接上等待多长时间呢?套接字读取呢?套接字写入呢?比如5s;
6.4 DNS查询
超时可以让你很好的控制代码,但是有些地方就没法那么随心所欲了;
# ./code/snippets/client_easy_way.rb
require 'socket'
socket = TCPSocket.new('google.com',80)
我们知道Ruby在构造函数内部调用了connect;
我们传入了主机名而非IP地址,Ruby需要查询DNS将主机名解析成可以连接的IP地址;
一个缓慢的DNS服务器会阻塞整个Ruby进程;
6.5 SSL套接字
SSL使用公钥加密提供了一套用于在套接字上进行安全的数据交换机制;
SSL套接字并没有取代TCP套接字,而是将不安全的套接字“升级”到安全的SSL套接字;(如果你愿意,可以在TCP套接字之上再添加一个安全层)
注意,一个套接字可以升级SSL,但是一个套接字不能同时进行SSL和非SSL通信;使用SSL时,端到端的通讯必须全部都使用SSL完成,否则无法保证安全;
对于那些既需要在SSL上又需要在不安全的TCP上运行的服务,需要使用两个端口(两个套接字);
HTTP就是一个常见的例子:不安全的HTTP默认使用端口80,而HTTPS(运行在SSL之上的HTTP)通讯默认是在端口443上;
所有的TCP套接字都可以转换成SSL套接字;(在Ruby中这通常使用标准库中的openssl实现)
会有一个TCP套接字到SSL套接字的包装器,这个过程需要一个SSL证书,在典型的产品设置中,不会让你去生成自签名证书(只适用于开发/测试);通常需要从一个可信任机构购买的证书;该机构会提供给你用于安全通信的cert和key;
总结:
到目前为止,我最初想要学习的目标达到了,这个系列的文章就留给大家;毕竟本文只是作为了解TCP Socket的基础性文章,所以一些Ruby语言下高级使用场景,还需要大家自己去学习和实践,祝大家在技术的道路上不畏艰险,勇往直前。