前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >java开发TCPIP协议:实现TCP单向裸数据发送

java开发TCPIP协议:实现TCP单向裸数据发送

作者头像
望月从良
发布于 2019-11-09 11:34:03
发布于 2019-11-09 11:34:03
1.1K00
代码可运行
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼
运行总次数:0
代码可运行

前面我们花费大量篇幅完成了TCP协议各种细节的讲解,从中我们能体会到该协议的复杂和烦琐。因此要想从零实现工业级鲁棒性的TCP数据传输协议几乎不可能,因此我们在实现过程中必须尽可能简化,就像老子说的天下难事必作于易,天下大事必作于细,因此我们在实现这么复杂的协议系统时必须从最简单的情况入手,然后一点点增加其骨架和血肉的丰富性。

本节我们先实现一次单向数据传输,我们将开放一个基于TCP协议的客户端,使用TCP协议向服务器发送裸数据,它的基本结构如下图:

该流程图是我们开发Client,让它与服务器进行三次握手连接后,Client向Server发送一个字符’t’,然后向服务器断开连接的过程。在完成这些代码的过程我体会到”知道那么多道理还是过不好这一生“这个大道理,虽然在前面章节我们言之凿凿的讲了很多TCP协议原理,到我真正实现起来时也得不断发蒙,调试了好久才能通过。

首先最容易发蒙的是seq和ack值的变化。通常情况下,当你发送的数据长度为len,如果发出去的数据包含有对应的seq值,如果对方收到数据后它返回的数据包对应ack就应该是发送方的seq+len,但有例外的情况,就像上图中握手和断开过程,如果对方发来的数据包中包含SYN,FIN等标志位,那么回复对方时ack的值必须是对方的seq加上对方数据长度,然后再加上1,这也就是为何在三次握手时,第一次我们给对方发送SYN包,seq设置为0,同时数据包没有任何数据,对方回复时却将ack设置为1的原因,然后对方发来的ACK+FIN时,它的seq为0,我们回复时必须将ack设置为1,这个现象在断开连接,也就是双方互发包含FIN的数据包时也是一样,这一点让我调试了好久才搞明白。

接下来我们进入实现阶段,首先我们需要在另一台设备上安装TCP服务器,只有服务器和客户端不在同一台设备上时我们才好使用wireshark抓包。通常情况下我们可以在手机上下载一个最简单的tcp服务器,我用的是iPhone,在AppStore里用tcp server为关键字就可以搜索到下面应用程序,设置好端口后就可以变成一个最简单的tcp服务器:

只要你能在另一台设备上运行可以接收TCP数据包的服务器程序都可以,我们的任务是在我们自己对TCP协议的实现基础上开发客户端,与它连接后,向它发送一个字符,然后端口连接,我们看看代码的实现。

在代码设计上我使用观察者模式,上层应用向TCP协议层调用相关接口后,TCP协议层以回调的方式通知上层应用,因此我们先添加接口定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package utils;

public interface ITCPHandler {
    public  void  connect_notify(boolean connect_res);  //返回tcp连接结果
    public  void  send_notify(boolean send_res, int packet_handler); //通知数据包发送结果
    public  void  connect_close_notify(boolean close_res); //返回连接关闭通知
}

任何想要调用TCP协议层的应用都必须继承该接口,我们看看客户端的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package Application;

import java.net.InetAddress;

import utils.ITCPHandler;

