基于 TCP 协议的自定义 Socket 数据帧来进行短数据的传输

如上一节所示,自定义数据帧中每一块内容的长度是用 byte,即字节来定义的,同时两台主机的 Socket 传输内容实际上是使用 byte[] ,即字节数组的方式。所以我们必须要掌握 int 型与 byte[] 型的转换方式。

2.3.1 int 型到 byte[] 型的转换

/**

  • int到byte[]

  • @param i 输入待转换的int

  • @return 返回的对应的byte[]

*/

public static byte[] intToByteArray(int i) {

byte[] result = new byte[4];

//由高位到低位

result[0] = (byte)((i >> 24) & 0xFF);

result[1] = (byte)((i >> 16) & 0xFF);

result[2] = (byte)((i >> 8) & 0xFF);

result[3] = (byte)(i & 0xFF);

return result;

}

2.3.2 byte[] 型到 int 型的转换:

/**

  • byte[]转int

  • @param bytes 指定的byte[]

  • @return int型的值

*/

public static int byteArrayToInt(byte[] bytes) {

return (bytes[3] & 0xFF) |

(bytes[2] & 0xFF) << 8 |

(bytes[1] & 0xFF) << 16 |

(bytes[0] & 0xFF) << 24;

}

在计算机中,“位”才是最小单位,1byte(字节)= 8 bit(位),更多关于位运算的知识,可以参考这篇博客。【传送门

3 代码实现

=========================================================================

为了更好的说明代码中每一步代表的含义,我将代码的解释说明注释在了代码中,如果我有写得不清楚的地方可以评论或者私信我,我会第一时间回复。

3.1 客户端代码


import java.io.*;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Arrays;

import java.util.Scanner;

public class Client {

public static void main(String[] args) throws Exception{

//连接服务器

// 127.0.0.1 指代的是本地电脑的 IP 地址

Socket s = new Socket(“127.0.0.1”, 5612);

System.out.println(“已连接到服务器5612端口,准备向服务器发送指令。”);

//获取输出流

OutputStream out = s.getOutputStream();

while(true){

//服务端开始输入指令

System.out.println(“请输入指令:”);

System.out.println(“1819:请求服务器中实验室人员的数量”);

System.out.println(“1820:请求服务器中实验室人员的平均补贴”);

System.out.println(“0000:退出与服务器的连接”);

Scanner scanner = new Scanner(System.in);

int instruction=scanner.nextInt();

if(instruction != 1819 && instruction != 1820 && instruction != 0000){

System.out.println(“输入的指令有误,请重新输入”);

continue;

}

// 如果客户端不选择主动退出

if (instruction!=0000) {

//将int型指令转化为byte[]

byte[] instructionArr = intToByteArray(instruction);

//将byte[]指令按照Socket数据包协议编码成新的数组

byte[] sendSocketArr = encode(instructionArr);

//开始在输出流中发送数据

out.write(sendSocketArr, 0, sendSocketArr.length);

System.out.println(“指令已经按照协议编码好发送给服务器端,等待服务器端数据返回”);

/下面就是为了接收服务器端返回的数据//

InputStream in = s.getInputStream();

BufferedInputStream bis = new BufferedInputStream(in);

//卧槽,用BufferedInputStream代替了InputStream,问题就解决了。

byte[] bs = new byte[1024 * 100];

bis.read(bs);

// 将接收到的字节数据的前几位打印出来,看看是否正确

System.out.println(Arrays.toString(Arrays.copyOfRange(bs, 0, 16)));

//首先确定校验符

//提取出指定校验符的指定位置

byte[] checkCharacterArr = subBytes(bs, 2, 4);

//将字节数组转化为int类型

int checkCharacter = byteArrayToInt(checkCharacterArr);

//根据校验符判断该Socket数据包是否是我们的数据包

if (checkCharacter == 888) {

//同理,从数据包中得到消息体的长度

byte[] lengthMsgArr = subBytes(bs, 6, 4);

//将字节数组转化为int类型

int lengthMsg = byteArrayToInt(lengthMsgArr);

System.out.println(“消息体长度”+lengthMsg);

//当知道数据包中消息体的长度后,便可以根据长度来解析出Socket的消息体

byte[] destMsg = subBytes(bs, 10, lengthMsg);

//按照Socket协议解码之后的数据

switch (instruction) {

case 1819:

int speed = byteArrayToInt(destMsg);

System.out.printf(“此时,实验室人员的数量有 %d 人\n”, speed);

break;

case 1820:

int volume=byteArrayToInt(destMsg);

System.out.printf(“实验室人员的平均补贴为 %d 元\n”,volume);

break;

default:

break;

}

System.out.println(“数据接收完毕。\n”);

}

}

else{

System.out.println(“退出连接”);

break;

}

}

//关闭通道

out.close();

s.close();

}

/**

  • 用于Socket数据包的解码,该方法并没有编写完成,比如若校验错误,则返回{-1,-1};

*开始解析数据包,不考虑粘包的情况。目前先假设数据包格式为:消息头(包头+校验符+消息体长度)+消息体。

*假设包头为:head[0]+head[1] 其中内容如下。校验码为int型的888,消息体长度为int型(取值范围为-2147483648到±2147483648),消息体为byte[]

*这样的话消息头总共占有10(2字节+int型的4字节+int型的4字节)字节,消息体则放在消息头后面

  • @param msg 整个Socket数据包

  • @return 返回消息体

*/

public static byte[] decodeMsg(byte[] msg){

//首先确定校验符

//提取出指定校验符的指定位置

byte[] checkCharacterArr=subBytes(msg,2, 4);

//将字节数组转化为int类型

int checkCharacter=byteArrayToInt(checkCharacterArr);

//根据校验符判断该Socket数据包是否是我们的数据包

// if (checkCharacter==888) {

//同理,从数据包中得到消息体的长度

byte[] lengthMsgArr = subBytes(msg, 6, 4);

//将字节数组转化为int类型

int lengthMsg = byteArrayToInt(lengthMsgArr);

//当知道数据包中消息体的长度后,便可以根据长度来解析出Socket的消息体

byte[] destMsg = subBytes(msg, 10, lengthMsg);

return destMsg;

// }else{

// return new byte[]{-1, -1};

// }

}

/**

  • Socket数据包协议

  • @param fileArr 输入的指令或者数据,要求为字节数组型

  • @return 根据Socket数据包协议编码的字节数组

*/

public static byte[] encode(byte[] fileArr){

byte[] head1 = new byte[2];

head1[0] = (byte) 0;

head1[1] = (byte) 1;

byte[] head2 = intToByteArray(888);

byte[] head3 = intToByteArray(fileArr.length);

//head是包的标识符

byte[] head = byteConcat(head1, head2, head3);

byte[] data = byteMerger(head, fileArr);

return data;

}

/**

  • int到byte[]

  • @param i 输入待转换的int

  • @return 返回的对应的byte[]

*/

public static byte[] intToByteArray(int i) {

byte[] result = new byte[4];

//由高位到低位

result[0] = (byte)((i >> 24) & 0xFF);

result[1] = (byte)((i >> 16) & 0xFF);

result[2] = (byte)((i >> 8) & 0xFF);

result[3] = (byte)(i & 0xFF);

return result;

}

/**

  • byte[]转int

  • @param bytes 指定的byte[]

  • @return int型的值

*/

public static int byteArrayToInt(byte[] bytes) {

return (bytes[3] & 0xFF) |

(bytes[2] & 0xFF) << 8 |

(bytes[1] & 0xFF) << 16 |

(bytes[0] & 0xFF) << 24;

}

public static byte[] byteConcat(byte[] bt1, byte[] bt2, byte[] bt3) {

byte[] bt4 = new byte[bt1.length + bt2.length + bt3.length];

int len = 0;

System.arraycopy(bt1, 0, bt4, 0, bt1.length);

len += bt1.length;

System.arraycopy(bt2, 0, bt4, len, bt2.length);

len += bt2.length;

System.arraycopy(bt3, 0, bt4, len, bt3.length);

return bt4;

}

public static byte[] byteMerger(byte[] bt1, byte[] bt2) {

byte[] bt3 = new byte[bt1.length + bt2.length];

System.arraycopy(bt1, 0, bt3, 0, bt1.length);

System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);

return bt3;

}

