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

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

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

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

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

套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的。下面先讨论套接字读写缓冲区,接着讨论连接建立超时、读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示例。

1 socket读写缓冲区

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

JAVA可以设置读写缓冲区的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。

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

当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。

2 socket连接建立超时

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内,连接没有建立成功,在TimeoutException异常被抛出。如果timeout的值小于三次握手的时间,那么Socket连接永远也不会建立。

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

3 socket读超时

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

如果对方进程崩溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去(由前面可知:双方要关闭连接需要四次挥手 .对方机重启或断开只是对方机的TCP连接关闭,本端的TCP连接还没关闭,所以本端机会一直阻塞),这时设置超时时间是非常重要的,否则调用read的线程会一直挂起。

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

4 socket写超时

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

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

当Socket的write发送数据时,如果网线断开、对端进程崩溃或者对端机器重启动,(由前面可知:双方要关闭连接需要四次挥手 .对端进程崩溃或者对端机器重启动只是对方机的TCP连接关闭,本端的TCP连接还没关闭,所以本端机会一直阻塞)TCP模块会重传数据,最后超时而关闭连接。下次如再调用write会导致一个异常而退出。

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

5 双重嵌套异常捕获

如果ServerSocket、Socket构造失败,只需要仅仅捕获这个构造失败异常而不需要调用套接字的close方法来释放资源(必须保证构造失败 后不会留下任何需要清除的资源),因为这时套接字内部资源没有被成功分配。如果构造成功,必须进入一个try finally语句块里调用close释放套接字。请参照下面例子程序。

import java.net.*;
import java.io.*;
public class SocketClientTest
{
  public static final int PORT = 8088;
  public static void main( String[] args ) throws Exception
  {
    InetAddress addr = InetAddress.getByName( "127.0.0.1" );
    Socket socket = new Socket();
    try
    {
      socket.connect( new InetSocketAddress( addr, PORT ), 30000 );
      socket.setSendBufferSize(100);
     
      BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
      int i = 0;
     
      while( true )
      {
        System.out.println( "client sent --- hello *** " + i++ );
        out.write( "client sent --- hello *** " + i );
        out.flush();
       
        Thread.sleep( 1000 );
      }
    }
    finally
    {
      socket.close();
    }
  }
}

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerTest
{
  public static final int PORT = 8088;
  public static final int BACKLOG = 2;
  public static void main( String[] args ) throws IOException
  {
    ServerSocket server = new ServerSocket( PORT, BACKLOG );
    System.out.println("started: " + server);
    try
    {
      Socket socket = server.accept();
      try
      {
        BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
        String info = null;
       
        while( ( info = in.readLine() ) != null )
        {
          System.out.println( info );
        }
      }
      finally
      {
        socket.close();
      }
    }
    finally
    {
      server.close();
    }
  }
}

执行上面的程序,在程序运行一会儿之后,断开client和server之间的网络连接,在机器上输出如下:

Server上的输出:

Echoing:client sent -----hello0

Echoing:client sent -----hello1

Echoing:client sent -----hello2

Echoing:client sent -----hello3

Echoing:client sent -----hello4

Echoing:client sent -----hello5

Echoing:client sent -----hello6

---->> 断开了网络连接之后没有数据输出

Client上的输出:

socket default timeout = 0

socket = Socket[addr=/10.15.9.99,port=8088,localport=4691]

begin to read

client sent --- hello *** 0

client sent --- hello *** 1

client sent --- hello *** 2

client sent --- hello *** 3

client sent --- hello *** 4

client sent --- hello *** 5

client sent --- hello *** 6

client sent --- hello *** 7

client sent --- hello *** 8

client sent --- hello *** 9

client sent --- hello *** 10

---->> 断开网络连接后客户端进程挂起

java.net.SocketException : Connection reset by peer: socket write error

at java.net.SocketOutputStream.socketWrite0( Native Method )

at java.net.SocketOutputStream.socketWrite( SocketOutputStream.java:92 )

at java.net.SocketOutputStream.write( SocketOutputStream.java:136 )

at sun.nio.cs.StreamEncoder.writeBytes( StreamEncoder.java:202 )

at sun.nio.cs.StreamEncoder.implFlushBuffer( StreamEncoder.java:272 )

at sun.nio.cs.StreamEncoder.implFlush( StreamEncoder.java:276 )

at sun.nio.cs.StreamEncoder.flush( StreamEncoder.java:122 )

at java.io.OutputStreamWriter.flush( OutputStreamWriter.java:212 )

