[小练习]100行不到使用Java Socket网络编程实现定向聊天
先写个TCP的:
一、效果展示
二、整体结构
文件名 | 作用 |
---|---|
Send.java | 客户端入口,连接 127.0.0.1:8888 |
Rece.java | 服务端入口,监听 8888 端口 |
Sen.java | 发送线程:把键盘输入写给对端 |
Rec.java | 接收线程:把对端消息打印出来 |
三、运行步骤
-
编译
javac *.java
-
启动服务
java Rece
-
启动客户端(另开终端
java Send
-
任意一方输入
quit
即可退出。
四、核心代码
1. 服务端 Rece.javaU26/Test2/Rece.java · 伏琪/java基础语法练习 - 码云 - 开源中国
https://gitee.com/fuqiqiqi/test/blob/master/U26/Test2/Rece.java
public class Rece {public static void main(String[] args) {try (ServerSocket ss = new ServerSocket(8888)) {Socket s = ss.accept();System.out.println("成功连接");new Sen(s).start(); // 发new Rec(s).start(); // 收// 等待两线程结束再关 Socketwhile (Sen.isAlive() || Rec.isAlive()) Thread.sleep(100);} catch (IOException | InterruptedException e) {e.printStackTrace();}}
}
2. 客户端 Send.java
U26/Test2/Rece.java · 伏琪/java基础语法练习 - Gitee.comhttps://gitee.com/fuqiqiqi/test/blob/master/U26/Test2/Rece.java
public class Send {public static void main(String[] args) {try (Socket socket = new Socket("127.0.0.1", 8888)) {System.out.println("成功连接");Sen sen = new Sen(socket);Rec rec = new Rec(socket);sen.start();rec.start();sen.join();rec.join();} catch (Exception e) {e.printStackTrace();}}
}
3. 发送线程 Sen.java
U26/Test2/Sen.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test2/Sen.java
public class Sen extends Thread {private final Socket s;Sen(Socket s) { this.s = s; }@Overridepublic void run() {try (Scanner sc = new Scanner(System.in);OutputStream out = s.getOutputStream()) {while (sc.hasNextLine()) {String line = sc.nextLine();out.write((line + "\n").getBytes(StandardCharsets.UTF_8));if ("quit".equalsIgnoreCase(line)) break;}} catch (IOException ignored) {}}
}
4. 接收线程 Rec.java
U26/Test2/Rec.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test2/Rec.java
public class Rec extends Thread {private final Socket s;Rec(Socket s) { this.s = s; }@Overridepublic void run() {try (BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8))) {String line;while ((line = br.readLine()) != null) {System.out.printf("%40s%n", line);}} catch (IOException ignored) {}}
}
五、线程模型图
六、心得
在写输出时,接受的消息向右对齐格式化数据,应该用printf()而不是print()。
最难的点在于不知道在哪里关闭Socket,一开始写在了工具类里,发现另一个关不了,只能写在了主函数里,在将其两个线程都运行完后关闭。
再来个UDP的,比TCP多占用一个端口:
一、效果展示
二、整体结构(4 个文件)
文件 | 作用 |
---|---|
Send.java | 客户端入口:监听 8889 接收,发向 8888 |
Rece.java | 服务端入口:监听 8888 接收,发向 8889 |
Sen.java | 发送线程:把键盘输入打成 UDP 包 |
Rec.java | 接收线程:把 UDP 包右对齐打印 |
三、运行步骤
-
编译
javac *.java
-
启动服务
java Rece
-
启动客户端(另开终端
java Send
-
任意一方输入
quit
即可优雅退出。
四、核心源码
1. 客户端 Send.java
U26/Test3/Send.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test3/Send.java
public class Send {public static void main(String[] args) throws Exception {int listen = 8889; // 我收int target = 8888; // 我发DatagramSocket recvSocket = new DatagramSocket(listen);DatagramSocket sendSocket = new DatagramSocket();Rec rec = new Rec(recvSocket, listen);Sen sen = new Sen(sendSocket, target);rec.start();sen.start();rec.join();sen.join();recvSocket.close();sendSocket.close();System.out.println("聊天结束");}
}
2. 服务端 Rece.java
U26/Test3/Rece.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test3/Rece.java
public class Rece {public static void main(String[] args) throws Exception {int listen = 8888; // 我收int target = 8889; // 我发DatagramSocket recvSocket = new DatagramSocket(listen);DatagramSocket sendSocket = new DatagramSocket();Rec rec = new Rec(recvSocket, listen);Sen sen = new Sen(sendSocket, target);rec.start();sen.start();rec.join();sen.join();recvSocket.close();sendSocket.close();System.out.println("聊天结束");}
}
3. 发送线程 Sen.java
U26/Test3/Sen.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test3/Sen.java
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;public class Sen extends Thread {private final DatagramSocket socket;private final int targetPort;public Sen(DatagramSocket socket, int targetPort) {this.socket = socket;this.targetPort = targetPort;}@Overridepublic void run() {try (Scanner sc = new Scanner(System.in)) {while (true) {String line = sc.nextLine();byte[] data = line.getBytes(StandardCharsets.UTF_8);DatagramPacket packet = new DatagramPacket(data, data.length,InetAddress.getByName("127.0.0.1"), targetPort);socket.send(packet);if ("quit".equalsIgnoreCase(line)) break;}} catch (IOException e) {e.printStackTrace();}}
}
4. 接收线程 Rec.java
U26/Test3/Rec.java · 伏琪/java基础语法练习 - 码云 - 开源中国https://gitee.com/fuqiqiqi/test/blob/master/U26/Test3/Rec.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;public class Rec extends Thread {private final DatagramSocket socket;private final int localPort;public Rec(DatagramSocket socket, int localPort) {this.socket = socket;this.localPort = localPort;}@Overridepublic void run() {byte[] buf = new byte[1024];DatagramPacket packet = new DatagramPacket(buf, buf.length);try {while (!isInterrupted()) {socket.receive(packet);String msg = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);System.out.printf("%40s%n", msg);if ("quit".equalsIgnoreCase(msg)) break;}} catch (IOException ignored) {} finally {socket.close();}}
}
五、线程模型图
六,心得
起初认为UDP只能有一个本地的来接收信息,所以我之前任务只能单向聊天,后来想到可以用不同端口监听消息,就可以实现多人聊天了。
不清楚为什么在写工具类时类的Datagramet成员访问不到,设置public都不行,最后加了个this才访问到。
接受消息时传入的Scoket可以不用传地址;
因为不像TCP只有一个Scoket,当一个暂停时,一方只能收,一方只能听,因此传两个Scoket,可以都暂停。
我是伏琪,关注订阅号伏琪了解更多。