TCP/IP 协议详解 第2章 链路层

在TCP/IP协议族中,链路层主要有三个目的:

  1. 为IP模块发送和接收IP数据报;
  2. 为ARP模块发送ARP请求和接收ARP应答;
  3. 为RARP发送RARP请求和接收RARP应答。

以太网和IEEE 802封装

以太网这个术语一般是指DEC、Intel、Xeror公司在1982年联合发布的一个标准,它是当今TCP/IP采用的主要的局域网技术,以太网IP数据报的封装在RFC894中定义,地址是48bit。

IEEE 802 是IEEE(电子电气工程师协会)802委员会公布的一个稍有不同的标准集,其中802.3针对整个CSMA/CD网络,802.4针对令牌总线网络,802.5针对令牌环网络。

最常用的封装格式是RFC894。

IEEE 802.2/802.3(RFC-1042)和以太网封装格式(RFC-894) (方框下的数字表示占用的字节数):
ieee802和以太网帧封装格式

继续阅读

TCP/IP 协议详解 第1章 概述

引言

TCP/IP起源于60年代美国政府资助的一个分组交换网络研究项目。

下面的说明来自 维基百科:

  • 分组交换:在计算机网络和通讯中是一种相对于电路交换的通信范例,分组(又称消息、或消息碎片)在节点间单独路由,不需要在传输前先建立通信路径。
  • 电路交换:要求必须首先在通信双方之间建立连接通道。在连接建立成功之后,双方的通信活动才能开始。通信双方需要传递的信息都是通过已经建立好的连接来进行传递的,而且这个连接也将一直被维持到双方的通信结束。在某次通信活动的整个过程中,这个连接将始终占用着连接建立开始时,通信系统分配给它的资源(通道、带宽、时隙、码字等等),这也体现了电路交换区别于分组交换的本质特征。

分层

网络协议通常分为不同层次进行开发,每一层分别负责不同的通信功能。一个协议族,比如TCP/IP,是一组不同层次上的多个系统的组合。

继续阅读

JUC 可重入 读写锁 ReentrantReadWriteLock

读写锁 ReadWriteLock

读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有writer,读取锁可以由多个reader线程同时保持。写入锁是独占的。

互斥锁一次只允许一个线程访问共享数据,哪怕进行的是只读操作;读写锁允许对共享数据进行更高级别的并发访问:对于写操作,一次只有一个线程(write线程)可以修改共享数据,对于读操作,允许任意数量的线程同时进行读取。

与互斥锁相比,使用读写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。

读写锁适用于读多写少的情况。

可重入读写锁 ReentrantReadWriteLock

属性

ReentrantReadWriteLock 也是基于 AbstractQueuedSynchronizer 实现的,它具有下面这些属性(来自Java doc文档):

  • 获取顺序:此类不会将读取者优先或写入者优先强加给锁访问的排序。
    • 非公平模式(默认):连续竞争的非公平锁可能无限期地推迟一个或多个reader或writer线程,但吞吐量通常要高于公平锁。
    • 公平模式:线程利用一个近似到达顺序的策略来争夺进入。当释放当前保持的锁时,可以为等待时间最长的单个writer线程分配写入锁,如果有一组等待时间大于所有正在等待的writer线程的reader,将为该组分配读者锁。
    • 试图获得公平写入锁的非重入的线程将会阻塞,除非读取锁和写入锁都自由(这意味着没有等待线程)。
  • 重入:此锁允许reader和writer按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入reader使用读取锁。
    writer可以获取读取锁,但reader不能获取写入锁。
  • 锁降级:重入还允许从写入锁降级为读取锁,实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。

  • 锁获取的中断:读取锁和写入锁都支持锁获取期间的中断。

  • Condition 支持:写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。
    读取锁不支持 ConditionreadLock().newCondition() 会抛出 UnsupportedOperationException

  • 监测:此类支持一些确定是读取锁还是写入锁的方法。这些方法设计用于监视系统状态,而不是同步控制。

继续阅读

TCP/IP 协议详解 第22、23章 TCP的定时器

第22章 TCP的坚持定时器

TCP通过让接收方指明希望从发送方接收的数据字节数(即窗口大小)来进行流量控制。如果窗口大小为0将有效阻止发送方传送数据,直到窗口变为非0为止。

ACK的传输不可靠,TCP不对ACK报文段进行确认,TCP只确认那些包含有数据的ACK报文段。