at java.io.BufferedWriter.flush( BufferedWriter.java:236 )

at com.xtera.view.SocketClientTest.main( SocketClientTest.java:99 )

当hello6被发送到server端后,网络连接被断开,这时server端不能接收任何数据而挂起。client端仍然继续发送数据,实际上 hello7、hello8、hello9、hello10都被复制到SendQ队列中,write方法立即返回。当client的SendQ队列被填满 之后,write方法就被阻塞。TCP模块在发送报文hello7之后,没有收到确认而超时重传,再重传几次之后关闭了TCP连接,同时导致被阻塞的 write方法异常返回。

通过抓包工具,我们可以看到超时重传的报文。

下面是规范代码实例:(服务端和客户端实现双向(可通过键盘输入)交互通信)

public class JabberClient {

public static void main(String[] args)
          throws IOException {
        // Passing null to getByName() produces the
        // special "Local Loopback" IP address, for
        // testing on one machine w/o a network:
        InetAddress addr =
          InetAddress.getByName("127.0.0.1");
        // Alternatively, you can use
        // the address or name:
        // InetAddress addr =
        //    InetAddress.getByName("127.0.0.1");
        // InetAddress addr =
        //    InetAddress.getByName("localhost");
        System.out.println("addr = " + addr);
        Socket socket =
          new Socket(addr, JabberServer.PORT);
        // Guard everything in a try-finally to make
        // sure that the socket is closed:
        try {
          System.out.println("socket = " + socket);
          BufferedReader KeyIn = new BufferedReader(new InputStreamReader(System.in));
          BufferedReader in =
            new BufferedReader(
              new InputStreamReader(
                socket.getInputStream()));
          // Output is automatically flushed
          // by PrintWriter:
          PrintWriter out =
            new PrintWriter(
              new BufferedWriter(
                new OutputStreamWriter(
                  socket.getOutputStream())),true);
//          for(int i = 0; i < 10; i ++) {
//            out.println("howdy " + i);
//            String str = in.readLine();
//            System.out.println(str);
//          }
//          out.println("END");
          String str =null;
          while(true)
          {
              str = KeyIn.readLine();
              if("END".equals(str))
                  break;
              out.println(str);
              System.out.println("Server:"+in.readLine());
             
             
          }
        } finally {
          System.out.println("closing...");
          socket.close();
        }
      }
    } ///:~

public class JabberServer { 
      // Choose a port outside of the range 1-1024:
      public static final int PORT = 8088;
      public static void main(String[] args)
          throws IOException {
        ServerSocket s = new ServerSocket(PORT);
        System.out.println("Started: " + s);
        try {
          Socket socket = s.accept();
          try {
            System.out.println(
              "Connection accepted: "+ socket);
            BufferedReader KeyIn = new BufferedReader(new InputStreamReader(System.in));
           
            BufferedReader in =
              new BufferedReader(
                new InputStreamReader(
                  socket.getInputStream()));
            // Output is automatically flushed
            // by PrintWriter:
            PrintWriter out =
              new PrintWriter(
                new BufferedWriter(
                  new OutputStreamWriter(
                    socket.getOutputStream())),true);
            while (true) { 
              String str = in.readLine();
              if (str.equals("END")) break;
              System.out.println("Client: " + str);
              out.println(KeyIn.readLine());
             
            }
          // Always close the two sockets...
          } finally {
            System.out.println("closing...");
            socket.close();
          }
        } finally {
          s.close();
        }
      }
    } ///:~

注意:一般在传输字符信息(例如txt文件,聊天信息)使用bufferedRead,printWrite  ,但是在传送一些文件时一定要使用字节输入输出流socket.getInputStream   和scoket.getOutputStream(但是 使用上面的字符流传输文件时,当文件传送完毕时打开会报错!!!)

转载于:https://www.cnblogs.com/kabi/p/5182798.html

