Java 网络编程入门:手把手实现两个程序之间的 TCP 对话

📅 2026/7/30 9:53:36
Java 网络编程入门:手把手实现两个程序之间的 TCP 对话
在学习 Java 网络编程时我们会接触 IP 地址、端口号、TCP、UDP、Socket、输入流和输出流等概念。如果只看定义这些知识很容易显得抽象。这些定义的详细解释在我上上篇博客中其实已经进行了比较详细的解释如果还不是很了解的小伙伴可以移步这篇文章计算机网络基础TCP、UDP、IP、端口号与套接字先进行了解我也会在文章的第一节对这些基础知识进行简单的解释。本文将从一个具体目标出发编写两个独立的 Java 程序让它们建立 TCP 连接并在控制台中互相发送消息。这个程序在后续也会不断的完善在此过程中将持续更新博客感兴趣的话可以持续关注一、网络编程基础概念1.1 什么是网络编程网络编程就是让不同的程序通过网络交换数据。这些程序可以在不同的计算机上也可以像文本一样先在同一台计算机上进行测试。一次网络通信至少需要解决三个问题对方在哪台计算机上数据应该交给这台计算机中的哪个程序双方按照什么规则传输和解释数据对应的基础概念分别是IP 地址定位计算机端口号定位计算机中的程序网络协议规定双方通信的规则。1.2 什么是输入流和输出流连接建立以后双方通过流收发数据InputStream read() 接收数据 OutputStream write() 发送数据输入和输出是站在当前程序的角度定义的数据进入当前程序 输入 数据离开当前程序 输出所以两边流的对应关系是A 的 OutputStream → B 的 InputStream B 的 OutputStream → A 的 InputStream二、从零实现两个 Java 程序的 TCP 对话前面已经介绍过 IP、端口号、TCP、Socket 等基础概念。现在开始进行代码的实现过程。首先我们明确最终要实现的效果为A 程序输入消息 ↓ A 将消息发送给 B ↓ B 在控制台打印消息 ↓ B 输入回复 ↓ B 将回复发送给 A ↓ A 在控制台打印回复在进行下面步骤之前首先要在Maven下创建项目然后创建两个类在这里为了方便表示直接将他们命名为A.java和B.java2.1 第一步让 B 监听端口并等待 A 连接2.1.1 创建 ServerSocket 并监听 54321 端口在前面的流程中B 的第一个职责是等待 A 的连接。因此我们先在B.java的main方法中创建一个ServerSocket相关的代码如下所示此行代码就表示当B程序启动之后就会在本机的54321端口上创建了一个服务端。我们可以把它理解为 B 先在本机创建了一个“接待点”地址为127.0.0.1:54321 。之后A 程序只要连接这个地址就能找到 B。但是我们也注意到了写完这行代码之后代码中有红色下划线提示错误这个时候我们需要用try\catch来解决这个问题快捷键为Alt Enter光标放在画线单词上同时需要对变量ss进行初始化解决后的完整代码如下所示public class B { public static void main(String[] args) { ServerSocket ss null; try { ss new ServerSocket(54321); } catch (IOException e) { throw new RuntimeException(e); } } }2.1.2 调用accept( )等待A的连接此处的问题与上面红线的问题一致解决方法也一致不多赘述了解决之后的代码如下所示try { Socket s ss.accept(); } catch (IOException e) { throw new RuntimeException(e); }2.2 第二步 让A主动连接BB 负责等待A 则负责主动发起连接。相关的代码为Socket sc new Socket(127.0.0.1, 54321);这行代码的含义是A 程序尝试连接到IP 地址为127.0.0.1 端口号为 54321 的程序。由于本例中 A 和 B 都运行在同一台电脑上所以使用127.0.0.1 他表示本机。用try\catch解决上述问题后相关代码如下public class A { public static void main(String[] args) { try { Socket sc new Socket(127.0.0.1, 54321); } catch (IOException e) { throw new RuntimeException(e); } } }此时A 和 B 已经建立 TCP 连接不过现在只是连接成功双方还没有发送任何消息。现在我们可以测试一下在两段代码中都加上对应的输出语句注意是要先运行B因为这样才能有对应的端口让A去连接。我就不在此赘述了。2.3 第三步 让A获取输入流、输出流和控制台输入A 连接成功后接下来要完成两件事从控制台获取用户输入的消息将消息发送给 B。首先需要让A能够读取来自B的消息我们用输入流来实现相关的代码如下所示当然啦这个功能需要在try里面实现哦代码的意思为A 通过in读取 B 发来的消息。InputStream in sc.getInputStream();其次我们要让A能给B发去信息我们用输出流来实现相关的代码如下同上注意事项代码的意思为A 通过os向 B 发送消息。OutputStream os sc.getOutputStream();我们还需要让A能够读取控制台中输入的内容相关的代码如下所示。代码的意思为A 通过scanner读取控制台中输入的内容。Scanner scanner new Scanner(System.in);此时A的状态可以表示为控制台输入 ↓ Scanner scanner ↓ A 程序 ↓ OutputStream os ↓ B 程序 B 的回复 ↓ InputStream in ↓ A 程序 ↓ 控制台输出接下来我们先实现A从控制台输入一句话。2.3.1 让 A 能够持续读取控制台输入现在 A 已经拥有in读取 B 发来的消息os向 B 发送消息scanner读取控制台输入。接下来我们需要让 A 在控制台中输入要发送给 B 的内容。由于后面需要不断进行对话所以先使用while (true)将整个聊天过程放入循环中while (true) { System.out.println(请说一个字); String m scanner.nextLine();代码的意思是A 每次先在控制台输出“请说一个字”然后通过scanner读取用户输入的一行内容并保存到变量m中。现在 A 已经可以读取控制台输入但消息还没有发送给 B。接下来需要通过输出流os将消息发送出去。2.3.2 A 先发送消息的长度在发送消息内容之前A需要先把消息的长度发送给B。这样做是为了让B在读取消息的时候知道接下来需要读取多少字符相关的代码为写在刚刚的while循环中int len m.length(); os.write(len);现在A 和 B的通信格式为先发送消息长度再发送消息内容。2.3.3 A 发送消息内容在while循环中继续添加一个for循环用于向B发送信息for (int i 0; i len; i) { os.write(m.charAt(i)); } os.flush();这段代码会依次取出m中的每一个字符并通过输出流发送给 B。os.flush(); 表示将已经写入输出流的数据发送出去。至此A 已经完成了发送消息的操作。接下来A 需要等待 B 的回复。2.3.4 A 等待并读取 B 的回复B在回复消息的时候同样会先发送消息的长度再发送消息内容。因此A在接受消息的时候需要先读取长度相关的代码如下int len1 in.read();// 等待 阻塞 System.out.println(收到消息);当 B 还没有发送回复时程序会停在in.read();这一行等待消息的到来。这也就是注释中“等待 阻塞”的含义当前线程暂时不继续执行直到 B 向 A 发送数据。当 A 读取到 B 发送的消息长度后接下来按照这个长度读取每一个字符for (int i 0; i len1; i) { int m1 in.read(); System.out.print((char) m1); }代码的意思是循环读取len1个字符并将每一个字符打印到 A 的控制台中。这样A 的一次操作就完成了A 输入消息 ↓ A 将消息发送给 B ↓ A 等待 B 回复 ↓ A 打印 B 的回复由于这些代码写在while (true)中因此 A 收到回复后会再次回到System.out.println(请说一个字);继续等待下一次输入。此时 A 中循环部分的完整代码如下while (true) { System.out.println(请说一个字); String m scanner.nextLine(); // 发送的第一个字节是数据的长度 int len m.length(); os.write(len); for (int i 0; i len; i) { os.write(m.charAt(i)); } os.flush(); int len1 in.read();// 等待 阻塞 System.out.println(收到消息); for (int i 0; i len1; i) { int m1 in.read(); System.out.print((char) m1); } System.out.println(); }A 的代码暂时写到这里。下面回到 B让 B 能够接收 A 发来的消息。2.4 第四步让 B 接收 A 的消息并回复B 与 A 建立连接后同样需要获取输入流、输出流和控制台输入工具相关代码如下// 对话 InputStream in s.getInputStream();// 输入流 读取对方的消息 OutputStream os s.getOutputStream();// 输出流 给对方发消息 Scanner scanner new Scanner(System.in);接下来同样使用循环让 B 可以持续接收消息并回复while (true) {B 和 A 的执行顺序不同A先发送消息再等待回复 B先接收消息再输入回复因此B 进入循环后的第一件事是读取 A 发来的消息。2.4.1 B 读取 A 发送的消息长度A 发送消息时第一个发送的数据是消息长度os.write(len);所以 B 需要先读取消息长度int len in.read(); System.out.println(收到消息);例如A 输入helloA 首先发送长度5B 执行int len in.read();后len的值就是5。接下来B 就知道需要继续读取 5 个字符。2.4.2 B 读取并打印 A 的消息内容继续添加以下代码for (int i 0; i len; i) { int m1 in.read();// 接受消息 System.out.print((char) m1);// 转成字符打印 } System.out.println();这段代码会按照刚才读取到的长度逐个读取 A 发送的字符。例如A 发送helloB 的控制台会输出收到消息 hello到这里B 已经可以接收并打印 A 发来的消息。 接下来让 B 在控制台输入回复。2.4.3 B 从控制台读取回复在 B 打印完 A 的消息后添加以下代码System.out.println(请输入消息); String m scanner.nextLine();B 的控制台会显示请输入消息随后等待用户输入。例如B 输入你好那么这段内容会保存到变量m中。接下来B 需要把这段回复发送给 A。2.4.4 B 将回复发送给 AB 发送消息的方式与 A 相同先发送长度再发送每一个字符。相关代码如下int len1 m.length(); os.write(len1); for (int i 0; i len1; i) { os.write(m.charAt(i));// 写字到输入框中 } os.flush();// 点击发送按钮这里int len1 m.length();用于获取 B 回复消息的长度。os.write(len1);先将长度发送给 A。随后通过循环将回复中的每一个字符发送出去for (int i 0; i len1; i) { os.write(m.charAt(i)); }最后os.flush();将消息立即发送给 A。此时B 中循环部分的完整代码如下while (true) { int len in.read(); System.out.println(收到消息); for (int i 0; i len; i) { int m1 in.read();// 接受消息 System.out.print((char) m1);// 转成字符打印 } System.out.println(); System.out.println(请输入消息); String m scanner.nextLine(); int len1 m.length(); os.write(len1); for (int i 0; i len1; i) { os.write(m.charAt(i));// 写字到输入框中 } os.flush();// 点击发送按钮 }至此A 和 B 就可以按照固定顺序持续对话A 输入消息 ↓ A 发送消息给 B ↓ B 接收并打印消息 ↓ B 输入回复 ↓ B 发送回复给 A ↓ A 接收并打印回复 ↓ 进入下一轮对话整个通信过程始终遵循下面的顺序A发送 → 接收 B接收 → 发送因此运行程序时仍然需要先启动 B再启动 A。这样一个基础的通过TCP 对话对话的程序我们就实现了接下来会持续更新哦并且会进一步完善完整代码如下A.javaimport java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner; // 网络基础知识 // 实现两个程序之间发送消息 public class A { public static void main(String[] args) { // 套接字 try { Socket sc new Socket(127.0.0.1, 54321);// 男方 System.out.println(发起连接~); InputStream in sc.getInputStream(); OutputStream os sc.getOutputStream(); Scanner scanner new Scanner(System.in); while (true) { System.out.println(请说一个字); String m scanner.nextLine(); // 发送的第一个字节是数据的长度 int len m.length(); os.write(len); for (int i 0; i len; i) { os.write(m.charAt(i)); } os.flush(); int len1 in.read();// 等待 阻塞 System.out.println(收到消息); for (int i 0; i len1; i) { int m1 in.read(); System.out.print((char) m1); } System.out.println(); } } catch (IOException e) { throw new RuntimeException(e); } } }B.javaimport java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class B { public static void main(String[] args) { // 套接字的服务类 // 54321 女方家门牌号 ServerSocket ss null;// 红娘 try { ss new ServerSocket(54321); } catch (IOException e) { throw new RuntimeException(e); } try { System.out.println(等待A来连接...); Socket s ss.accept(); System.out.println(连接成功~); // 对话 InputStream in s.getInputStream();// 输入流 读取对方的消息 OutputStream os s.getOutputStream();// 输出流 给对方发消息 Scanner scanner new Scanner(System.in); while (true) { int len in.read(); System.out.println(收到消息); for (int i 0; i len; i) { int m1 in.read();// 接受消息 System.out.print((char) m1);// 转成字符打印 } System.out.println(); System.out.println(请输入消息); String m scanner.nextLine(); int len1 m.length(); os.write(len1); for (int i 0; i len1; i) { os.write(m.charAt(i));// 写字到输入框中 } os.flush();// 点击发送按钮 } } catch (IOException e) { throw new RuntimeException(e); } } }