深入剖析Socket实现

文章出自:http://blog.csdn.net/cumtwyc/article/details/48273085

在我们平时的开发中用到的最多的是HTTP协议,而HTTP协议本身是一种应用层协议,属于文本协议;并且这种协议也基本上满足了应用的大部分需求。HTTP协议当初的设计并没有想到它应用的是如此的广泛,所以设计的时候考虑的比较简单实用,也许也就是这种简单实用才这么广泛;但如今,HTTP协议似乎并不能满足所有的需求,特别是当今的web2.0时代,浏览器应用横行的年代,也越来越多需要长连接的应用,所以在HTML5以及Flash等客户端应用中都加入了长连接的定义,并且我也相信在未来的互联网开发中会出现很多的长连接应用。在我们公司也曾经自己开发过长连接的应用,前端是基于flash的,后端是基于Java的实现,自己基于TCP/IP协议制定了一套稳定,安全,可靠的应用层协议,至今一直在线上运行,情况也比较稳定;在此,我想基于我的知识和对于socket的理解在这里做一次分享,也许不是很深入和透彻,但绝对很基础。

其实如果不理解套接字的具体实现所关联的数据结构和底层协议的工作细节,就很难抓住网络编程的精妙之处,对于TCP套接字(即Socket的实例)来说更是如此。这里我就对创建和使用Socket和ServerSocket实例的底层细节进行介绍。请注意,这些内容仅仅涵盖了一些普通的事件实例,略去了很多细节。尽管如此,我相信即使是这样的基础的理解也是有用的。如果希望了解更详尽的内容,可以参考TCP规范,或关于该方面的其他著作(例如TCP/IP详解)。
图1是一个Socket实例所关联的一些信息的简化视图。JVM或其运行的平台(即,主机操作系统中的“套接字层”)为这些类的支持提供了底层实现。Java对象上的操作则转换成了这种底层抽象上的操作。在这里,“Socket”指的是图1中的类之一,而“套接字(socket)”指的是底层抽象,这种抽象是有操作系统提供或由JVM自己实现(例如在嵌入式系统中)。有一点需要注意,即运行在统一主机上的其他程序可能也会通过底层套接字抽象来使用网络,因此会与Java Socket实例竞争系统资源,如端口等。

socket
socket

