栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Java】基于GUI的网络通信程序设计

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java】基于GUI的网络通信程序设计

目录

一. 程序内容

二. 要求分析

三. 程序编写

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");
                else
                    messageArea.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() {
            @Override
            public 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() {
            @Override
            public 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();
    }

    @Override
    public 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");
                else
                    messageArea.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() {
            @Override
            public 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() {
            @Override
            public 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();
    }

    @Override
    public 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");
            else
                messageArea.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() {
            @Override
            public 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() {
            @Override
            public 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));
    }

}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/281065.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号