JAVA Socket 底层是怎样基于TCP/IP 实现的???相关推荐

  1. 基于TCP/IP协议的Java服务端与Android客户端的Socket通信及数据交互

    基于TCP/IP协议的Java服务端与Android客户端的Socket通信及数据交互 一.前言 1.Java服务端程序代码的项目名为TcpSocketServerOfJava,包名为com.exam ...

  2. java: java mina ——基于TCP/IP、UDP/IP协议栈的通信框架

    Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务.虚拟机管道通信服务等),M ...

  3. Linux系统下实现基于TCP/IP协议的简单Socket通信

    Linux系统下实现基于TCP/IP协议的简单Socket通信 网络套接字Socket Socket概念 主机字节序和网络字节序 Sockaddr地址结构 Socket实现客户端.服务端通信 服务端程 ...

  4. 【网络编程】基于TCP/IP协议的C/S模型

    相关视频--C3程序猿-windows网络编程:第一部分tcp/ip 我的小站--半生瓜のblog 基于TCP/IP协议的C/S模型 基于TCP/IP协议的C/S模型 TCP/IP协议 Client/ ...

  5. 基于TCP/IP协议的物联网安卓应用开发基础教程(Android Studio开发)

    [华为云IOT物联网论坛外部版主:中华小能能] 第0章 简介 大家好,在上上上期发布的教程中,教大家如何利用Android Studio开发出一款接入华为云物联网平台并调用相关API完成设备属性查询. ...

  6. Linux网络编程——基于tcp/ip的模拟聊天(文件传输)工具

    开发平台:Linux 开发工具:Ubuntu, sourceInsight4.0 项目介绍: 本项目基于TCP/IP协议创建一个网络通信系统,可以实现客户之间的聊天通信以及文件传输,同时利用进程实现多 ...

  7. 【HoloLens2学习】基于TCP/IP的无人机操控飞行

    在YouTube上看到有人做了基于UDP的操控,就尝试了下,使用网上现有的代码进行了修改和尝试,下面是示意图,视频链接hololens2直升机飞行控制测试02. 基于TCP/IP的服务端-客户端代码借 ...

  8. TCPIP技术实验大作业:基于TCP/IP的程序开发技术综述及应用实践

    一.基于TCP/IP的程序开发技术综述 1.1TCP/IP协议族简介 TCP/IP也被称作传输控制协议/网际协议,作为网络互连的核心协议,受到广泛的应用.该协议类型作为开放性的标准应用在各种计算机中, ...

  9. 基于TCP/IP协议的网络摄像头的QT项目

    目录 项目简述: 1.服务器 步骤一:首先搭建一个基本的服务器框架. 1.初始化服务器的函数主体 2.等待连接 步骤二:数据库的使用,本次项目使用的Sqlite3数据库 1.数据库初始化 2.登录时使 ...

最新文章

  1. 基于Spring Boot+Cloud构建微云架构
  2. (0056)iOS开发之深拷贝与浅拷贝
  3. MySQL如何存储Emoji表情,UTF-8和UTF-8MB4字符编码有何区别
  4. 一颗接一颗的飞鸽传书
  5. 95-864-040-源码-HDFS-Flink 和 HDFS 的交互
  6. 初识Firebug 全文 — firebug的使用
  7. 9.包装类的构造方法
  8. 安装新版xampp后apache无法启动提示:Apache Service detected with wrong path解决方案
  9. 颠覆你的思维,探寻人类发展史
  10. 输入输出练习 python
  11. 研究生自然辩证法试题题库及答案
  12. PERMISSION_UNFINISHED
  13. Social Radio:像收听广播一样收听Twitter状态更新
  14. html里怎样返回首页,返回上一页-html页面返回上一页面怎么写
  15. 天嵌E9开发板tftp烧录eMMC教程(Android)
  16. 【Vissim】做一次完整的交通仿真——1.基本认识
  17. 什么是SSL协议 SSL协议的实现方式
  18. Bitwig Studio 4.0.1 x64 Windows+Linux 音乐制作宿主软件
  19. 中国制霸生成器火了/ 马斯克香水被炒至原价10倍/ 闽南话翻英语算法来了… 今日更多新鲜事在此...
  20. 豆瓣爬虫实战——Angelababy到底是什么风评

热门文章

  1. Scala特质可以继承Java类及限制特质的使用
  2. Dubbo Admin服务测试功能
  3. java cxf 搭soa,WebService CXF入门问题 SOA 骑着上帝去环游 - 贪吃蛇学院-专业IT技术平台...
  4. 文本分类数据集_181个NLP教程,481个文本数据集,Colab一键直达,无需环境配置...
  5. 2015国产手机圈成绩单
  6. 硬件工程师面试经历2015---笔试篇
  7. 在linuxc中如何实现输入隐藏
  8. 在MATLAB function中可变的变量数据类型
  9. messenger android 4.,AndroidIPC机制(4)-Messenger
  10. 空调c语言入门自学视频教程,本人大一,自学c语言,想问一下用哪个编程环境比较好啊?...