带界面的网络聊天Demo
基于目前学习的多线程、网络编程、图形用户界面编程的知识,编写一个简易的Demo,旨在复习和巩固学过的知识,温故而知新。
实现效果:
Thread部分 + Network部分
通过编写发送和接收两个线程来实现程序的同时接收和发送功能,多线程实现方式采用实现Runnable
接口。
线程中通过DatagramSocket
类和DatagramPacket
类来实现网络上的发送和接收。
Send类
package xyz.lilmoon.threads;
import java.net.*;
public class Send implements Runnable{
static DatagramSocket datagramSocket;
@Override
public void run() {
try {
datagramSocket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
}
// 提供发送方法,方便Button的事件监听器进行调用
public void send(String msg, String toIP, int toPort) {
// 获取输入
byte[] buffer = msg.getBytes();
try {
DatagramPacket datagramPacket = new DatagramPacket(buffer, 0, buffer.length, InetAddress.getByName(toIP), toPort);
datagramSocket.send(datagramPacket);
} catch (Exception e) {
e.printStackTrace();
}
// 发送"bye"后断开连接
if (msg.equals("bye")) datagramSocket.close();
}
}
Receive类
package xyz.lilmoon.threads;
import xyz.lilmoon.gui.Gui;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Receive implements Runnable{
Gui client;
int openPort;
DatagramSocket datagramSocket;
public Receive(Gui client, int openPort) {
this.client = client;
this.openPort = openPort;
try {
// 打开端口
datagramSocket = new DatagramSocket(openPort);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
byte[] buffer = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buffer, 0, buffer.length);
String msg;
do {
try {
datagramSocket.receive(datagramPacket);
} catch (IOException e) {
e.printStackTrace();
}
msg = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
client.receiveMsg.setText("to " + client.getTitle() + ":" + msg);
} while (!msg.equals("bye"));
// 发送"bye"后断开连接
datagramSocket.close();
}
}
GUI部分
通过AWT
类和Swing
类来构建程序界面及各类事件监听器。
package xyz.lilmoon.gui;
import xyz.lilmoon.threads.Receive;
import xyz.lilmoon.threads.Send;
import javax.swing.*;
import java.awt.*;
public class Gui extends JFrame {
int openPort;
Send sender;
// 将接收信息的文本框设为public,方便Receive类对其进行修改
public TextArea receiveMsg;
// 构造器
public Gui(int openPort){
sender = new Send();
new Thread(sender).start();
this.openPort = openPort;
// 通过this将frame传入Receive类中,使其能够修改其中的文本框信息
new Thread(new Receive(this, openPort)).start();
System.out.println("Construct");
}
// 初始化方法
public void init() {
// 设置窗口属性
setTitle("Client_" + openPort / 1111);
setBounds(200, 200, 250, 500);
setLayout(new FlowLayout());
// 创建控件
TextField toIP = new TextField("localhost", 12);
TextField toPort = new TextField(4);
TextArea input = new TextArea(10, 26);
Button send = new Button("Send");
receiveMsg = new TextArea(10, 26);
// 添加事件监听器
send.addActionListener(e -> {
String msg = input.getText();
String IP = toIP.getText();
int port = Integer.parseInt(toPort.getText());
// 发送信息
sender.send(msg, IP, port);
});
// 添加控件
add(new Label("toIP:"));
add(toIP);
add(new Label("toPort:"));
add(toPort);
add(new Label("Input:"));
add(input);
add(send);
add(receiveMsg);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
调用类
遵循编程规范,将可复用的代码都抽取出来,main方法中只负责启动程序。
创建两个类调用Gui类来创建窗体,启动程序。
Client_1:
public class Client_1 extends JFrame {
// main方法
public static void main(String[] args) {
new Gui(1111).init();
}
}
Client_2:
public class Client_2 extends JFrame {
// main方法
public static void main(String[] args) {
new Gui(2222).init();
}
}
可优化的地方(因为懒而没做的部分)
- 在Receive类中传入发送消息的frame参数,用于获取消息来源的用户名
- 设置消息队列,依次输出消息
- 设置发送消息后清空输入框