目录

一. 程序内容

二. 要求分析

三. 程序编写

0. 程序结构

1. 服务端程序的GUI设计

2. 服务端业务逻辑的编写

3. 为GUI界面绑定按钮事件

4. 将服务端的源码复制后,进行重构,并加以修改为客户端

四、源代码


一. 程序内容

这是合工大软件工程专业Java程序设计课程实验二的内容,该实验要求编写Java程序完成以下功能:

1. 设计一个基于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连接后,编程实现服务端、客户端之间的“单向通信”:在客户端的输入界面发送消息,在服务端接收该消息,并将接收到对方的数据追加显示在多行文本框中。

4. 在完成上述实验内容的基础上,尝试实现“双向通信”功能,即服务端、客户端之间可以相互发送、接收消息,并以此作为实验成绩评优的加分依据

二. 要求分析

总的来看,我们需要依次完成以下几个工作:

1. 服务端程序的GUI设计。

2. 服务端业务逻辑的编写。

3. 为GUI界面绑定按钮事件。

4. 将服务端的源码复制后,进行重构,并加以修改为客户端。

5. 测试服务端和客户端的连通性。

整理思路后,就可以开始编写我们的程序。

三. 程序编写

0. 程序结构

共三个类:

1. 主类Main,用于封装main函数。

2. 继承自JFrame的公共类ServerWindow,封装了服务端程序的GUI界面。

3. 继承自Thread的公共类Server,封装了服务端的业务逻辑。

Main类代码:

import javax.swing.*;public class Main {public static void main(String[] args) {ServerWindow mainWindow = new ServerWindow();}
}

1. 服务端程序的GUI设计

Ⅰ 原理介绍

Swing 是一个为Java设计的GUI工具包,提供了许多比AWT更精致的屏幕显示元素。支持可更换的面板和主题,缺点则是执行速度较慢,优点就是可以在所有平台上采用统一的样式和行为。

Java Swing 示例程序:Java Swing 介绍 | 菜鸟教程 (runoob.com)https://www.runoob.com/w3cnote/java-swing-demo-intro.html

Ⅱ 具体思路

整个GUI界面的结构如上图所示。

我们将界面分为上、中、下三个部分,分别使用三个JPanel包裹(为了方便布局,建议将组件放置于JPanel而非直接置于顶层容器JFrame中。),在ServerWindow类中也加入这些组件。

在ServerWindow类中如下声明所有的GUI组件:

    JPanel serverSettings;JTextField portField;JButton startBtn;JPanel areaPanel;JTextArea messageArea;JPanel sendPanel;JTextField sendField;JButton sendBtn;

其后,在该类的构造函数中需要对以上变量进行初始化:

public ServerWindow() {super("服务端");this.setSize(500,300);this.setResizable(false);this.setLayout(new BorderLayout());initializeServerSettings();initializeAreaPanel();initializeSendPanel();this.add(serverSettings,BorderLayout.NORTH);this.add(areaPanel,BorderLayout.CENTER);this.add(sendPanel,BorderLayout.SOUTH);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);}

为了优化代码可读性,将三个JPanel内组件的初始化代码单独列为private函数,如下:

    private void initializeServerSettings() {serverSettings = new JPanel();portField = new JTextField(30);startBtn = new JButton("Start");serverSettings.setBorder(new EmptyBorder(10, 5, 10, 5));serverSettings.add(new JLabel("Port:"));serverSettings.add(portField);serverSettings.add(startBtn);}private void initializeSendPanel() {sendPanel = new JPanel();sendBtn = new JButton("Send");sendField = new JTextField(30);sendPanel.setBorder(new EmptyBorder(10, 5, 10, 5));sendPanel.add(new JLabel("Send:"));sendPanel.add(sendField);sendPanel.add(sendBtn);}private void initializeAreaPanel() {areaPanel = new JPanel();messageArea = new JTextArea(9, 40);areaPanel.add(new JScrollPane(messageArea));}

至此,GUI的绘制部分就基本完成。

2. 服务端业务逻辑的编写

Ⅰ 原理 + 具体实现

整个服务端的业务逻辑,即从启动服务、等待连接、发送和接收消息、关闭连接均在Server类中完成。