为了防止 “接收方等待接收数据(因为它已经向发送方通告了一个非0窗口),而发送方在等待允许它继续发送数据的窗口更新” 这种死锁发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(window probe)。

坚持状态与重传超时之间的一个不同的特点就是TCP从不放弃发送窗口探查,这个过程将持续到 或者窗口被打开,或者应用程序使用的连接被终止。

糊涂窗口综合症

糊涂窗口综合症(SWS,Silly Window Syndrome)发生时会出现:少量的数据将通过连接进行交换,而不是满长度的报文段。

该现象可发生在两端中的任何一端:接收方可以通告一个小的窗口(而不是一直等到有大的窗口时才通告),而发送方也可以发送少量的数据(而不是等待其他的数据以便发送一个大的报文段)。可以在任何一端采取措施避免出现糊涂窗口综合症的现象。

  • 接收方不通告小窗口。通常的算法是接收方不通告一个比当前窗口大的窗口(可以为0),除非窗口可以增加一个报文段大小(也就是将要接收的MSS)或者可以增加接收方缓存空间的一半,不论实际有多少。

第23章 TCP的保活定时器

保活定时器不是TCP规范的一部分。

保活功能主要是为服务器应用程序提供,试图在服务器端检测客户端已消失的半开放的连接。

使用保活选项的一端称为服务器,另一端则为客户端,可以两端都使用。

如果一个给定连接在两个小时内没有任何动作,则服务器向客户发送一个探查报文段。客户主机必须处于以下4个状态之一:

  1. 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常工作的。服务器在两小时以后将保活定时器复位。如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来2小时再复位。
  2. 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务器将不能够收到对探查的响应,并在75秒后超时。服务器总共发送10个这样的探查,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
  3. 客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。
  4. 客户主机正常运行,但是从服务器不可达。这与状态2相同,因为TCP不能够区分状态4与状态2之间的区别,它所能发现的就是没有收到探查的响应。

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

TCP/IP 协议详解 第21章 TCP的超时与重传

TCP通过确认从另一端收到数据来提供可靠的运输层,由于数据和确认都有可能丢失,TCP通过在发送时设置一个定时器来解决这种问题,当定时器溢出时还没有收到确认,它就重传该数据。对于任何实现而言,关键在于超时和重传的策略。

对于每个连接,TCP管理4个不同的定时器:

  • 重传定时器 使用于当希望收到另一端的确认。
  • 坚持(persist)定时器 使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
  • 保活(keepalive)定时器 可检测到一个空闲连接的另一端何时崩溃或重启。
  • 2MSL定时器 测量一个连接处于 TIME_WAIT 状态的时间。

在进行超时重传时,两次重传的时间间隔是一个倍乘关系(每次增加1倍),称为“指数退避(exponential backoff)”。

继续阅读

TCP/IP 协议详解 第19、20章 TCP的数据流

第19章 交互数据流

交互数据总是以小于最大报文段长度(MSS,Maximum Segment Size)的分组(一般也称为微小分组,tinygram)发送。

经受时延的确认

通常TCP在收到数据时并不立即发送 ACK,它会推迟发送,以便将 ACK 与需要沿该方向发送的数据一起发送(这种现象也称为数据捎带 ACK)。绝大多数实现采用的时延是200ms。

Nagle 算法

Nagle 算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组,并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达的越快,数据也就发送得越快。在希望减少微小分组数目的低速广域网上,则会发送更少的分组。

对于低延时的应用则需要关闭Nagle 算法,可以通过 TCP_NODELAY 选项来关闭。

在发送的报文段中如果包含了丢失的报文段中的数据,则称为重新分组化。

继续阅读

TCP/IP 协议详解 第18章 TCP 连接的建立与终止

TCP是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。

连接建立与终止

连接建立协议

为建立一条TCP连接的步骤:

  • 客户端发送一个 SYN 段(SYN 标志置 1)指明客户端打算连接的服务器的端口,以及其初始序号 ISN。
  • 服务器发回包含服务器的初始序号的 SYN-ACK 报文段(SYN、ACK标志都置 1)作为应答,确认序号设置为客户端的 ISN 加 1 以对客户端的 SYN 报文段进行确认(一个 SYN 占用一个序号)。
  • 客户端必须将确认序号设置为服务器的 ISN 加 1 以对服务器的 SYN-ACK 报文段进行确认。

这三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake)。

继续阅读

TCP/IP 协议详解 第17章 TCP 传输控制协议

TCP使用网络层(IP),提供了一种面向连接的、可靠的字节流服务。

