本文接上篇《脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》,继续脑残式的网络编程知识学习 ^_^。php
套接字socket是大多数程序员都很是熟悉的概念,它是计算机网络编程的基础,TCP/UDP收发消息都靠它。咱们熟悉的web服务器底层依赖它,咱们用到的MySQL关系数据库、Redis内存数据库底层依赖它。咱们用微信和别人聊天也依赖它,咱们玩网络游戏时依赖它,读者们可以阅读这篇文章也是由于有它在背后默默地支持着网络通讯。html
本篇文章依然尝试使用动画图片的方式,来对这个知识点进行“脑残式”讲解(哈哈),指望读者们能够更加简单、直观地理解Socket通讯的数据读写本质。git
友情提示:若是您的网速较慢,加载gif动画可能较慢,请耐心等候哦。程序员
学习交流:github
- 即时通信开发交流3群:185926912[推荐]web
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》算法
(本文同步发布于:http://www.52im.net/thread-1732-1-1.html)数据库
钱文品(老钱):毕业于华中科技大学计算机科学与技术专业,互联网分布式高并发技术十年老兵,目前任掌阅科技资深后端工程师。熟练使用 Java、Python、Golang 等多种计算机语言,开发过游戏,制做过网站,写过消息推送系统和MySQL 中间件,实现过开源的 ORM 框架、Web 框架、RPC 框架等。编程
做者的Github: https://github.com/pyloque后端
本文是系列文章中的第2篇,本系列大纲以下:
《脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》
《脑残式网络编程入门(二):咱们在读写Socket时,究竟在读写什么?》(本文)
当客户端和服务器使用TCP协议进行通讯时,客户端封装一个请求对象req,将请求对象req序列化成字节数组,而后经过套接字socket将字节数组发送到服务器,服务器经过套接字socket读取到字节数组,再反序列化成请求对象req,进行处理,处理完毕后,生成一个响应对应res,将响应对象res序列化成字节数组,而后经过套接字将本身数组发送给客户端,客户端经过套接字socket读取到本身数组,再反序列化成响应对象。
通讯框架每每能够将序列化的过程隐藏起来,咱们所看到的现象就是上图所示,请求对象req和响应对象res在客户端和服务器之间跑来跑去。
也许你以为这个过程仍是挺简单的,很好理解,可是实际上背后发生的一系列事件超出了大家中大多数人的想象。通讯的真实过程要比上面的这张图复杂太多。你也许会问,咱们须要了解的那么深刻么,直接拿来用不就能够了么?
在互联网技术服务行业工做多年的经验告诉我,若是你对底层机制不了解,你就会不明白为何对套接字socket的读写会出现各类奇奇乖乖的问题,为何有时会阻塞,有时又不阻塞,有时候还报错,为何会有粘包半包问题,NIO具体又是什么,它是什么特别新鲜的技术么?对于这些问题的理解都须要你了解底层机制。
为了方便你们对通讯底层的理解,我花了些时间作了下面这个动画,它并不能彻底覆盖底层细节的全貌,可是对于理解套接字的工做机制已经足够了。请读者仔细观察这个动画,后面的讲解将围绕着这个动画展开。
咱们平时用到的套接字其实只是一个引用(一个对象ID),这个套接字对象其实是放在操做系统内核中。这个套接字对象内部有两个重要的缓冲结构,一个是读缓冲(read buffer),一个是写缓冲(write buffer),它们都是有限大小的数组结构。
当咱们对客户端的socket写入字节数组时(序列化后的请求消息对象req),是将字节数组拷贝到内核区套接字对象的write buffer中,内核网络模块会有单独的线程负责不停地将write buffer的数据拷贝到网卡硬件,网卡硬件再将数据送到网线,通过一些列路由器交换机,最终送达服务器的网卡硬件中。
一样,服务器内核的网络模块也会有单独的线程不停地将收到的数据拷贝到套接字的read buffer中等待用户层来读取。最终服务器的用户进程经过socket引用的read方法将read buffer中的数据拷贝到用户程序内存中进行反序列化成请求对象进行处理。而后服务器将处理后的响应对象走一个相反的流程发送给客户端,这里就再也不具体描述。
咱们注意到write buffer空间都是有限的,因此若是应用程序往套接字里写的太快,这个空间是会满的。一旦满了,写操做就会阻塞,直到这个空间有足够的位置腾出来。不过有了NIO(非阻塞IO),写操做也能够不阻塞,能写多少是多少,经过返回值来肯定到底写进去多少,那些没有写进去的内容用户程序会缓存起来,后续会继续重试写入。
一样咱们也注意到read buffer的内容可能会是空的。这样套接字的读操做(通常是读一个定长的字节数组)也会阻塞,直到read buffer中有了足够的内容(填充满字节数组)才会返回。有了NIO,就能够有多少读多少,无须阻塞了。读不够的,后续会继续尝试读取。
那上面这张图就展示了套接字的所有过程么?显然不是,数据的确认过程(ack)就彻底没有展示。好比当写缓冲的内容拷贝到网卡后,是不会当即从写缓冲中将这些拷贝的内容移除的,而要等待对方的ack过来以后才会移除。若是网络情况很差,ack迟迟不过来,写缓冲很快就会满的。
细心的同窗可能注意到图中的消息req被拷贝到网卡的时候变成了大写的REQ,这是为何呢?由于这两个东西已经不是彻底同样的了。内核的网络模块会将缓冲区的消息进行分块传输,若是缓冲区的内容太大,是会被拆分红多个独立的小消息包的。而且还要在每一个消息包上附加上一些额外的头信息,好比源网卡地址和目标网卡地址、消息的序号等信息,到了接收端须要对这些消息包进行从新排序组装去头后才会扔进读缓冲中。这些复杂的细节过程就很是难以在动画上予以呈现了。
还有个问题那就是若是读缓冲满了怎么办,网卡收到了对方的消息要怎么处理?通常的作法就是丢弃掉不给对方ack,对方若是发现ack迟迟没有来,就会重发消息。那缓冲为何会满?是由于消息接收方处理的慢而发送方生产的消息太快了,这时候tcp协议就会有个动态窗口调整算法来限制发送方的发送速率,使得收发效率趋于匹配。若是是udp协议的话,消息一丢那就完全丢了。
网络协议内部实现还有更多复杂的细节有待继续挖掘,留着之后继续分析吧。
若是您以为本系列文章过于基础,您可直接阅读如下系列:
《网络编程懒人入门(五):快速理解为何说UDP有时比TCP更有优点》
《鲜为人知的网络编程》系列文章为高阶必读,该系列目录以下:
《鲜为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》
《鲜为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》
关于移动端网络特性及优化手段的总结性文章请见:
《现代移动端网络短链接的优化手段总结:请求速度、弱网适应、安全保障》
《通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》
《理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程》
《高性能网络编程(一):单台服务器并发TCP链接数到底能够有多少》
《高性能网络编程(二):上一个10年,著名的C10K并发链接问题》
《高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了》
《高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索》
《技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)》
《Java新一代网络编程模型AIO原理及Linux系统AIO介绍》
《NIO框架入门(一):服务端基于Netty4的UDP双向通讯Demo演示》
《NIO框架入门(二):服务端基于MINA2的UDP双向通讯Demo演示》
《NIO框架入门(三):iOS与MINA二、Netty4的跨平台UDP双向通讯实战》
《NIO框架入门(四):Android与MINA二、Netty4的跨平台UDP双向通讯实战》
《P2P技术详解(一):NAT详解——详细原理、P2P简介》
《P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解》
《P2P技术详解(三):P2P技术之STUN、TURN、ICE详解》
>> 更多同类文章 ……
(本文同步发布于:http://www.52im.net/thread-1732-1-1.html)