关于WebSocket基本原理,在这片博文中有浅显易懂的解释,我在此就不再重复造轮子了:WebSocket 教程 - 阮一峰的网络日志 (ruanyifeng.com)https://www.ruanyifeng.com/blog/2017/05/websocket.html

在Java中,实现WebSocket通信,主要依靠java.net.Socket和java.net.ServerSocket两个类。

在服务端中,需要ServerSocket和Socket两个对象。

Socket client;
ServerSocket server;

ServerSocket用于在服务端计算机的指定端口建立一个监听服务,并回应随时可能到来的客户端请求。考虑如下的语句:

Socket LinkSocket = MyListener.accept();

该语句调用了ServerSocket对象的accept()方法,这个方法的执行将使Server端的程序处于等待状态,程序将一直阻塞(使用多线程的原因),直到捕捉到一个来自Client端的请求,并返回一个用于与该Client通信的Socket对象Link-Socket。此后Server程序只要向这个Socket对象读写数据,就可以实现向远端的Client读写数据。

打个简单的比方,SeverSocket就好比站在酒店门口的迎宾小姐,而Socket好比大堂内接待顾客的接待员,迎宾小姐的工作就是迎接到来的顾客,并交付给大堂内的接待员。

为了能将具体的连接信息和发送、接收的数据显示在GUI上,需要同时传入GUI界面中messageArea的引用。

JTextArea messageArea;

这一段的具体代码如下图所示:

server = new ServerSocket(port);
messageArea.append("- 服务已在端口 " + port + "上启动。\n");
//从ServerSocket等待新连接的Socket。
client = server.accept();
messageArea.append("- " + client.getInetAddress().getLocalHost() + " 已连接到服务。\n");

上述代码会阻塞程序,因此需要在新线程中运行。我选择使用继承Thread类的方式实现多线程。在Server类的构造函数中,完成对传入参数的处理后,便直接调用对象的start()函数,启动新线程。

    Server(int port,JTextArea msgArea) {this.port = port;this.messageArea = msgArea;this.start();}

Java多线程:Java多线程看这一篇就足够了(吐血超详细总结) - Java团长 - 博客园 (cnblogs.com)https://www.cnblogs.com/java1024/archive/2019/11/28/11950129.html

Socket对象有两个关键的方法,一个是getInputStream方法,另一个是getOutputStream方法。getInputStream方法可以得到一个输入流,服务端的Socket对象上的getInputStream方法得到的输入流其实就是从客户端发回的数据流。GetOutputStream方法得到一个输出流,服务端Socket对象上的getOutputStream方法返回的输出流就是将要发送到客户端的数据流,(其实是一个缓冲区,暂时存储将要发送过去的数据)。

BufferedReader br;
BufferedWriter bw;
InputStream is;
OutputStream os;

因此服务端与客户端的数据传输,需要依靠Socket对象的InputStream和OutputStream完成,具体如下实现:

