服务器端代码如下:
/** * 服务器端(只负责接收消息) */ class ServSocket { // 字节数组的长度 private static final int BYTE_LENGTH = 20; public static void main(String[] args) throws IOException { // 创建 Socket 服务器 ServerSocket serverSocket = new ServerSocket(9999); // 获取客户端连接 Socket clientSocket = serverSocket.accept(); // 得到客户端发送的流对象 try (InputStream inputStream = clientSocket.getInputStream()) { while (true) { // 循环获取客户端发送的信息 byte[] bytes = new byte[BYTE_LENGTH]; // 读取客户端发送的信息 int count = inputStream.read(bytes, 0, BYTE_LENGTH); if (count > 0) { // 成功接收到有效消息并打印 System.out.println("接收到客户端的信息是:" + new String(bytes)); } count = 0; } } } }客户端代码如下:
/** * 客户端(只负责发送消息) */ static class ClientSocket { public static void main(String[] args) throws IOException { // 创建 Socket 客户端并尝试连接服务器端 Socket socket = new Socket("127.0.0.1", 9999); // 发送的消息内容 final String message = "Hi,Java."; // 使用输出流发送消息 try (OutputStream outputStream = socket.getOutputStream()) { // 给服务器端发送 10 次消息 for (int i = 0; i < 10; i++) { // 发送消息 outputStream.write(message.getBytes()); } } } }以上程序的通讯结果如下图所示:
通过上述结果我们可以看出,服务器端发生了粘包和半包的问题,因为客户端发送了 10 次固定的“Hi,Java.”的消息,正常的结果应该是服务器端也接收到了 10 次固定的消息才对,但现实的结果并非如此。 粘包和半包的解决方案
粘包和半包的解决方案有以下 3 种:
发送方和接收方规定固定大小的缓冲区,也就是发送和接收都使用固定大小的 byte[] 数组长度,当字符长度不够时使用空字符弥补;
在 TCP 协议的基础上封装一层数据请求协议,既将数据包封装成数据头(存储数据正文大小)+ 数据正文的形式,这样在服务端就可以知道每个数据包的具体长度了,知道了发送数据的具体边界之后,就可以解决半包和粘包的问题了;
以特殊的字符结尾,比如以“\n”结尾,这样我们就知道结束字符,从而避免了半包和粘包问题(推荐解决方案)。
那么接下来我们就来演示一下,以上解决方案的具体代码实现。
解决方案1:固定缓冲区大小固定缓冲区大小的实现方案,只需要控制服务器端和客户端发送和接收字节的(数组)长度相同即可。
服务器端实现代码如下:
/** * 服务器端,改进版本一(只负责接收消息) */ static class ServSocketV1 { private static final int BYTE_LENGTH = 1024; // 字节数组长度(收消息用) public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9091); // 获取到连接 Socket clientSocket = serverSocket.accept(); try (InputStream inputStream = clientSocket.getInputStream()) { while (true) { byte[] bytes = new byte[BYTE_LENGTH]; // 读取客户端发送的信息 int count = inputStream.read(bytes, 0, BYTE_LENGTH); if (count > 0) { // 接收到消息打印 System.out.println("接收到客户端的信息是:" + new String(bytes).trim()); } count = 0; } } } }客户端实现代码如下:
/** * 客户端,改进版一(只负责接收消息) */ static class ClientSocketV1 { private static final int BYTE_LENGTH = 1024; // 字节长度 public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 9091); final String message = "Hi,Java."; // 发送消息 try (OutputStream outputStream = socket.getOutputStream()) { // 将数据组装成定长字节数组 byte[] bytes = new byte[BYTE_LENGTH]; int idx = 0; for (byte b : message.getBytes()) { bytes[idx] = b; idx++; } // 给服务器端发送 10 次消息 for (int i = 0; i < 10; i++) { outputStream.write(bytes, 0, BYTE_LENGTH); } } } }以上代码的执行结果如下图所示: