用Java代码简单的通信过程

       TCP提供了一种面向连接的、可靠的字节流服务。面向连接比较好理解,就是连接双方在通信前需要预先建立一条连接,这犹如实际生活中的打电话。助于可靠性,TCP协议中涉及了诸多规则来保障通信链路的可靠性,总结起来,主要有以下几点:

      (1)应用数据分割成TCP认为最适合发送的数据块。这部分是通过“MSS”(最大数据包长度)选项来控制的,通常这种机制也被称为一种协商机制,MSS规定了TCP传往另一端的最大数据块的长度。值得注意的是,MSS只能出现在SYN报文段中,若一方不接收来自另一方的MSS值,则MSS就定为536字节。一般来讲,在不出现分段的情况下,MSS值还是越大越好,这样可以提高网络的利用率。

      (2)重传机制。设置定时器,等待确认包。

      (3)对首部和数据进行校验。

      (4)TCP对收到的数据进行排序,然后交给应用层。

      (5)TCP的接收端丢弃重复的数据。

      (6)TCP还提供流量控制。

TCP报文:

(1)TCP封装数据的格式:

(2)TCP首部的格式:

 TCP首部报文格式的说明:

     (1)每个TCP段都包括源端和目的端的端口号,用于寻找发送端和接收端的应用进程。这两个值加上IP首部的源端IP地址和目的端IP地址唯一确定一个TCP连接。
     (2)序号用来标识从TCP发送端向接收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。
     (3)当建立一个新连接时,SYN标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN,该主机要发送数据的第一个字节的序号为这个ISN1,因为SYN标志使用了一个序号。
     (4)既然每个被传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当时上次已成功收到数据字节序号加1。只有ACK标志为1时确认序号字段才有效。
     (5)发送ACK无需任何代价,因为32位的确认序号字段和ACK标志一样,总是TCP首部的一部分。因此一旦一个连接建立起来,这个字段总是被设置,ACK标志也总是被设置为1
     (6TCP为应用层提供全双工的服务。因此,连接的每一端必须保持每个方向上的传输数据序号。
     (7TCP可以表述为一个没有选择确认或否认的华东窗口协议。因此TCP首部中的确认序号表示发送方已成功收到字节,但还不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认。
     (8)首部长度需要设置,因为任选字段的长度是可变的。TCP首部最多60个字节。
     (96个标志位中的多个可同时设置为1
         URG
-紧急指针有效
         ACK-确认序号有效
         PSH-接收方应尽快将这个报文段交给应用层
         RST-重建连接
         SYN-同步序号用来发起一个连接
         FIN-发送端完成发送任务
     (10TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端期望接收的字节数。窗口大小是一个16为的字段,因而窗口大小最大为65535字节。
     (11)检验和覆盖整个TCP报文端:TCP首部和TCP数据。这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。TCP检验和的计算和UDP首部检验和的计算一样,也使用伪首部。
     (12)紧急指针是一个正的偏移量,黄蓉序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。
     (13)最常见的可选字段是最长报文大小MMS,每个连接方通常都在通信的第一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。

TCP三次握手与四次挥手全过程:

 

1、建立连接协议(三次握手)
    (1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
    (2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
    (3) 客户必须再次回应服务段一个ACK报文,这是报文段3。


TCP的三次握手可以通过报文来分析:

   (1)客户端向服务器端发起同步请求,服务器侧端口固定为 102,客户端端口由 socket 随机产生


     (2)服务器端向客户端响应,同时也向客户端发起同步请求

     (3)客户端予以确认

2 连接终止协议(四次挥手)

   由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
 (1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
 (2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
 (3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
 (4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

通过Java代码实现TCP连接三次握手和四次挥手的过程:


package com.zhaoming.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class ClientConnect extends Thread
{
    //定义一个Client的对象
    private Client client;
    
    private Socket socket;
    //定义IP和端口号是常量
    private static final String IP = "192.168.1.190";
    
    private static final int PORT = 20108;
  //声明str是一个静态变量,
    public static String str = null;
    //进行构造(持有另一个类对象的引用)    
    public ClientConnect(Client client)
    {
        this.client = client;
    }
    //启用线程,处理连接
    public void run()
    {
        try
        {
            //初始化要连接的socket套接字
            socket = new Socket(IP,PORT);
            client.getjButton1().setEnabled(false);
        }
        catch (UnknownHostException e1)
        {
            e1.printStackTrace();
        }
        catch (IOException e1)
        {
            e1.printStackTrace();
        }
        //从服务器端通过socket读取信息
        BufferedReader br = null;
        try
        {
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //把读到的信息显示到TextArea中
            while (true)
            {
                str = br.readLine();
                client.getjTextArea1().append(str + "\n");
                //建一个新的文本文档,用于存储从服务器读到的信息
                File file = new File("E:/temperatrue.txt");
                
                PrintWriter out = null;
                String date = null;
                //修改时间显示的格式
                SimpleDateFormat sdf = null;
                
                try
                {
                    //把读到的信息,写到到文本文件中存储起来
                    out = new PrintWriter(new FileOutputStream(file, true));
                    sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    date = sdf.format(new Date());
                    out.println(date+"\t"+str);
                    out.flush();
                }
                catch (FileNotFoundException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    out.close();    
                }
                }
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        //进行流关闭处理,先进行判断,然后再关闭
        finally
        {
            try
            {
                if(br != null)
                {
                    br.close();
                    br =null;
                }
                if(socket != null)
                {
                    socket.close();
                    socket = null;
                }
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
}
      由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
      TCP协议的连接是全双工连接,一个TCP连接存在双向的读写通道。 
简单说来是 “先关读,后关写”,一共需要四个阶段。以客户机发起关闭连接为例:
     1.服务器读通道关闭
     2.客户机写通道关闭
     3.客户机读通道关闭
     4.服务器写通道关闭
      关闭行为是在发起方数据发送完毕之后,给对方发出一个FIN(finish)数据段。直到接收到对方发送的FIN,且对方收到了接收确认ACK之后,双方的数据通信完全结束,过程中每次接收都需要返回确认数据段ACK。

 

 

 

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
下面是一个简单Java TCP通信示例代码: 服务端代码: ```java import java.io.*; import java.net.*; public class TCPServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(9000); } catch (IOException e) { System.err.println("Could not listen on port: 9000."); System.exit(1); } Socket clientSocket = null; try { System.out.println("Waiting for connection....."); clientSocket = serverSocket.accept(); System.out.println("Accepted connection from client: " + clientSocket.getInetAddress().getHostName()); } catch (IOException e) { System.err.println("Accept failed."); System.exit(1); } PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); String inputLine; while ((inputLine = in.readLine()) != null) { System.out.println("Server: " + inputLine); out.println(inputLine); if (inputLine.equals("Bye.")) { break; } } out.close(); in.close(); clientSocket.close(); serverSocket.close(); } } ``` 客户端代码: ```java import java.io.*; import java.net.*; public class TCPClient { public static void main(String[] args) throws IOException { Socket socket = null; PrintWriter out = null; BufferedReader in = null; try { socket = new Socket("localhost", 9000); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (UnknownHostException e) { System.err.println("Don't know about host: localhost."); System.exit(1); } catch (IOException e) { System.err.println("Couldn't get I/O for the connection to: localhost."); System.exit(1); } BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); String userInput; while ((userInput = stdIn.readLine()) != null) { out.println(userInput); String responseLine = in.readLine(); System.out.println("Client: " + responseLine); if (responseLine.equals("Bye.")) { break; } } out.close(); in.close(); stdIn.close(); socket.close(); } } ``` 在这个例子中,服务端监听9000端口,等待客户端连接。客户端连接上服务端后,它可以从标准输入读取消息,并将其发送到服务端。服务端接收到消息后,将其打印到控制台上,并将其发送回客户端。客户端接收到响应后,也将其打印到控制台上。这个过程将一直持续,直到客户端发送`Bye.`消息,服务端将关闭连接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值