本文讲解传统Socket,而java中新的NIO和这里讲的稍微有所区别。以ppt的形式展现。
第一步:建立连接
• 我们做的事情:
Socket socket = new Socket("10.255.209.45", 1111)
• 操作系统做的事情:
三次握手
Socket建立连接时发生了什么
第一步:建立连接时的seq
• 建立连接时生成的seq可以是随机的,也可以是动态增长并循环的
• 双方的seq不相关
• 自己发送syn、ack时seq会加1,对方回复ack的时候回带上原来的seq以便区分ack哪个包
• 发送普通数据包,seq增加数据包字节数,接收端据此将数据放入缓冲区不同offset处
• 只ack最大的连续收到的包,例如收到1/2/5,回复ack 2即可
对端不存在会怎样?
– Socket socket = new Socket(“不存在的ip",1111)
– java.net.ConnectException:Connection timed out
对端端口没有应用在监听会怎样?
– Socket socket = new Socket(“正确的ip",1111)
– java.net.ConnectException:Connection refused
• 连接超时时间配置
– Socket socket = new Socket();
– InetSocketAddress addr = new InetSocketAddress("10.255.209.45",111);
– socket.connect(addr);
– socket.connect(addr, 0);
– 二者效果等同,都是由OS的参数决定等待多久没连接上就算超时
• 从以上俩图可以看出,没有设置连接超时时间时使用SO_TIMEOUT参数
• SO_TIMEOUT也可能会为0,此时取决于操作系统的实现,但也不会无限等待,最多2MSL,或路由等网络设备返回目标不可达,或对端返回RST连接重置标志
• 2MSL在Linux 2.6中固定为60s
• 连接超时时间配置
– Socket socket = new Socket();
– InetSocketAddress addr = new InetSocketAddress("10.255.209.45",111);
– socket.connect(addr, 10 * 1000); // 10s
• 超时时间为0,使用阻塞模式,OS返回连接结果状态
• 超时时间大于0,使用非阻塞模式,OS返回后在jvm中等待超时
从下图可看出,连接超时时间大于0的时候,会首先设置连接为非阻塞模式,并等待特定时间,直至连接成功或超时;最后再将连接设置为非阻塞模式。
连接超时和so_timeout的区别
so_timeout干嘛的
• socket. getInputStream
– Return new SocketInputStream(this);
• socket.setOption
– switch (opt) {
– case SO_TIMEOUT:
– timeout = tmp; break;
• socketInputStream.read
– return read(b, off, length, impl.getTimeout());
• so_timeout: OS等待直到输入缓冲区有数据
连接超时、so_timeout二者区别
• 连接超时时间限制三次握手建立连接的总时间(发送sync,收到ack的时间之和),不包括三次握手的第三步。
• 后者限制对方何时必须完成业务层处理,返回结果给发送方。
为什么先建立连接后传输数据?
• 协商最大包大小:双方缓冲区大小、中间路由设备会影响此值
• 协商滑动窗口大小、对方是否支持特定功能、计算Round trip time等
• 确定一个序号,后续发送数据包时顺序递增,以便进行丢包重传,在接收端重组接收到的乱序的报文
• 确认双向链路均正常(防火墙可能限制)
• 等等
为什么需要关闭连接?
• 释放双方占用的内存、端口号等资源
• 避免上一个连接的数据包被下一个连接收到
关闭过程:
同时关闭:
close_wait怎么解决
• 原因:对端关闭连接后,自己没有关闭连接,导致自己这边的状态为close_wait
• 很少出现
• 查代码去
主动发起关闭的一方出现FIN_WAIT_2 、TIME_WAIT
• 主动调用close的一方会出现time_wait
– Socket socket = new Socket();
socket.close();
– Socket socket =serverSocket.accept();
socket.close();
• 主动A调用close的一方进入FIN_WAIT_2状态,对方B也调用close后,A进入TIME_WAIT状态,等待2MSL防止对方没收到最后的ack
• 2MSL在Linux 2.6中固定为60s
解决FIN_WAIT_2
• 出现FIN_WAIT_2原因:对方没有调用close
• 办法:
– 1、检查对方代码;
– 2、修改OS的参数“net.ipv4.tcp_fin_timeout”,Linux默认60秒,超时后强制关闭,转换到CLOSED
解决TIME_WAIT办法1:暴力关闭
• SO_LINGER选项:
– 设置为true,并在应用代码中自定义超时时间,达到超时时间或收到对方回复后强制关闭连接,且给对方发送RST信息。设置为0立即强制关闭。默认false,调用close时立即返回,进入FIN_WAIT_1
解决TIME_WAIT办法2:调整OS参数
• 用OS参数解决:
– net.ipv4.ip_local_port_range:增加可用端口范围
• 客户端有效
• 服务器(ServerSocket)端TIME_WAIT不会占用端口
– net.ipv4.tcp_max_tw_buckets:超过此数的TIME_WAIT端口立即被清除,之后可使用
– net.ipv4.tcp_tw_recycle:2RTT内回收端口
– net.ipv4.tcp_tw_reuse:允许重用TIME_WAIT端口
– sysctl –wnet.netfilter.nf_conntrack_tcp_timeout_time_wait=1
– net.inet.tcp.msl控制time_wait时长(FreeBSD)
– Windows可在注册表修改参数
解决TIME_WAIT办法3:迂回
• 出现TIME_WAIT原因:防止对方没收到最后的ack
• 解决办法:
– 1、使用长连接,socket不调用close,所以就不会产生TIME_WAIT,而是一直处于ESTABLISHED
– 2、自己不主动close,让对方close
– 3、将必须使用大量短连接的应用分拆,部署到不同的物理机或虚拟机
其他socket选项(在代码中使用的叫做选项)
• TCP_NODELAY:
– 默认false,如果每次向输入缓冲区写入数据较少,OS会等达到特定大小,或超过一定时间后再发送这些数据;
– 设置为true,则不论写入多少数据都一次发送出去
• TCP_CORK:暂停TCP_NODELAY的作用最多200ms
• TCP_LINGER2:
– 作用同tcp_fin_timeout,由应用设置而不是OS参数
• 提示:NIO中的SocketChannel也可以和Socket一样设置选项
其他socket参数(在OS调整的叫做参数)
• tcp_abort_on_overflow:OS处理不了就抛弃连接请求,避免被DOS攻击(默认为disabled)
• tcp_max_syn_backlog:收到syn回复ack后放入syn队列,表示半连接,超过此值的syn被丢弃;或表示OS已建立连接但应用没有accept的最大连接数(默认值有固定算法)
• tcp_syn_retries:发送多少次syn都没有收到ack后认定为无法连接
• tcp_synack_retries:收到syn后回复ack多少次后再收到syn则丢弃
• net.core.somaxconn:最大连接数
其他资料
• man手册
– man sendfile:zero-copy技术
– man tcp:
• tcp讲解,找到man手册的SEE ALSO部分,有相关的命令;
• 继续用man命令查看,以此类推,可熟悉tcp相关的大多数知识
– man udp/man ip/man socket/man route