java socket原理

TCP / IP模型中有5层结构:应用层、传输层、网络层、数据链路层以及物理层。其中IP协议是位于网络层的,TCP协议是位于传输层的,通过IP协议可以使两台计算机使用同一种语言,从而允许Internet上连接不同类型的计算机和不同的操作系统的网络,IP协议只能保证计算机能够接收和发送分组数据,当计算机要和远程的计算机建立连接时,TCP协议会让它们建立连接:用于发送和接收数据的虚拟电路。

在Java中,我们使用Socket、ServerSocket类创建一个套接字连接,从套接字得到的结果就是一个InputStream以及OutputStream对象,以便将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中。

套接字是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。Java有两个基于数据流的套接字类:ServerSocket,服务器用它监听进入的连接,Socket,客户端用它初始一次连接。监听套接字只能接收新的连接请求,不能接收实际的数据包,即ServerSocket不能接收实际的数据包。

套接字是基于TCP / IP实现的,它是用来提供一个访问TCP的服务接口,即Socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。

Socket简介

Socket字面意思就是套接字,包含了源IP地址、源端口、目的IP地址和目的端口。

Socket是应用层与TCP / IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP / IP协议族隐藏在Socket接口后面。

Socket底层数据结构

对于套接字结构,是指底层实现(包括JVM和TCP / IP,但通常是后者)的数据结构集合,包含了特定Socket所关联的信息。套接字结构包含:

  • 该套接字所关联的本地和远程互联网地址和端口
  • 一个FIFO队列用于存放接收到的等待分配的数据(RecvQ),以及一个用于存放等待传输数据的队列(SendQ)
  • 打开和关闭TCP握手相关的额外协议状态信息
image.png

由于TCP提供了一种可信赖的字节流服务,任何写入Socket和OutputStream的数据副本都没有保留,直到连接的另一端将这些数据成功接收,向输出流写数据并不意味着数据实际上已经被发送—,它们只是被复制到本地缓冲区,就算在Socket的OutputStream上进行flush操作,也不能保证数据能够立即发送到信道。此外,字节流服务的自身属性决定了其无法保留输入流中消息的边界消息

Socket数据传输的底层实现

一旦创建了一个套接字实例,操作系统就会为其分配缓冲区以存放接收和要发送的数据。

Java可以设置读写缓冲区的大小:

setReceiveBufferSize(int size)
setSendBufferSize(int size

向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲队列SendQ,就是在Socket的OutputStream上调用flush方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。当有数据从网络来到时,TCP协议栈模块接收数据并放入缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。

Socket超时

Scoke连接建立超时

Socket连接建立是基于TCP的连接建立过程。TCP的连接需要通过3次握手报文来完成,开始建立TCP连接时需要发送同步SYN报文,然后等待确认报文SYN + ACK,最后再发送确认报文ACK。TCP连接的关闭通过4次挥手来完成,主动关闭TCP连接的一方发送FIN报文,等待对方的确认报文,被动关闭的一方也发送FIN报文,然后等待确认报文。

正在等待TCP连接请求的一端有一个固定长度的连接队列,该队列中的连接已经被TCP接受(即三次握手已经完成),但还没有被应用层所接受。TCP接受一个连接是将其放入这个连接队列,而应用层接受连接是将其从该队列中移出。应用层可以通过设置backlog变量来指明该连接队列的最大长度,即已被TCP接受而等待应用层接受的最大连接数。

当一个连接请求SYN到达时,TCP确定是否接受这个连接。如果队列中还有空间,TCP模块将对SYN进行确认并完成连接的建立,但应用层只有在三次握手中的第三个报文收到后才会知道这个新连接,如果队列没有空间,TCP将不理会收到的SYN。如果应用层不能及时接受已被TCP接受的连接,这些连接可能会占满整个连接队列,新的连接请求可能不会响应而会超时。如果一个连接请求SYN发送后,一段时间后没有收到确认SYN + ACK,TCP会重传这个连接请求SYN两次,每次重传的时间间隔加倍,在规定的时间内仍然没有收到SYN + ACK,TCP将放弃这个连接请求,连接请求就超时了

Java Socket连接建立超时和TCP是相同的,如果TCP建立连接时三次握手超时,那么导致Socket连接建立也就超时了,可以设置Socket连接建立的超时时间:

connect(SocketAddress endpoint,int timeout)

如果在timeout内,连接没有建立成功,TimeoutExpection异常被抛出,如果timeout的值小于三次握手的时间,那么Socket连接永远不会建立。

不同应用层有不同的连接建立过程,Socket的连接建立和TCP一样,仅仅需要三次握手就完成连接,但有些应用程序需要交互很多信息后才能成功建立连接,比如Telnet协议,在TCP三次握手完成后,需要进行选项协商之后,Telnet连接才建立完成。

2、Socket读超时

如果输入缓冲队列RecvQ中没有数据,read操作会一直阻塞而挂起线程,直到有新的数据到来或者有异常产生,调用setSoTimeout(int timeout)可以设置超时时间,如果到了超时时间仍没有数据,read会抛出一个SocketTimeoutExpection,程序需要捕获这个异常,但是当前的Socket连接仍然是有效的。

如果对方进程奔溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去,这是设置超时时间是非常重要的,否则调用read的线程会一直挂起。

TCP模块把接受到的数据放入RecvQ中,直到应用层调用输入流的read方法来读取。如果RecvQ队列被填满了,这时TCP会根据滑动窗口机制通知对方不要继续发送数据,本端停止接收从对端发送来的数据,直到接受者应用程序调用输入流的read方法后腾出空间。

3、Socket写超时

Socket的写超时是基于TCP的超时重传。超时重传是TCP保证数据可靠性传输的一个重要机制,其原理是在发送一个数据报文后就开启一个计时器,在一定时间内如果没有得到发送报文的确认ACK,那么就重新发送报文。如果重新发送多次之后,仍没有确认报文,就发送一个复位报文RST,然后关闭TCP连接。首次数据报文发送与复位报文传输之间的时间差大约为9分钟,也就是说如果9分钟内没有得到确认报文,就关闭连接,但是这个值是根据不同的TCP协议栈实现而不同

如果发送端调用write持续地写出数据,直到SendQ队列被填满。如果在SendQ队列已满时调用write方法,则write将被阻塞,直到SendQ有新的空闲空间为止,也就是说直到一些字节传输到了接受者套接字的RecvQ中,如果此时RecvQ队列也已经被填满,所有操作都将被停止,直到接收端调用read方法将一些字节传输到应用程序。

Socket写超时是基于TCP协议栈的超时重传机制,一般不需要设置write的超时时间,也没有提供这种方法。

关闭服务端连接

    在客户端和服务端的数据交互完成后,一般需要关闭网络连接。对于服务端来说,需要关闭Socket和ServerSocket。

在关闭Socket后,客户端并不会马上感知自已的Socket已经关闭,也就是说,在服务端的Socket关闭后,客户端的Socket的isClosed和isConnected方法仍然会分别得到false和true。但对已关闭的Socket的输入输出流进行操作会抛出一个SocketException异常。下面的代码演示了在服务端关闭Socket后,客户端是所何反应的。

package server;
 
import java.net.*;
 
class Client
{
    public static void main(String[] args) throws Exception
    {
        Socket socket = new Socket("127.0.0.1", 1234);
        Thread.sleep(1000);
        // socket.getOutputStream().write(1);
        System.out.println("read() = " + socket.getInputStream().read());
        System.out.println("isConnected() = " + socket.isConnected());
        System.out.println("isClosed() = " + socket.isClosed());
    }
}
public class CloseSocket
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket = new ServerSocket(1234);
        while (true)
        {
            Socket socket = serverSocket.accept();
            socket.close();           
        }
    }
}
read() = -1
isConnected() = true
isClosed() = false