is = client.getInputStream();
os = client.getOutputStream();
br = new BufferedReader(new InputStreamReader(is));
bw = new BufferedWriter(new OutputStreamWriter(os));
while(true) {String newMsg = br.readLine();if (newMsg != null) //意味着客户端发来了新消息。{messageArea.append(">> " + newMsg + "\n");}

上述代码中,通过不断读取InputStream,来得到客户端发送来的新消息,并将新消息显示在messageArea中。这段代码同样会阻塞程序。

因为在Socket连接中可能会发生异常,因此整段代码完整包裹在try语句中,并通过以下异常处理语句确定异常、显示异常消息:

            catch (IOException e) {e.printStackTrace();if (e instanceof java.net.ConnectException)messageArea.append("- 服务启动失败,请重试或更换端口。" + "\n");elsemessageArea.append("- 与客户端的连接已断开,服务停止。\n");} finally {try {server.close();//无论如何都应当调用} catch (IOException e) {e.printStackTrace();}}

无论如何,最后都应当调用server.close()语句,关闭ServerSocket对端口的占用。

最后,Server类还应当提供一个sendMsg方法,用于向客户端主动发送信息:

public void sendMsg(String msg) {System.out.println("sendMsg");try {bw.write(msg + "\n");//务必在一条信息后加上换行符,代表发送完成。bw.flush();messageArea.append("<< " + msg + "\n");} catch (IOException e) {e.printStackTrace();}}

3. 为GUI界面绑定按钮事件

下面,我们回到GUI界面中,为其中的两个按钮绑定事件。

首先是启动服务的Start按钮,按下按钮时,应当创建一个新的Server对象,传入端口号和messageArea组件,如下:

        startBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {try {int port = Integer.parseInt(portField.getText());server = new Server(port,messageArea);}catch(java.lang.NumberFormatException exception) {messageArea.append("- 端口格式有误,请重新输入。\n");}System.out.println(portField.getText());}});

其后是发送消息的Send按钮,按下按钮后,调用Server对象的sendMsg方法,传入要发送的信息,如下:

        sendBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {server.sendMsg(sendField.getText());sendField.setText("");}});

4. 将服务端的源码复制后,进行重构,并加以修改为客户端

至此,服务器端的代码全部完成了。客户端的代码只需要在服务端的基础上稍加修改即可完成。

这里建议使用IDEA的代码重构功能,在需要修改的类名、变量名上右键,使用重构 - 重命名,即可将整个代码中所有出现的该标识符自动替换为新名字。

Client端除了不需要ServerSocket以外,具体业务逻辑与Server端基本一致,这里就不再细说,建议直接参照源码变化。

四、源代码

Server.Java

package exp.server;import javax.swing.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.Buffer;
import java.util.ArrayList;//服务器类,用于处理最初的PortWaiter的创建任务以及向客户端发送消息。
class Server extends Thread{Socket client;ServerSocket server;JTextArea messageArea;BufferedReader br;BufferedWriter bw;InputStream is;OutputStream os;int port;Server(int port,JTextArea msgArea) {this.port = port;this.messageArea = msgArea;this.start();}@Overridepublic void run() {super.run();try {server = new ServerSocket(port);messageArea.append("- 服务已在端口 " + port + "上启动。\n");//从ServerSocket等待新连接的Socket。client = server.accept();messageArea.append("- " + client.getInetAddress().getLocalHost() + " 已连接到服务。\n");is = client.getInputStream();os = client.getOutputStream();br = new BufferedReader(new InputStreamReader(is));bw = new BufferedWriter(new OutputStreamWriter(os));while(true) {String newMsg = br.readLine();if (newMsg != null) {messageArea.append(">> " + newMsg + "\n");}}} catch (IOException e) {e.printStackTrace();if (e instanceof java.net.ConnectException)messageArea.append("- 服务启动失败,请重试或更换端口。" + "\n");elsemessageArea.append("- 与客户端的连接已断开,服务停止。\n");} finally {try {server.close();} catch (IOException e) {e.printStackTrace();}}}public void sendMsg(String msg) {System.out.println("sendMsg");try {bw.write(msg + "\n");bw.flush();messageArea.append("<< " + msg + "\n");} catch (IOException e) {e.printStackTrace();}}
}

ServerWindow.Java

package exp.server;import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;public class ServerWindow extends JFrame{JPanel serverSettings;JTextField portField;JButton startBtn;JPanel areaPanel;JTextArea messageArea;JPanel sendPanel;JTextField sendField;JButton sendBtn;Server server;public ServerWindow() {super("服务端");this.setSize(500,300);this.setResizable(false);this.setLayout(new BorderLayout());initializeServerSettings();initializeAreaPanel();initializeSendPanel();this.add(serverSettings,BorderLayout.NORTH);this.add(areaPanel,BorderLayout.CENTER);this.add(sendPanel,BorderLayout.SOUTH);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);startBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {try {int port = Integer.parseInt(portField.getText());server = new Server(port,messageArea);}catch(java.lang.NumberFormatException exception) {messageArea.append("- 端口格式有误,请重新输入。\n");}System.out.println(portField.getText());}});sendBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {server.sendMsg(sendField.getText());sendField.setText("");}});}private void initializeServerSettings() {serverSettings = new JPanel();portField = new JTextField(30);startBtn = new JButton("Start");serverSettings.setBorder(new EmptyBorder(10, 5, 10, 5));serverSettings.add(new JLabel("Port:"));serverSettings.add(portField);serverSettings.add(startBtn);}private void initializeSendPanel() {sendPanel = new JPanel();sendBtn = new JButton("Send");sendField = new JTextField(30);sendPanel.setBorder(new EmptyBorder(10, 5, 10, 5));sendPanel.add(new JLabel("Send:"));sendPanel.add(sendField);sendPanel.add(sendBtn);}private void initializeAreaPanel() {areaPanel = new JPanel();messageArea = new JTextArea(9, 40);areaPanel.add(new JScrollPane(messageArea));}}

