如上一节所示,自定义数据帧中每一块内容的长度是用 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(位)
,更多关于位运算的知识,可以参考这篇博客。【传送门】
=========================================================================
为了更好的说明代码中每一步代表的含义,我将代码的解释说明注释在了代码中,如果我有写得不清楚的地方可以评论或者私信我,我会第一时间回复。
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;
}
}
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);
System.out.println(“数据正在编码中……”);
sendSocketArr = encodeMsg(fileArr1819);
break;
case 1820:
byte [] fileArr1820=intToByteArray(400);
System.out.println(“数据正在编码中……”);
sendSocketArr=encodeMsg(fileArr1820);
break;
default:
break;
}
OutputStream out = s.getOutputStream();
System.out.println(“开始发送……”);
out.write(sendSocketArr, 0, sendSocketArr.length);
// 将发送到的字节数据的前几位打印出来,并与客户端接收的数据做一个对比,看看是否正确
System.out.println(Arrays.toString(Arrays.copyOfRange(sendSocketArr, 0, 16)));
System.out.println(“发送完毕。\n”);
}
}else{
break;
}
}
//关闭资源。io流关闭的同时socket也会关闭,或者用什么方法隔离开。
in.close();
s.close();
serverSocket.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};