从上面的运行结果可以看出例程Client并未抛出SocketException异常。而在012行的read方法返回了-1。如果将socket.close去掉,客户端的read方法将处于阻塞状态。这是因为Java在发现无法从服务端的Socket得到数据后,就通过read方法返回了-1。如果将011行的注释去掉,Client就会抛出一个SocketException异常。在write时候并不会抛异常, write再去read就会抛异常,因为服务端进程不会向客户端发送ACK 报文,而是发送了一个RST 报文请求将处于异常状态的连接复位。客户端接收到了这个抛异常,

RST (Reset)

TCP连接的断开有两种方式:

  • 连接正常关闭时双方会发送FIN,经历4次挥手过程;
  • 通过RST包异常退出,此时会丢弃缓冲区内的数据,也不会对RST响应ACK。

java中,调用Socket#close()可以关闭Socket,该方法类似Unix网络编程中的close方法,将Socket的 读写 都关闭,已经排队等待发送的数据会被尝试发送,最后(默认)发送FIN。考虑一个典型的网络事务,A向B发送数据,A发送完毕后close(),FIN发送出去;B一直read直到返回了-1,也通过close()发送FIN,4次挥手,连接关闭,一切都很和谐。

那什么时候会用RST而非FIN关闭连接呢?

Socket#setSoLinger(true,0),则close时会发送RST

如果主动关闭方缓冲区还有数据没有被应用层消费掉,close会发送RST并忽略这些数据

A向B发送数据,B已经通过close()方法关闭了Socket,虽然TCP规定半关闭状态下B仍然可以接收数据,但close动作关闭了该socket上的任何数据操作,如果此时A继续write,B将返回RST,A的该次write无法立即通知应用层(因为write仅把数据写入发送缓冲区),只会把状态保存在tcp协议栈内,下次read时才会抛出SocketException。

2. 对已关闭socket读写会产生的异常

2.1 主动关闭方

close()后,无论是发送FIN/RST关闭的,之后再读写均会抛java.net.SocketException:socket is closed.

2.2 被动关闭方

被FIN关闭

  • 写(即向”已被对方关闭的Socket”写)
    如上所说,第一次write得到RST响应但不抛异常,第二次write抛异常,ubuntu下是broken pipe (断开的管道),win7下是Software caused connection abort: socket write error

  • 读 – 始终返回 -1

被RST关闭
读写都会抛出异常:connection reset (by peer)

重点在于:

  • connection reset:另一端用RST主动关闭连接
  • broken pipe / Software caused connection abort: socket write error : 对方已调用Socket#close()关闭连接,己方还在写数据
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 运输层协议概述 从通信和信息处理的角度看,运输层向它上面的应用层提供通信服务,它属于面向通信部分的最高层,同时也是...
    srtianxia阅读 2,324评论 0 2
  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 9,196评论 0 11
  • 目录: TCP是什么TCP报文结构TCP连接过程TCP状态转移TCP流量控制 —— 滑动窗口TCP拥塞控制TCP可...
    Katou_Megumi阅读 1,291评论 0 4
  • 安装eclipse配置环境:1.D:\adt-bundle-windows-x64\android-sdk-win...
    小妮詪拽阅读 83评论 0 0
  • 很多人以為「自由」就是隨心所欲、為所欲為,自己想幹嘛就幹嘛!這看似不錯,但細究,如果每個人都以自我、私慾為標準...
    郭長介阅读 241评论 0 0