面向连接意味着两个使用TCP协议的应用在彼此交换数据之前必须先建立一个TCP连接。

TCP使用以下方式来提供可靠服务:

  • 应用数据被分割成TCP认为最适合发送的数据块。由TCP传递给IP的信息单位称为报文段或段(segment)。
  • 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  • 当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。
  • TCP将保持它首部和数据的校验和。这是一个端到端的校验和,目的是检测在传输过程中的任何变化。如果收到的校验和有差错,TCP将丢弃这个报文段和不缺人收到这个报文段(希望发端超时并重发)。
  • TCP报文段接收到的顺序是不确定的,所以TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
  • IP数据包还会发生重复,TCP的接收端必须丢弃重复的数据。
  • TCP还能提供流量控制。TCP连接的双方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。

继续阅读

MySQL 事件调度器 定时调度

MySQL 5.1 引入的时间调度器可以作为定时任务调度器,取代系统的cron调度,调度时间可以精确到秒,实时性好。

开启事件调度器

首先查看是否开启了事件调度器: show variables like "event_scheduler";SELECT @@event_scheduler;

开启事件调度器: SET GLOBAL event_scheduler = ON;,这个命令需要具有 SUPER 权限才能执行,可以用 root 用户来执行,说明是作用在整个数据库服务上的,而不单是某个数据库实例。

MySQL服务器默认是没有开启事件调度器的,这样上述通过命令开启的在MySQL服务器重启后会失效,可以在配置文件里默认开启,在 [mysqld] 下添加配置: event_scheduler=ON 默认开启。

查看事件的执行情况

SELECT * FROM information_schema.EVENTS;

继续阅读

Struts2 备忘

今天帮两个同事各看了一个问题,有点意思。

Struts2 JSON 序列化与 Action get 方法

场景:同事跑来说他的ajax请求总是进入ajax的错误处理的回调函数,他需要用ajax提交请求到action,然后处理返回结果。

异常现象:这个同事也没什么经验,他告诉我的是ajax请求总是进入ajax的错误处理的回调函数,我在chrome开发工具那里一看,http响应是500,肯定进入错误处理的回调函数了,所以问题应该是在服务端。

分析与解决:在eclipse里debug跟踪发现action执行是ok的,返回的结果名也是 SUCCESS,配置也没问题,但控制返回给Struts2框架后却抛出一堆异常,异常信息跟请求完全无关,从异常栈来看,抛出异常的方法也没有在请求方法里调用,虽然它们都在同一个action里,异常栈的方法基本都是JSON序列化有关的方法的,也就是说问题应该是出在Struts2框架在把action结果序列化为JSON返回给浏览器之前。一开始也看不出具体问题,后来同事在网上搜索看到说是 setter/getter 导致的,然后他的那个被抛出异常的方法也是以get开头命名的,坑就这么形成了:Struts2在序列化action的结果时,会把action的所有公开的getter方法的结果序列化到json里,那个方法以get开头,自然也就被调用了,然后异常就抛出来了。

这个问题自己写的时候一般都会注意避开,但不小心的人还是会踩上的。

ajax 提交 JS 数组对象与 Struts2 参数解析

场景:在页面输入一组用户信息,然后用jQuery的ajax提交给后台的Struts2 action,在JS里,这组信息是用数组来存储,数组的每个元素是一个JSON对象。

异常现象:在tomcat的控制台输出的异常信息是这样的:No result defined for action package4.business.action and result input

分析与解决:一开始我也以为是Java代码的问题,毕竟异常是在控制台里抛出的,但代码也看不出什么问题。这个请求的url在浏览器地址栏直接打开是没有问题的,所以我想,是不是跟请求方法有关,改为 GET 后还是出错。再想,地址栏打开是不带数据的,so在ajax里也不带数据,结果也没异常了,所以可以确定是请求参数的问题了。在chrome的开发工具那里看到,数组参数都被序列化为 persons[0][name]=name 的形式发出去了,这在js里是没问题的,访问对象的属性可以用句点(json.prop),也可以用方括号(json[prop]),参数被提交到Struts2时,它进行参数解析并映射到Java对象,它的规则却与这个有冲突了,对于句点,Struts2也是解析为对象的属性,但方括号却是被当作数组的,所以解析肯定失败,会抛出异常,捕获到参数解析异常后,请求会被路由到命名为 input 的结果那里,但我们这个项目都没配置 input,所以抛出的异常是找不到 result input


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。