返回

Java to TypeScript Socket: Reliable Message Handling

java

Java 向 TypeScript Socket 服务器发送消息的可靠方法

当Java客户端尝试向TypeScript编写的Socket服务器发送消息时,可能会遇到服务器在接收消息后立即关闭的问题。本篇文章深入探讨此类问题的原因,并提出若干有效的解决方案,帮助读者建立一个稳定且可扩展的通信机制。

问题分析

现象如题中所述:服务器成功接收客户端发送的消息后,并未保持监听状态,而是立即关闭了连接,这往往是因为TCP连接机制的固有特性以及客户端代码的处理方式。问题具体表现为,当 Java客户端完成一次写入操作并关闭输出流后,服务器端收到一个ECONNRESET 错误。 这个错误表明连接已被对端关闭。socket.pipe(socket);这一行代码在问题中起了加速错误发生的作用,它直接将数据流反向写回客户端,并且因为服务器端没有缓冲写入,写入会阻塞,最终导致错误发生。简言之,服务器端收到数据后直接试图回写数据到已经关闭的客户端连接导致服务器程序退出。

解决方案 1:保持客户端连接开放

核心在于不要在每次发送完消息后立即关闭Java客户端的socket连接, 应该将写入动作与连接创建及关闭动作分开处理。 创建一个独立的输出流在循环内发送多条信息。

操作步骤:

  1. 修改 Java客户端代码,使用 PrintWriter 来输出文本,保持 Socket 连接的活跃。
  2. 建立连接,循环发送消息。发送完所有消息后,再关闭 socket。

代码示例:

import java.io.PrintWriter;
import java.net.Socket;

public class JavaClient {

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

      Socket socket = new Socket("127.0.0.1", 60001);
      PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

        String[] messages = {"Hello", "From", "Java"};
         for(String msg : messages){
             out.println(msg); // using println here which is also sends a new line character.
             Thread.sleep(1000);  // Add delay
         }


     socket.close();
     System.out.println("Socket closed");
    }
}

服务器端 myserver.ts的代码删除socket.pipe(socket):

import { createServer } from 'net';

function main() {
   const socketServer = createServer((socket) => {
    console.log("Client connected");
       socket.on('data', function (data) {
           console.log('Received: ' + data.toString());
       });

       socket.on("end", () => {
          console.log("Client disconnected");
      })
   });

   socketServer.listen(60001, '127.0.0.1');
    console.log("Server started on 127.0.0.1:60001")
}

main();

运行方式:

  1. 先使用 ts-node ./myserver.ts 命令启动 TypeScript 服务器。
  2. 然后编译并运行 JavaClient

这样修改之后, myserver.ts就可以收到并打印 Hello From Java 这三个消息。 注意需要println或者\n来表示换行,否则nodejs读取socket流时,会把没有遇到换行符的数据放在一个流里面,可能会读取不到完整的信息。服务器可以保持监听状态,客户端发送完毕后才关闭连接。服务器Client disconnected会被打印出来。

解决方案 2:基于长度前缀的消息格式

客户端可以发送基于长度前缀的消息,这种做法可以让服务器知道消息的完整长度。这对于处理包含任意字符或者二进制的数据来说特别有用。此方法需要双方使用约定好的格式,同时也是一种标准的网络传输方式。

操作步骤:

  1. 修改 Java 客户端代码,在每个消息前添加长度前缀。
  2. 修改 TypeScript 服务器代码,解析长度前缀并按此长度读取数据。

Java 代码示例:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class JavaClient {

    public static void main(String[] args) throws IOException {
        try (Socket socket = new Socket("127.0.0.1", 60001);
            OutputStream out = socket.getOutputStream();){


            String[] messages = {"First Message", "Second Message", "Short"};

              for(String message: messages) {

                    byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
                    int messageLength = messageBytes.length;
                    ByteBuffer lengthBuffer = ByteBuffer.allocate(4); // Length: 4 bytes

                  lengthBuffer.putInt(messageLength);
                   out.write(lengthBuffer.array());

                  out.write(messageBytes);
                Thread.sleep(1000);

              }

        }
    }
}

TypeScript 服务器代码示例:

import { createServer } from 'net';

function main() {
    const socketServer = createServer((socket) => {
      let receivedDataBuffer = Buffer.alloc(0);
      socket.on('data', (data) => {
        receivedDataBuffer = Buffer.concat([receivedDataBuffer, data]);
            while(receivedDataBuffer.length >= 4){
                 const messageLength = receivedDataBuffer.readInt32BE();
                  if(receivedDataBuffer.length >= 4+ messageLength){
                      const messageBuffer = receivedDataBuffer.subarray(4, 4 + messageLength);
                     const message = messageBuffer.toString();
                     console.log(`Received: ${message}`);

                    receivedDataBuffer = receivedDataBuffer.subarray(4+messageLength)
                }
            else
                break;
            }


        });
       socket.on("end",()=>{
             console.log("Client disconnected")
         });
    });


    socketServer.listen(60001, '127.0.0.1', () => {
        console.log('Server started');
    });
}


main();

运行方式:

  1. 使用 ts-node ./myserver.ts 启动 TypeScript 服务器。
  2. 然后编译并运行 JavaClient

使用长度前缀的方式,即使客户端不立刻关闭连接,也能准确地分隔每一条信息,服务器可以正确的接收多条消息。

安全性建议

  • 始终对网络传输的数据进行安全检查,防止恶意代码注入。
  • 设置读取缓冲区大小,避免接收过大的数据包而导致资源耗尽。

以上就是处理 Java 向 TypeScript Socket服务器发送消息问题的两个有效解决方案,根据项目的具体需求选择最合适的方式。 结合以上建议和实践,可以搭建更健壮的应用通信链路。