/**

  • 利用System.arraycopy的方法在字节数组中截取指定长度数组

  • @param src

  • @param begin

  • @param count

  • @return

*/

public static byte[] subBytes(byte[] src, int begin, int count) {

byte[] bs = new byte[count];

System.arraycopy(src, begin, bs, 0, count);

return bs;

}

}

3.2 服务器端代码


import java.io.*;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Arrays;

public class Server {

public static void main(String[] args) throws Exception {

//服务器开始监听5612端口

ServerSocket serverSocket = new ServerSocket(5612);

System.out.println(“服务端已启动,正在监听5612端口…”);

//等待客户端连接

Socket s = serverSocket.accept();

//获取输入流:服务器指令。指令的大小利用一个1024的数组肯定是可以存储的

InputStream in = s.getInputStream();

BufferedInputStream bis = new BufferedInputStream(in);

while(true){

//一样的字节数组缓冲操作,将IO流中的数据放到buf数组中

byte[] buf = new byte[1024];

int length = bis.read(buf);

if (length!=-1){

//解析数据包,得到服务器的请求

//首先确定校验符

//提取出指定校验符的指定位置

System.out.println(Arrays.toString(buf));

byte[] checkCharacterArr = subBytes(buf, 2, 4);

//将字节数组转化为int类型

int checkCharacter = byteArrayToInt(checkCharacterArr);

//根据校验符判断该Socket数据包是否是我们的数据包

if (checkCharacter == 888) {

//同理,从数据包中得到消息体的长度

byte[] lengthMsgArr = subBytes(buf, 6, 4);

//将字节数组转化为int类型

int lengthMsg = byteArrayToInt(lengthMsgArr);

//当知道数据包中消息体的长度后,便可以根据长度来解析出Socket的消息体

byte[] destMsgArr = subBytes(buf, 10, lengthMsg);

int destMsg = byteArrayToInt(destMsgArr);

//哈哈哈,原来在switch之前定义,就可以使用这个变量

byte[] sendSocketArr = null;

switch (destMsg) {

case 1819:

byte[] fileArr1819 = intToByteArray(18);

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-ufrP9eBg-1710415714093)]
[外链图片转存中…(img-pF1QWv5R-1710415714094)]
[外链图片转存中…(img-rp0my9Ev-1710415714094)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-oL0tSrcx-1710415714095)]

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

[外链图片转存中…(img-S0LvMLx9-1710415714096)]

[外链图片转存中…(img-DAkqbRN2-1710415714096)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值