某工業設備/舊協議系統要求客戶端與服務端通過 **原始 TCP 字節流** 通信,數據格式為十六進制字節序列(如 01 03 00 00 00 02 C4 0B)。本文提供一對可直接運行的「阻塞式單線程 Server / Client」完整代碼,涵蓋:
DataInputStream/DataOutputStream)public class HexByteServer {
private static final int PORT = 5020;
private final ServerSocket serverSocket;
public HexByteServer() throws IOException {
this.serverSocket = new ServerSocket(PORT);
}
public void start() {
System.out.println("Server listening on port " + PORT);
while (true) {
try (Socket client = serverSocket.accept()) {
System.out.println("Client connected: " + client.getRemoteSocketAddress());
handleClient(client);
} catch (IOException e) {
System.err.println("Accept error: " + e.getMessage());
}
}
}
private void handleClient(Socket socket) throws IOException {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
DataOutputStream out = new DataOutputStream(socket.getOutputStream())) {
byte[] single = new byte[1];
StringBuilder frame = new StringBuilder();
while (in.read(single) != -1) {
frame.append(bytesToHex(single)).append(' ');
// 簡易幀結束判斷:對端暫時無數據發送
if (in.available() == 0) {
System.out.println("Received frame: " + frame.toString().trim());
// 業務處理...
doBusiness(frame.toString().trim());
frame.setLength(0); // 清空準備下一幀
}
}
}
}
private void doBusiness(String hexFrame) {
// 這裡接入實際協議解析邏輯
System.out.println("Business processing: " + hexFrame);
}
/** 字節數組 → 大寫十六進制字符串(每字節兩位,不足補 0) */
public static String bytesToHex(byte[] src) {
if (src == null || src.length == 0) return "";
StringBuilder sb = new StringBuilder(src.length * 2);
for (byte b : src) {
int v = b & 0xFF;
if (v < 0x10) sb.append('0');
sb.append(Integer.toHexString(v).toUpperCase());
}
return sb.toString();
}
public static void main(String[] args) throws IOException {
new HexByteServer().start();
}
}
public class HexByteClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 5020;
public static void main(String[] args) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(HOST, PORT), 3000);
System.out.println("Connected to server");
try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(System.in)) {
byte[] buf = new byte[1];
System.out.println("輸入十六進制字節(如 01 03 00),按 Enter 發送,Ctrl+C 退出");
// 這裡演示從標準輸入讀取原始字節,實際項目中通常直接構造 byte[]
while (in.read(buf) != -1) {
out.write(buf);
out.flush();
}
}
} catch (IOException e) {
System.err.println("Client error: " + e.getMessage());
}
}
}
read(byte[]) 單字節讀取?協議無固定長度、無分隔符時,流式讀取只能逐字節讀。配合 in.available() == 0 判斷「對端本次發送暫停」,作為幀邊界(適用於請求-響應、低頻設備)。高頻/長連接場景建議自定義協議頭(長度域)或特殊分隔符。
b & 0xFF 與 Integer.toHexString 的必要性byte 有符號(-128~127),直接 toHexString 會產生負數補碼(如 -1 → "ffffffff")。b & 0xFF 強制提取無符號 8 位值(0~255),再轉十六進制,保證輸出 "FF" 而非 "FFFFFFFF"。| 模型 | 適用場景 | 代碼變更 |
|---|---|---|
| 單線程阻塞(本文) | 低併發、測試驗證 | — |
線程池 ExecutorService | 中等併發(<500 連接) | pool.submit(() -> handleClient(client)) |
| NIO 選擇器 | 高併發(萬級連接) | Netty / Mina 框架 |
@Test
public void testBytesToHex() {
assertEquals("010300000002C40B", HexByteServer.bytesToHex(
new byte[]{0x01, 0x03, 0x00, 0x00, 0x00, 0x02, (byte)0xC4, 0x0B}));
assertEquals("FF", HexByteServer.bytesToHex(new byte[]{(byte)0xFF}));
assertEquals("00", HexByteServer.bytesToHex(new byte[]{0x00}));
}
本文提供的阻塞式字節流 Server/Client 代碼結構清晰、資源管理規範(try-with-resources)、十六進制轉換工具經過實測,可直接作為舊協議對接、設備調試、教學演示的起點。生產環境請根據併發量選擇線程池或 Netty 重構網絡層,業務邏輯層可完全復用。