javasocket底层是怎样基于tcp/ip实现的???

     2022-03-22     327

关键词:

首先必须明确: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(但是 使用上面的字符流传输文件时,当文件传送完毕时打开会报错!!!)

javasocket基于tcp/ip协议

Javasocket基于TCP/IP协议应用多线程服务器原理:1、         服务器端创建serversocket并绑定要监听的端口号,循环调用serversoket的accept()方法,等待客户端的连接请求2、       &... 查看详情

二十javasocket工作机制

主机A的应用程序要能够和主机B的应用程序通信,必须通过socket建立连接,而建立socket连接必须由底层TCP/IP来建立TCP连接。建立TCP连接需要底层IP来寻址网络中的主机。我们知道网络层使用的IP可以帮助我们根据IP地址来找到目... 查看详情

javasocket编程(li)

一、网络编程中两个主要的问题  一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定In... 查看详情

javasocket编程(代码片段)

Socket概念tcp协议全称是TransmissionControlProtocol,传输控制协议,是以字节流的方式发送数据的协议。ip全称为InternetProtocol互联网协议,tcp/ip协议在四层模型中的传输层。http、ftp、telnet(ssh)远程登录服务为应用层协议࿰... 查看详情

基于i/o的server/client实现

.../Client进行对照。网络编程中须要解决的两个主要问题:1、怎样准确的定位网络上的一台或多台主机。2、找到主机后怎样可靠高效的进行传输数据。而解决这两个问题的主要方式就是非常好的运用TCP/IP协议。所以我们所做的网络... 查看详情

javasocket编程

一、网络编程概述网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。java.net包中J2SE的API包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不... 查看详情

tcp/ip的底层队列是如何实现的?(代码片段)

...拥塞控制算法后,我越发想要更加深入的了解TCP/IP的一些底层原理,搜索了很多网络上的资料,收益颇多。今天就总结一下。我自己比较了解Java语言,对Java网络编程的理解就止于Netty框架的使用。Netty的源码贡献者NormanMaurer对于... 查看详情

javasocket编程(代码片段)

Socket概念tcp协议全称是TransmissionControlProtocol,传输控制协议,是以字节流的方式发送数据的协议。ip全称为InternetProtocol互联网协议,tcp/ip协议在四层模型中的传输层。http、ftp、telnet(ssh)远程登录服务为应用层协议࿰... 查看详情

javasocket编程学习笔记

在上一篇中,使用了javaSocket+Tcp/IP 协议来实现应用程序或客户端--服务器间的实时双向通信,本篇中,将使用UDP协议来实现Socket的通信。1.关于UDP  UDP协议(用户数据报协议)是无连接的、不可靠的、无序的,速度快,进行... 查看详情

golang-tcp/ip网络编程

...编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协议的. 比如: QQ 聊天 [示意图]  2)b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协... 查看详情

项目日志之基于javasocket的网络通讯

   JavaAPI网络类包中的Socket类是网络上运行的两个程序间双向通信的一端,它既可以接受请求,也可以发送请求,利用它可以较为方便的编写网络上数据的传递。我们打算通过Java中基于Socket的网络编程实现一个简单的... 查看详情

你只会用javasockets?推荐11个开源的javasocket框架

来源:csdn.net/xiaojin21cen/article/details/78587425ZeroCICE的Java版,Netty2作者的后续之作ApacheMINA,Crmky的Cindy之外,还有个超简单的QuickServer,让你专心编写自己的业务代码,不用编写一行TCP代码。1、QuickServer一个免... 查看详情

java的linkedhashset是怎样实现存取有序的,底层原理是啥

...,它继承与HashSet、又基于LinkedHashMap来实现的。LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同,因此LinkedHashSet的实现上非常简单,只提供了四个构造方法,并通过传递一个标... 查看详情

浅谈http中get和post请求方式的区别

...TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET和POST本质上就是TCP链接,并无差别。HTTP只是个行为准则,而TCP才是GET和POST怎么实现的基本。HTTP定义了与服务器交... 查看详情

tcp/ip协议分层模型

...数据包  通过IP,相互通信的主机之间不论经过怎样的底层链路都能实现通信  分组交换  查看详情

javasocket

转载自并发编程网–ifeve.com本文链接地址:Java网络教程之SocketJava网络教程-基础 Java提供了非常易用的网络API,调用这些API我们可以很方便的通过建立TCP/IP或UDP套接字,在网络之间进行相互通信,其中TCP要比UDP更加常用,但在本教... 查看详情

get,post传值总结

...上两者没有任何区别。他们都是HTTP协议中的请求方法。底层实现都是基于TCP/IP协议。上述的所谓区别,只是浏览器厂家根据约定,做得限制而已。HTTP请求,最初设定了八种方法。这八种方法本质上没有任何区别。只是让请求,... 查看详情

以太网在tcp/ip网络模型的哪一层工作?

...太网的方式联网,运行的协议在数据链路层(TCP/IP协议的底层协议)TCP/IP(TransmissionControlProtocol/InternetProtocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个... 查看详情