Client.Java

package exp.server;import javax.swing.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;//服务器类,用于处理最初的PortWaiter的创建任务以及向客户端发送消息。
class Client extends Thread{Socket server;JTextArea messageArea;BufferedReader br;BufferedWriter bw;InputStream is;OutputStream os;int port;String address;Client(int port, JTextArea msgArea, String address) {this.port = port;this.messageArea = msgArea;this.address = address;this.start();}@Overridepublic void run() {super.run();try {server = new Socket(address, port);messageArea.append("- 已连接到主机 " + server.getInetAddress().getLocalHost() + "\n");is = server.getInputStream();os = server.getOutputStream();br = new BufferedReader(new InputStreamReader(is));bw = new BufferedWriter(new OutputStreamWriter(os));while(true) {String newMsg = br.readLine();if (newMsg != null) {messageArea.append(">> " + newMsg + "\n");}}} catch (IOException e) {e.printStackTrace();if(e instanceof java.net.ConnectException)messageArea.append("- 无法连接到主机,请重试或检查地址和端口。" + "\n");elsemessageArea.append("- 与远程主机的连接已断开。\n");}}public void sendMsg(String msg) {System.out.println("sendMsg");try {bw.write(msg + "\n");bw.flush();messageArea.append("<< " + msg + "\n");} catch (IOException e) {e.printStackTrace();}}
}

ClientWindow.Java

package exp.server;import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;public class ClientWindow extends JFrame{JPanel clientSettings;JTextField addressField;JTextField portField;JButton connectBtn;JPanel areaPanel;JTextArea messageArea;JPanel sendPanel;JTextField sendField;JButton sendBtn;Client client;public ClientWindow() {super("客户端");this.setSize(500,300);this.setResizable(false);this.setLayout(new BorderLayout());initializeServerSettings();initializeAreaPanel();initializeSendPanel();this.add(clientSettings,BorderLayout.NORTH);this.add(areaPanel,BorderLayout.CENTER);this.add(sendPanel,BorderLayout.SOUTH);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);connectBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {try {int port = Integer.parseInt(portField.getText());client = new Client(port,messageArea, addressField.getText());}catch(java.lang.NumberFormatException exception) {messageArea.append("- 端口格式有误,请重新输入。\n");}System.out.println(portField.getText());}});sendBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println(sendField.getText());client.sendMsg(sendField.getText());sendField.setText("");}});}private void initializeServerSettings() {clientSettings = new JPanel();addressField = new JTextField(20);portField = new JTextField(10);connectBtn = new JButton("Connect");clientSettings.setBorder(new EmptyBorder(10, 5, 10, 5));clientSettings.add(new JLabel("IP:"));clientSettings.add(addressField);clientSettings.add(new JLabel("Port:"));clientSettings.add(portField);clientSettings.add(connectBtn);}private void initializeSendPanel() {sendPanel = new JPanel();sendBtn = new JButton("Send");sendField = new JTextField(30);sendPanel.setBorder(new EmptyBorder(10, 5, 10, 5));sendPanel.add(new JLabel("Send:"));sendPanel.add(sendField);sendPanel.add(sendBtn);}private void initializeAreaPanel() {areaPanel = new JPanel();messageArea = new JTextArea(9, 40);areaPanel.add(new JScrollPane(messageArea));}}