public class TCPRawDataSender implements ITCPHandler{
    private  TCPThreeHandShakes  tcp_socket = null;
    @Override
    public void connect_notify(boolean connect_res) {
        if (connect_res) {
            System.out.println("connection established!");
            try {
                tcp_socket.tcp_send("t".getBytes());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        else {
            System.out.println("connection fail!");
        }
    }

    @Override
    public void send_notify(boolean send_res, int packet_handler) {
        if (send_res == true) {
            System.out.println("send data ok!");
        }

        try {
            tcp_socket.tcp_close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void connect_close_notify(boolean close_res) {
        if (close_res == true) {
            System.out.println("connection close complete!");
        } else {
            System.out.println("connection close fail!");
        }
    }

    public void run() {
         try {
            InetAddress ip = InetAddress.getByName("192.168.2.127"); //220.181.43.8
            short port = 1234;
            tcp_socket = new TCPThreeHandShakes(ip.getAddress(), port, this);
            tcp_socket.tcp_connect();
            System.out.println("finish handshake!");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

它的逻辑很简单,首先调用tcp_connect与给定服务器进行三次握手的连接。连接成功后,它的connect_notify接口会被调用,然后他使用tcp_send来向服务器发送一个字节,该调用会返回一个整数当做当前数据发送的句柄,当发送完成后它的send_notify接口会被调用,然后将发送结果与数据对应句柄关联起来告知相应数据发送是否成功。该接口被调用时客户端使用tcp_close来断开连接,如果断开成功,那么它的connect_close_notify会被调用。

同时前面实现的TCPThreeHandShakes将作为使用TCP协议的中间层,由它负责调用TCP协议封包层来发送和接收数据,为此我对其做了一些修改:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TCPThreeHandShakes extends Application{
    private byte[] dest_ip;
    private byte [] send_data = null;
    private short dest_port;
    private int ack_num = 0;
    private int seq_num = 0;
    ITCPHandler tcp_handler = null;
    //增加协议状态标量
    private static int CONNECTION_IDLE = 0;
    private static int CONNECTION_CONNECTING = 1;
    private static int CONNECTION_CONNECTED = 2;
    private static int CONNECTION_CLOSING = 3;
    private static int CONNECTION_SEND = 4;
    private int  tcp_state = CONNECTION_IDLE;
    public TCPThreeHandShakes(byte[] server_ip, short server_port, ITCPHandler tcp_handler) {
        this.dest_ip = server_ip;
        this.dest_port = server_port;
         //指定一个固定端口,以便抓包调试
        Random rand = new Random();
        this.port = (short)rand.nextInt();
        this.tcp_handler = tcp_handler;
    }

   public boolean tcp_connect() throws Exception {
       if (tcp_state != CONNECTION_IDLE) {  //只有在空闲状态才能发起新连接
           return  false;
       }
       tcp_state = CONNECTION_CONNECTING;
       beginThreeHandShakes();

       return true;
   }

   public boolean tcp_close() throws Exception {
       if (this.tcp_state != CONNECTION_CONNECTED) {
           return false;
       }
       tcp_state = CONNECTION_CLOSING;
       //启动关闭流程
       beginClose();
       return true;
   }

   public boolean tcp_send(byte[] data) throws Exception {
       if (this.tcp_state != CONNECTION_CONNECTED) {
           return false;
       }
       tcp_state = CONNECTION_SEND;
       send_data = data;
       createAndSendPacket(data, "ACK");
       return true;
   }
.....
@Override
    public void handleData(HashMap<String, Object> headerInfo) {
               。。。。

       if (ack) {
           int seq_num = (int)headerInfo.get("seq_num");
           int ack_num = (int)headerInfo.get("ack_num");
           byte[] data = (byte[])headerInfo.get("data");
           int data_length = 0;
           if (data != null) {  //如果收到对方数据,那么将对方seq加上数据长度最为ack回应
               data_length += data.length;
              }
           try {
            if (this.tcp_state == CONNECTION_CONNECTING && syn) {
                this.tcp_state = CONNECTION_CONNECTED;
                System.out.println("tcp handshake from othersize with seq_num" + seq_num + " and ack_num: " + ack_num);
                this.seq_num = ack_num;
                this.ack_num = seq_num + 1;
                createAndSendPacket(null, "ACK");
                this.tcp_handler.connect_notify(true); //通知上面连接成功
            }
          if (tcp_state == CONNECTION_SEND && ack_num == this.seq_num + send_data.length) {
              tcp_state = CONNECTION_CONNECTED;
              this.seq_num = ack_num;
              send_data = null;
              this.ack_num = seq_num + data_length;
              tcp_handler.send_notify(true, 0);
          }

          if (tcp_state == CONNECTION_CLOSING) {  //向对方发送ack+fin然后收到ack进入到此
              tcp_state = CONNECTION_IDLE;
              this.seq_num = 0;
              this.ack_num = 0;
          }

          if (fin) {  //收到fin包时,ack 要在对方seq上加1
              this.seq_num = ack_num;
              this.ack_num = seq_num + data_length + 1;
              createAndSendPacket(null, "ACK");
              if (tcp_state != CONNECTION_IDLE && tcp_state != CONNECTION_CLOSING) { //这里意味对方主动发起断开,因此回复ack后再次向对方发送ack+fin
                  createAndSendPacket(null, "FIN,ACK");
              }
          }


        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       }
      //收到服务器发回的fin+ack包,正式关闭连接
      if (ack && fin) {
          System.out.println("receive fin packet and close connection");
          if (this.tcp_state == CONNECTION_CLOSING) {
                this.tcp_state = CONNECTION_IDLE;
                this.tcp_handler.connect_close_notify(true); //通知上面连接关闭
                 try {
                       int seq_num = (int)headerInfo.get("seq_num");
                       int ack_num = (int)headerInfo.get("ack_num");
                       System.out.println("tcp handshake closing from othersize with seq_num" + seq_num + " and ack_num: " + ack_num);
                       this.seq_num += 1;
                       this.ack_num = seq_num + 1;
                        createAndSendPacket(null, "ACK");
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
            }
      }
        }
}

我对这个类代码上做了较大改动,具体的修改设计过程请观看视频的详细讲解,在上面代码完成后运行时,效果如下,首先是服务器端成功接收到了代码向他发送的字符’t’:

同时wireshark抓包情况如下:

下一节我们将在此基础上实现更复杂的双向数据发送。更多精彩内容和讲解演示请点击‘阅读原文’。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java从零开发TCPIP协议:实现TCP数据的收发机制
本节我们在上一节基础上进一步完成TCP协议的收发机制。上一节我们已经实现了向服务器方发送一个字符,本节我们要实现连续发送多个字符,并且能正常接收数据功能,完成了这些功能后,我们就可以基于此去开发其他构建在TCP之上的其他协议。
望月从良
2020/02/11
7240
java从零开发TCPIP协议:实现TCP数据的收发机制
代码实现TCP三次握手:程序实现
本节我们通过代码来实现TCP协议连接时的三次握手过程。首先我们需要再次重温一下TCP数据包的相关结构:
望月从良
2019/08/20
1K0
代码实现TCP三次握手:程序实现
详解TCP的重置功能和实现连接结束功能
上一节我们完成了TCP三次握手原则,当双方通过三次握手交换了各自用于传递信息的参数后,双方进入数据分发模式,在TCP协议上说双方都进入了ESTABLISHED状态。基于早期质量低下的数据传输网络,连接建立只不过是开始,在通讯过程中保持稳定和通畅是TCP协议的重要内容。
望月从良
2019/09/18
1.6K0
TCP三次握手过程详解
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP协议在建立连接时,会采用一种称为“三次握手”的过程来确保双方的通信能力和理解能力,从而建立起可靠的传输连接。
炒香菇的书呆子
2024/10/01
2820
java代码实现FTP协议
前几节我们完成了ftp协议的主要讲解,同时使用wireshark抓包了解ftp数据协议包的特征,本节我们使用代码完成ftp协议,代码将模仿ftp客户端,它与服务器建立连接后,使用用户名和密码登陆服务器,然后获得服务器的当前目录内容,继而通过数据连接获取服务器推送目录具体信息,最后客户端关闭,下面我们看看具体的代码实现,首先在工程目录下新建名为FTPClient的类,相关实现如下:
望月从良
2020/03/17
1.2K0
java代码实现FTP协议
Linux下TCP连接过程总结
一、Linux服务器上11种网络连接状态:       图:TCP的状态机 通常情况下,一个正常的TCP连接,都会有三个阶段:1、TCP三次握手; 2、数据传送; 3、TCP四次挥手 注:以下说明最
猿人谷
2018/01/17
5K0
Linux下TCP连接过程总结
网络通信的神奇之旅:解密Linux TCP网络协议栈的工作原理
TCP,全称传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议。
Lion 莱恩呀
2024/08/20
1850
网络通信的神奇之旅:解密Linux TCP网络协议栈的工作原理
java实现HTTP协议:POST协议代码实现
本节我们使用代码实现HTTP的POST协议流程。任何HTTP服务器都会支持客户端将文件上传,该功能的实现往往要走POST协议流程。为了使用代码实现该协议,首先需要一个目的HTTP服务器,我选择iPhone版本的福昕pdf阅读器,它支持通过POST协议将文件从电脑上传到手机,在打开其上传功能后,在电脑输入相应网址就能看到如下画面:
望月从良
2020/06/23
1.1K0
java实现HTTP协议:POST协议代码实现
基于☀️TCP/IP协议的聊天实例
2、写入:write——服务器read 往服务器发送请求/向服务器写入账号密码等
星河造梦坊官方
2024/08/15
1240
基于☀️TCP/IP协议的聊天实例
从0到1用java再造tcpip协议栈:代码实现ping应用功能1
上一节我们讲解了基于ICMP echo协议的ping原理,并提出下图的代码实现架构:
望月从良
2022/01/17
4500
从0到1用java再造tcpip协议栈:代码实现ping应用功能1
tcp rst报文_TCP报文格式
对于TCP客户端,在发送完SYN报文之后,如果接收到的回复报文同时设置了ACK和RST标志,在检查完ACK的合法性之后,处理RST标志,关闭套接口。对于ACK确认序号,其应当大于第一个未确认序号(snd_una),并且,确认序号不应大于未发送数据的序号(snd_nxt)。
全栈程序员站长
2022/11/10
1.7K0
从0到1用java再造tcpip协议栈:架构重建,完整实现ping应用
在原先代码设计中,我们为了方便,喜欢在一个模块中组织数据包的协议头,然后将要发送的数据融合在一起,并调用网卡将数据发送出去,这种偷懒的做法将多种逻辑融合在一起。这种做法一旦遇到复杂的数据发送需求时,系统逻辑的复杂性会呈现出爆炸性的增长,最后超出我们的控制范围。
望月从良
2019/03/04
6850
从0到1用java再造tcpip协议栈:架构重建,完整实现ping应用
TCP协议详解-定时器
        如代码所示,如果tcp的state<ESTABLISHED,表明其处于连接建立状态。定时器超时后,调用dropit终止连接。大多数伯克利系统将建立一个连接的最长时间设置为75s。连接建立定时器配合重传定时器一起使用,重传定时器会隔一段时间重传SYN,如下图所示:
无毁的湖光-Al
2018/08/14
7880
TCP协议详解-定时器
Python TCP 协议网络编程《一》
详细三次握手过程如下: 第一次握手:起初两端都处于CLOSED关闭状态,Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN-SENT状态,等待Server确认;
Wu_Candy
2022/07/04
3200
Python TCP 协议网络编程《一》
Python中TCP协议的理解
Num01–>TCP通信模型 Test01–>TCP客户端案例 #! /usr/bin/env python3 # -*- coding:utf-8 -*- from socket import
py3study
2020/01/08
9480
TCP ,丫的终于来了!!
之前的文章一直在聊各种网络协议,那么从这篇文章开始,我就会和你聊一聊关于 TCP 协议的种种特征,比如 TCP 连接管理(也是这篇文章主要讨论的)、TCP 超时和重传、TCP 拥塞控制、TCP 数据流和窗口管理。
谭庆波
2021/05/28
4430
TCP ,丫的终于来了!!
TCP协议三次握手和四次挥手抓包分析
 TCP协议在双方建立连接的时候需要三次握手,首先客户端发送SYN标志为1的TCP数据包,然后服务器端收到之后,也会发送一个SYN标志置位,并且带有ack应答的数据包,最后客户端再发送给服务端一个应答,这样就建立起了通信。
用户4415180
2022/06/23
5690
TCP协议三次握手和四次挥手抓包分析
TCP/IP详解之 《网络协议》图解 TCP 连接建立与释放
http://blog.csdn.net/chenhanzhun/article/details/41622555
bear_fish
2018/09/20
2.4K0
TCP/IP详解之  《网络协议》图解 TCP 连接建立与释放
60秒问答:系统调用之send函数
今天上午 回顾了 TCP/IP编程之select函数详解 ,发现还有问题。进行总结
早起的鸟儿有虫吃
2021/07/22
8420
活久见!TCP两次挥手,你见过吗?那四次握手呢?
正常情况下。只要数据传输完了,不管是客户端还是服务端,都可以主动发起四次挥手,释放连接。
小白debug
2022/06/20
5470
活久见!TCP两次挥手,你见过吗?那四次握手呢?
推荐阅读
相关推荐
java从零开发TCPIP协议:实现TCP数据的收发机制
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验