一.实验目的
1.掌握Java中GUI程序的编写,包括事件监听机制。
2.掌握Java的网络通信编程,ServerSocket,Socket类的使用。
3.掌握Java中多线程的编程,Thread类,Runnable接口的使用。
4.掌握用面向对象的方法分析和解决复杂问题。
二.实验内容
编写程序完成以下功能:
- 设计一个基于GUI的客户-服务器的通信应用程序,如图1,图2所示。
图1 Socket通信服务器端界面 | 图2 Socket通信客户端界面 |
2.图1为Socket通信服务器端界面,点击该界面中的【Start】按钮,启动服务器监听服务(在图1界面中间的多行文本区域显示“Server starting…”字样)。图2为Socket通信客户端界面,点击该界面中的【Connect】按钮与服务器建立链接,并在图2所示界面中间的多行文本区域显示“Connect to server…”字样,当服务器端监听到客户端的连接后,在图1界面中间的多行文本区域追加一行“Client connected…”字样,并与客户端建立Socket连接。
3.当图1所示的服务器端和图2所示的客户机端建立Socket连接后,编程实现服务端、客户端之间的“单向通信”:在客户端的输入界面发送消息,在服务端接收该消息,并将接收到对方的数据追加显示在多行文本框中。
希望同学们学习其中的思路,而不是直接复制粘贴代码。
使用Socket传递消息的机制
服务端首先通过一个端口(port)来创建一个ServerSocket,于是在此端口上可以传递信息。
serverSocket = new ServerSocket(port);
同时服务端也需要创建一个Socket(clientSocket),来接受客户端通过Socket传递来的流(输入输出流)
服务器端通过ServerSocket的accept方法等待客户端连接
一旦有客户端连接成功,serverSocket.accept()
将返回一个 Socket
对象,表示与客户端建立的连接。这个 Socket
对象被赋值给 clientSocket
变量。
通过 clientSocket.getOutputStream()
获取客户端的输出流。这样就可以通过 bufferedWriter
向客户端发送数据。
通过 clientSocket.getInputStream()
获取客户端的输入流。这样就可以通过 bufferedReader
从客户端接收数据。
clientSocket = serverSocket.accept();
ServerFrame.textArea.append("Client connected…" + "\n");
bufferedWriter = new BufferedWriter(
new OutputStreamWriter(clientSocket.getOutputStream()));
bufferedReader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
客户端接收和发送消息
socket=new Socket(serverIP,port);
this.bufferedReader=new BufferedReader(
new InputStreamReader(socket.getInputStream()));
this.bufferedWriter=new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream()));
首先创建一个名为 socket
的 Socket
对象,用于与服务端建立连接。构造方法中传入了服务端的 IP 地址 serverIP
和端口号 port
。
然后
通过 socket.getInputStream()
获取服务端的输入流,通过socket.getOutputStream()获取服务端的输出流,这样就可以通过 bufferedReader
从服务端接收数据,通过 bufferedWriter
向服务端发送数据。
设计思路
1.设计Client和Server的图形化界面,分别在ClientFrame和ServerFrame类中实现。如图
2. 设计Server类和Client类,图形化界面的具体功能(从port框中获取端口,开启服务器、服务器端发送消息,客户端输入ip、端口号与主机连接、客户端发送信息)在这两个类中实现。
同时设计了一些对用户输入错误信息、错误操作的处理,更符合实际需要。如:服务器页面不输入port直接点start提示用户要输入端口值,客户端只输入ServerIP和ServerPort其中的一项或者都没输入,提示用户输入。如图
服务器端没有开启时,客户端就连接,提示
ServerFrame类中显示消息的框设置为public static,在Server类中接受到信息后需要在框中显示新的信息。ClientFrame类中同理。
3. 为图形化的界面按钮等添加监听器,实现具体功能。
测试结果如下:
ClientFrame类的设计
这个使用了JFormDesigner来设计,省去了很多麻烦
如图 可以在框中任意添加和拖动各种组件,很方便,同时也可以设置字体、大小等。
如果想添监听器,就对着组件右键,有各种监听器,可以直接生成监听函数
更多关于JFormDesigner请自己探索
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
/*
* Created by JFormDesigner on Sat Apr 08 19:21:43 CST 2023
*/
/**
* @author archi
*/
public class ClientFrame {
public ClientFrame() {
initComponents();
}
private Client client;
private String server_IP;
private int server_Port=-1;//初始化为-1,表示未输入
private String msgToSend;
private void bt_connectMouseClicked(MouseEvent e) {
if(e.getButton()==1){
if(server_IP==null) {
textArea.append("请输入Server IP!"+"\n");
} else if (server_Port==-1) {
textArea.append("请输入Server Port!"+"\n");
}else{
client=new Client(server_IP,server_Port);
textArea.append("Connect to server…"+"\n");
client.listenForMessage();
}
}
}
//用于监听输入ip的框
private void ipFieldKeyReleased(KeyEvent e) {
if(e.getSource()==ipField){
server_IP= ipField.getText();
}
}
//监听输入serverPort的框
private void portFieldKeyReleased(KeyEvent e) {
if(e.getSource()==portField){
server_Port= Integer.parseInt(portField.getText());
}
}
//监听发送信息的框
private void wordsFieldKeyReleased(KeyEvent e) {
if(e.getSource()==wordsField){
msgToSend=wordsField.getText();//获取框中的信息
}
}
//监听发送的框
private void bt_sayMouseClicked(MouseEvent e) {
if(e.getButton()==1){
if(msgToSend!=null){
textArea.append(msgToSend+"\n");//在显示消息的区域附加新的信息
client.sendMessage(msgToSend);
wordsField.setText(null);//发送后设置发送框为空
msgToSend=null;//设置发送的信息为空
}
}
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
ServerUI = new JFrame();
panel1 = new JPanel();
serverIP = new JLabel();
ipField = new JTextField();
serverPort = new JLabel();
portField = new JTextField();
bt_connect = new JButton();
scrollPane1 = new JScrollPane();
textArea = new JTextArea();
wordsField = new JTextField();
bt_say = new JButton();
//======== ServerUI ========
{
ServerUI.setTitle("\u5ba2\u6237\u7aef");
Container ServerUIContentPane = ServerUI.getContentPane();
ServerUIContentPane.setLayout(null);
//======== panel1 ========
{
panel1.setBorder(new TitledBorder("\u5ba2\u6237\u7aef\u8bbe\u7f6e\uff1a"));
panel1.setLayout(null);
//---- serverIP ----
serverIP.setText("Server IP:");
serverIP.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
panel1.add(serverIP);
serverIP.setBounds(10, 30, 110, serverIP.getPreferredSize().height);
//---- ipField ----
ipField.setFont(new Font("JetBrains Mono Medium", Font.PLAIN, 16));
ipField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
ipFieldKeyReleased(e);
}
});
panel1.add(ipField);
ipField.setBounds(115, 25, 145, 35);
//---- serverPort ----
serverPort.setText("Server Port:");
serverPort.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
panel1.add(serverPort);
serverPort.setBounds(270, 30, 130, 27);
//---- portField ----
portField.setFont(new Font("JetBrains Mono Medium", Font.PLAIN, 16));
portField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
portFieldKeyReleased(e);
}
});
panel1.add(portField);
portField.setBounds(390, 25, 145, 35);
//---- bt_connect ----
bt_connect.setText("Connect");
bt_connect.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
bt_connect.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
bt_connectMouseClicked(e);
}
});
panel1.add(bt_connect);
bt_connect.setBounds(545, 25, bt_connect.getPreferredSize().width, 35);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < panel1.getComponentCount(); i++) {
Rectangle bounds = panel1.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = panel1.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
panel1.setMinimumSize(preferredSize);
panel1.setPreferredSize(preferredSize);
}
}
ServerUIContentPane.add(panel1);
panel1.setBounds(15, 10, 655, 70);
//======== scrollPane1 ========
{
//---- textArea ----
textArea.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
textArea.setEditable(false);
scrollPane1.setViewportView(textArea);
}
ServerUIContentPane.add(scrollPane1);
scrollPane1.setBounds(15, 90, 655, 280);
//---- wordsField ----
wordsField.setFont(new Font("JetBrains Mono Medium", Font.PLAIN, 16));
wordsField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
wordsFieldKeyReleased(e);
}
});
ServerUIContentPane.add(wordsField);
wordsField.setBounds(10, 390, 560, 45);
//---- bt_say ----
bt_say.setText("Say");
bt_say.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
bt_say.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
bt_sayMouseClicked(e);
}
});
ServerUIContentPane.add(bt_say);
bt_say.setBounds(585, 395, 100, 40);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < ServerUIContentPane.getComponentCount(); i++) {
Rectangle bounds = ServerUIContentPane.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = ServerUIContentPane.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
ServerUIContentPane.setMinimumSize(preferredSize);
ServerUIContentPane.setPreferredSize(preferredSize);
}
ServerUI.setSize(705, 500);
ServerUI.setLocationRelativeTo(ServerUI.getOwner());
}
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
ServerUI.setVisible(true);
ServerUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JFrame ServerUI;
private JPanel panel1;
private JLabel serverIP;
private JTextField ipField;
private JLabel serverPort;
private JTextField portField;
private JButton bt_connect;
private JScrollPane scrollPane1;
public static JTextArea textArea;
private JTextField wordsField;
private JButton bt_say;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
public static void main(String[] args) {
new ClientFrame();
}
}
Client类设计
public class Client {
Socket socket;
private BufferedReader bufferedReader;
private BufferedWriter bufferedWriter;
private final String username="LocalClient";
public Client(String serverIP,int port){
try{
socket=new Socket(serverIP,port);
//初始化输入流缓冲区和输出流缓冲区,注意这里使用的是socket传递来的输入输出流
//这样客户端就可以接受服务器端的消息(从输入流读入),向服务器端发送消息(向输出流写入)
this.bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.bufferedWriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
}catch (IOException e){
closeEverything();
}
}
//发送信息时将信息写入到输出流即可
public void sendMessage(String msgToSend) {
try {
bufferedWriter.write(username+": "+msgToSend);
bufferedWriter.newLine();//每次写完一行后换行
bufferedWriter.flush();//将缓冲区中的内容刷新到道输出流中
} catch (IOException e) {
closeEverything();
}
}
//为client创建接收信息的新线程
public void listenForMessage() {
new Thread(() -> {
try {
while(socket.isConnected()) {
//接受到的信息显示在显示消息的那个框里
ClientFrame.textArea.append(bufferedReader.readLine()+"\n");
}
} catch (IOException e) {
closeEverything();
}
}
).start();
}
//如果程序运行过程中发生错误,调用这个函数,关掉所有用到的东西,接受程序
public void closeEverything() {
try {
if (socket != null) {
socket.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerFrame的设计
这个与ClientFrame的设计方法基本一样
直接贴代码
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.border.*;
/*
* Created by JFormDesigner on Sat Apr 08 17:35:15 CST 2023
*/
/**
* @author archi
*/
public class ServerFrame {
private String server_Port;
private Server server;
private String msgToSend;
public ServerFrame() {
initComponents();
}
//点击start开始的框
private void bt_startMouseClicked(MouseEvent e) {
if (e.getButton() == 1) {//点击左键
try {
if (server_Port == null) {
textArea.append("未输入端口值!" + "\n");
} else {
ServerFrame.textArea.append("Server starting..." + "\n");
server = new Server(Integer.parseInt(server_Port));
}
server.startServer();
} catch (IOException e1) {
e1.printStackTrace();
server.closeEverything();
}
}
}
//输入端口值的框
private void portFieldKeyReleased(KeyEvent e) {
if(e.getSource()==portField){
this.server_Port=portField.getText();//端口值
}
}
private void messageFieldKeyReleased(KeyEvent e) {
if(e.getSource()==messageField){
msgToSend=messageField.getText();
}
}
//点击发送后将输入框中的内容置空
private void bt_sayMouseClicked(MouseEvent e) {
if(e.getButton()==1){//点击左键
if(msgToSend!=null){
server.sendMessage(msgToSend);
textArea.append(msgToSend+"\n");
msgToSend=null;
messageField.setText(null);
}
}
}
//say发送信息的框
private void bt_sayKeyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_ENTER){
if(msgToSend!=null){
server.sendMessage(msgToSend);
textArea.append(msgToSend+"\n");
msgToSend=null;
messageField.setText(null);
}
}
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
ServerUI = new JFrame();
scrollPane1 = new JScrollPane();
textArea = new JTextArea();
panel1 = new JPanel();
label1 = new JLabel();
portField = new JTextField();
bt_start = new JButton();
messageField = new JTextField();
bt_say = new JButton();
//======== ServerUI ========
{
ServerUI.setTitle("\u670d\u52a1\u5668");
Container ServerUIContentPane = ServerUI.getContentPane();
ServerUIContentPane.setLayout(null);
//======== scrollPane1 ========
{
//---- textArea ----
textArea.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
textArea.setEditable(false);
scrollPane1.setViewportView(textArea);
}
ServerUIContentPane.add(scrollPane1);
scrollPane1.setBounds(25, 95, 650, 270);
//======== panel1 ========
{
panel1.setBorder(new TitledBorder("\u670d\u52a1\u5668\u8bbe\u7f6e\uff1a"));
panel1.setToolTipText("\u670d\u52a1\u5668\u8bbe\u7f6e\uff1a");
panel1.setLayout(null);
//---- label1 ----
label1.setText("port:");
label1.setFont(new Font("JetBrains Mono Medium", Font.BOLD, 16));
panel1.add(label1);
label1.setBounds(10, 30, 60, 30);
//---- portField ----
portField.setFont(new Font("JetBrains Mono Medium", Font.PLAIN, 16));
portField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
portFieldKeyReleased(e);
}
});
panel1.add(portField);
portField.setBounds(60, 25, 465, 40);
//---- bt_start ----
bt_start.setText("Start");
bt_start.setFont(new Font("Microsoft YaHei UI", Font.BOLD, 16));
bt_start.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
bt_startMouseClicked(e);
}
});
panel1.add(bt_start);
bt_start.setBounds(530, 25, 90, 35);
{
// compute preferred size
Dimension preferredSize = new Dimension();
for(int i = 0; i < panel1.getComponentCount(); i++) {
Rectangle bounds = panel1.getComponent(i).getBounds();
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
}
Insets insets = panel1.getInsets();
preferredSize.width += insets.right;
preferredSize.height += insets.bottom;
panel1.setMinimumSize(preferredSize);
panel1.setPreferredSize(preferredSize);
}
}
ServerUIContentPane.add(panel1);
panel1.setBounds(30, 0, 640, 85);
//---- messageField ----
messageField.setFont(new Font("JetBrains Mono Medium", Font.PLAIN, 16));
messageField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
messageFieldKeyReleased(e);
}
});
ServerUIContentPane.add(messageField);
messageField.setBounds(25, 380, 535, 40);
//---- bt_say ----
bt_say.setText("Say");
bt_say.setFont(new Font("Microsoft YaHei UI", Font.BOLD, 16));
bt_say.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
bt_sayMouseClicked(e);
}
});
bt_say.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
bt_sayKeyPressed(e);
}
});
ServerUIContentPane.add(bt_say);
bt_say.setBounds(580, 380, 95, 40);
ServerUIContentPane.setPreferredSize(new Dimension(700, 475));
ServerUI.setSize(700, 475);
ServerUI.setLocationRelativeTo(ServerUI.getOwner());
}
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
ServerUI.setVisible(true);
ServerUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
private JFrame ServerUI;
private JScrollPane scrollPane1;
public static JTextArea textArea;
private JPanel panel1;
private JLabel label1;
private JTextField portField;
private JButton bt_start;
private JTextField messageField;
private JButton bt_say;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
public static void main(String[] args) {
new ServerFrame();
}
}
Server的设计
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author archi
*/
public class Server {
BufferedWriter bufferedWriter;
BufferedReader bufferedReader;
Socket clientSocket;
ServerSocket serverSocket;
public Server(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void startServer() {
new Thread(() -> {
try {
clientSocket = serverSocket.accept();
ServerFrame.textArea.append("Client connected…" + "\n");
bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
closeEverything();
}
try {
while (clientSocket.isConnected()) {
String line = bufferedReader.readLine();
ServerFrame.textArea.append(line + "\n");//将读到的信息添加到textArea中
}
} catch (IOException e) {
closeEverything();
e.printStackTrace();
}
}
).start();
}
public void sendMessage(String msgToSend) {
try {
bufferedWriter.write("Server: " + msgToSend);
bufferedWriter.newLine();
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
closeEverything();
}
}
public void closeEverything() {
try {
if (bufferedWriter != null) {
bufferedWriter.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
主类
public class Main {
public static void main(String[] args) {
new ServerFrame();
new ClientFrame();
}
}