【Java】基于GUI的网络通信程序设计相关推荐

  1. Java基于GUI的网络通信程序设计【电竞杜小帅】

    1.设计一个基于GUI的客户-服务器的通信应用程序,如图1,图2所示. 图1 Socket通信服务器端界面 图2 Socket通信客户端界面 2.图1为Socket通信服务器端界面,点击该界面中的[S ...

  2. 合肥工业大学宣城校区Java技术实验二 基于GUI的网络通信程序设计

    一.实验目的 1.掌握Java中GUI程序的编写,包括事件监听机制. 2.掌握Java的网络通信编程,ServerSocket,Socket类的使用. 3.掌握Java中多线程的编程,Thread类, ...

  3. 基于GUI的网络通信程序设计(Java)

    使用 socket 实现了服务端与多个客户端之间的双向通信 服务端 package exp;import java.awt.*; import java.awt.event.ActionEvent; ...

  4. 基于GUI的网络通信设计

    一.实验目的 ​ 1.掌握Java中GUI程序的编写,包括事件监听机制. ​ 2.掌握Java的网络通信编程,ServerSocket,Socket类的使用. ​ 3.掌握Java中多线程的编程,Th ...

  5. Java基于GUI完成的猜数字小游戏

    猜数字小游戏 不废话想看看效果吧! 当我们答对后就会出现: 如果你实在急着想知道答案: 简要说明: 所猜测的数字为1~1000: 只能通过按确定输出答案,未设置Enter键直接输入的监控. 退出请直接 ...

  6. 【java毕业设计】基于java+swing+GUI的连连看游戏设计与实现(毕业论文+程序源码)——连连看游戏

    基于java+swing+GUI的连连看游戏设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+swing+GUI的连连看游戏设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦 ...

  7. java毕业设计——基于java+Socket+sqlserver的网络通信系统设计与实现(毕业论文+程序源码)——网络通信系统

    基于java+Socket+sqlserver的网络通信系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+Socket+sqlserver的网络通信系统设计与实现,文章末尾附有 ...

  8. 【java毕业设计】基于java+swing+GUI的雷电游戏GUI设计与实现(毕业论文+程序源码)——雷电游戏

    基于java+swing+GUI的雷电游戏GUI设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+swing+GUI的雷电游戏GUI设计与实现,文章末尾附有本毕业设计的论文和源码下 ...

  9. 【JAVA小游戏+水果售卖系统】基于GUI界面编程的水果“人生”模拟系统

    [JAVA]基于GUI界面编程的水果"人生"模拟系统 一.系统主要功能及简介 二.系统体系结构 三.系统设计技术 四.编码说明 五.效果展示 一.系统主要功能及简介 该系统以JAV ...

最新文章

  1. 使用beanUtils操纵javabean
  2. Javascript各种事件汇总
  3. Java学习笔记——面向对象
  4. oracle视图view看不出来主键,oracle - 使用主键创建视图
  5. WPF中使用流文档灵活地显示内容
  6. 2017.9.15 postgresql批量插入造成冲突后执行更新
  7. 问题 A: 百钱买百鸡问题
  8. 苹果 Siri 被曝涉嫌泄露用户隐私;中国联通回应 5G 入网问题;PHP 7.4 beta 1 发布 | 极客头条...
  9. 1. jenkins常见错误及解决方法
  10. 产品经理学习记录(一)
  11. Plc编程入门基础知识,在短时间内如何学会编程
  12. 新手CrossApp 之CollectionView小结
  13. 双绞线连接布线方案(计算机网络)
  14. Mac突然连接不上WiFi的问题
  15. 爬虫实战:英雄联盟手游能“干掉”王者荣耀?微博4.3亿网友吵翻了……
  16. 【解决方案】windows7无法启动黑屏,报0xc000014c错误解决方案不用重新安系统
  17. 粗糙集的概念和一些例子
  18. iOS 16 NSTextContentStorage locationFromLocation:withOffset: 崩溃问题
  19. AutoMapper 的使用
  20. 一男子连开28个黄网被捕,网友:就这点钱,你还是找个班上吧

热门文章

  1. Java线程池系列--线程池的种类(Executors的用法)
  2. Java安全(十五) 一些链子之C3P0
  3. 事业单位计算机专业知识
  4. AGPBI: {“kind“:“error“,“text“:“Program type already present:的解决方法
  5. Hack radio【小尝试总结】
  6. hello world、hello 计科人
  7. css标签鼠标移动事件,CSS鼠标响应事件经过、移动、点击示例介绍
  8. win10修改启动项
  9. 零基础编程学python还是java_零基础学python还是java 二者有哪些区别
  10. 仿抖音 火山视频滑动播放