图1
在此,“套接字结构”是指底层实现(包括JVM和TCP/IP,但通常是后者)的数据结构集,这些数据结构包括了特定Socket实例所关联的信息。例如,套接字结构除其他信息外还包括:
l 该套接字说关联的本地和远程互联网地址和端口号。本地互联网地址(图中标记为“Local IP”)是赋值给本地主机的;本地端口号在Socket实例创建时设置的。远程地址和端口号标记了与本地套接字连接的远程套接字(如果没有连接的话)。不久,我们将对这些值确定的时间和方式做进一步介绍。
l 一个FIFO(先进先出,First In First Out)队列用于存放接收到的等待分配的数据,以及一个用于存放等待传输的数据的队列。
l 对于TCP套接字,还包括了与打开和关闭TCP握手相关的额外协议状态信息。图1中,状态是“关闭”;所有套接字的起始状态都是关闭的。
一些多用途操作系统为用户提供了获取底层数据结构“快照”的工具,netstat是其中之一,太在UNIX(Linux)和Windows平台上都可用。只要给定适当的选项,netstat就能显示和图1的那些信息:SendQ和RecvQ中的字节数,本地和远程IP地址和端口号,以及连接状态等。netstat的命令行选项有多种,但它输出看起来是这样的:
Active Internet connections(server and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:36045 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:53363 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
tcp 0 0 128.133.190.219:34077 4.71.104.187:80 TIME_WAIT
tcp 0 0 128.133.190.219:43346 79.62.132.8:22 ESTABLISHED
tcp 0 0 128.133.190.219:875 128.133.190.43:2409 ESTABLISHED
tcp6 0 0 :::22 :::* LISTEN
前4行和最后一行描述了正在侦听连接的服务器套接字。第5行代表了到一个Web服务器(80端口)的连接,该服务器已经单方面关闭。倒数第2行是先有的TCP连接。如果系统支持的话,你可能想要尝试一下netstat,来检测下上文描述的场景的连接状态。然而要知道,这些图中描述的状态转换过程转瞬即逝,可能很难通过netstat提供的“快照”功能将其捕获。
了解这些数据结构,以及底层协议如何对其进行影响是非常有用的,因为它们控制了各种Socket对象行为的各个方面。例如,由于TCP提供了一种可信赖的字节流服务,任何写入Socket的OutputStream的数据副本都必须保留,直到其在连接的另一端被成功接收。向输出流写数据并不意味着数据实际上已经被发送,他们只是被复制到了本地缓冲区。就算在Socket的OutputStream上进行flush操作,也不能保证数据能够立即发送到信道。此外,字节流服务的自身属性决定了其无法保留输入流中消息的边界信息,这里的边界信息的意思就是上一个数据包和下一个数据包之间的区别信息。这使一些协议的接收和解析过程变得复杂。另一方面,对于DatagramSocket,数据包并没有为重传而进行缓存,任何时候调用send()方法返回后,数据就已经发送给了执行传输任务的网络子系统。如果网络子系统由于某种原因无法处理这些消息,该数据包将毫无提示地被丢弃(不过这种情况很少发生)。
1、缓冲区和TCP
作为程序员,在使用TCP套接字时需要记住的最重要一点是:
不能假设在连接的一端将数据写入输出流和在另一端从输入流读取数据之间有任何一致性。
尤其是在发送端由单个输出流的write()方法传输的数据,可能会通过另一端的多个输入流的read()方法来获取;而一个read()方法可能会返回多个write()方法传输的数据。
为了展示这种情况,考虑如下程序:
byte[] buf0 = new byte[1000];
byte[] buf1 = new byte[2000];
byte[] buf2 = new byte[5000];

Socket s = new Socket(destAddr, destPort);
OutputStream out = s.getOutputStream();

out.write(buf0);

out.write(buf1);

out.write(buf2);

s.close();
其中,圆点代表了设置缓冲区数据的代码,但不包括对out.write()方法的调用。在本节的讨论中,“in”代表接收端Socket的InputStream,“out”代表发送端Socket的OutputStream。
这个TCP连接想接收端传输8000字节。在连接的接收端,这8000字节的分组方式取决于连接两端out.write()方法和in.read()方法的调用时间差,以及提供给in.read()方法的缓冲区大小。
我们可以认为TCP连接上发送的所有字节序列在某一瞬间被分成了3个FIFO队列;
l SendQ:在发送端底层实现中缓存的字节,这些字节已经写入了输出流,但还没在接收端主机上成功接收。
l RecvQ:在接收端底层实现中缓存的字节,等待分配到接收程序,即从输入流中读取。
l Delivered:接收者从输入流已经读取到的字节。
调用out.write()方法将向SendQ追加字节。TCP协议负责将字节按顺序从SendQ移动到RecvQ。有重要的一点需要明确,这个转移过程无法由用户程序控制或直接观察到,并且在块中(chunk)发生,这些块的大小在一定程度上独立于传递给write()方法的缓冲区大小。
接收程序从Socket的InputStream读取数据时,字节就从RecvQ移动到Delivered中,而转移的块的大小依赖于RecvQ中的数据量和传递给read()方法缓冲区大小。
图2展示了上例中3次调用out.write()方法后,另一端调用in.read()方法前,以上3个队列的可能状态。不同的阴影效果分别代表了上文中3次调用write()方法传输的不同数据。
图2描述的发送端主机的netstat输出的瞬间状态中,会包含类似于下一行的内容:

在接收端主机,netstat会显示:

图1
图1

图2 3次调用write()方法后3个队列的状态
现在假设接收者调用read()方法时使用的缓冲区数组大小为2000字节,read()调用则将把等待分配队列(RecvQ)中的1500字节全部移动到数组中,返回值为1500。注意,这些数据包括了第一次和第二次调用write()方法时传输的字节。在过一段时间,但TCP连接传完更多数据后,这三部分的状态可能如图3所示。


图3
图3

图3 第一次调用read()方法后
如果接收者现在调用read()方法时使用4000字节的缓冲区数组,将有很多字节从等待分配队列(RecvQ)转移到已分配队列(Delivered)中。这包括第二次调用write()方法时剩下的1500字节加上第三次调用write()的前2500字节。此时队列的状态如图4所示。


图4
图4

图4 另一次调用read()后
下次调用read()方法返回的字节数,取决于缓冲区数组的大小,以及发送方套接字/TCP实现通过网络向接收方实现传输数据的时机。数据从SendQ到RecvQ缓冲区的移动过程对应用程序协议的设计有重要的指导性。我们已经遇到过需要对使用带内(in-band)分隔符,并通过Socket来接收的消息进行解析的情况。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • ———————————————回答好下面的足够了---------------------------------...
    恒爱DE问候阅读 1,710评论 0 4
  • 史上最全的iOS面试题及答案 iOS面试小贴士———————————————回答好下面的足够了----------...
    Style_伟阅读 2,344评论 0 35
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,960评论 0 7
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,278评论 0 6
  • 多线程、特别是NSOperation 和 GCD 的内部原理。运行时机制的原理和运用场景。SDWebImage的原...
    LZM轮回阅读 2,001评论 0 12