• 重构本地聊天程序


    重构本地聊天程序

    0.0、前言

    ​ 人们对事物总是报以崇高的期许。

    ​ 这并不算特别优秀的作品、代码不够优雅、前端界面过于简单、不能完全解耦、等等等等,但相比于以前好了太多太多,模块功能简洁明了、业务逻辑足够清晰、代码尽量解耦且不臃肿、最重要的注释足够多、用我现在看以前的代码、确实是头疼不已、好在命名规范、翻译翻译也能知道写的是啥破玩意。

    ​ 写了啥?

    ​ 一句话概括:基于RSA公私钥、网络编程、GUI三者进行数字签名与完整性认证的本地聊天程序。

    ​ 实现出现的bug:

    ​ 由于有经验、有些错误简单就不做赘述、提提给我使绊子的:

    ​ 1、一个是通过字节数组传输到且解析后,可能会出现、解析错误,显示超过了127位或者128。

    ​ 这个错误有两种原因:

    ​ 1、是你发送的消息即字符串超过了117个,解决办法:分组发送。

    ​ 2、接收到的字节数组以及接收用的字节数组长度是要超出或者等于,要接收的消息的,那么会照成字节数组的默认值0也会参与解析。

    ​ 解决办法:利用字符串分割方法、我们发送时在消息末尾添加一个空格 ,接收到后先用分割方法一空格为分割符进行分割,这样可只取出我们所需要的消息。当然还有其他方法,比如接收端利用循环判断进行取出有效值(所以前者是我偷懒想出来的)

    ​ 2、显示解密错误,即利用RSA进行解密时出错。

    ​ 原因:很简单,秘钥用错了,要么是A的东西用了B的秘钥。要么是最狗血的一种:秘钥过时了,即我已经更新了秘钥但是你用旧秘钥解密,不错才怪。

    ​ 后者解决办法:重新写一个产生密钥对的类,且序列化密钥对,之后只需要使用,不需要生成。

    ​ 3、我在使用UDP传输时想完成一个请求连接的功能,即A端间隔1秒不断发送请求连接的消息,B则等待接收,若接收到,则给予A回执消息,通知A,A接收到通知则停止发送请求并显示连接完成。

    ​ 原因:上述看似无差错,但是!A发给B后,B从接收到发送回执消息这段时间A是不知道它接收到了的,所以A又会继续发送请求,导致B会在连接成功后又收到一次请求消息。(这个错误没影响,但但是以为这个错误与第二个错误中的第二种错误有关,快烦死我了)

    ​ 解决方法:发送请求的时间拉长一点,可三秒一次等等。

    能让我头疼的错误不多,就想到这么多了。其实错误原因还是以为考虑少了,不能完全考虑到运行情况。但这也让我小小温故而知新一次、颇有收益。

    1.1、项目结构

    请添加图片描述

    1.2、项目代码

    1.2.1、PublicPrivateKeyAcquisition.java

    实现手段:

    通过java的jdk中的API进行公私钥的生成与序列化。

    import java.security.Key;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.NoSuchAlgorithmException;
    
    //1、公私钥获取:生成公私钥、序列化公私钥、获取公钥、获取私钥。
    public class PublicPrivateKeyAcquisition {
    
        public KeyPair keyPair;
    
        // 产生公私钥。并初始化RSA算法。
        public void produceKeyPair() throws NoSuchAlgorithmException {
            //使用RSA算法获得密钥对生成器对象keyPairGenerator
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            //设置密钥长度为1024
            keyPairGenerator.initialize(1024);
            //生成密钥对
            keyPair = keyPairGenerator.generateKeyPair();
        }
    
        // 即 将存储了公私钥的对象存储进文件中,方便下次使用
        // 序列化对象,name是作为序列化后文件的名字
        public void serialize(String name) throws Exception {
            String str = name+".txt";
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(str));
            oos.writeObject(keyPair);
            oos.close();
        }
        // 通过相应的文件取出对应的公私钥对象,可通过公私钥对象获得公私钥。
        // 反序列化
        public KeyPair deserialization(String route) throws IOException, ClassNotFoundException {
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream(route+".txt"));
            Object obj=ois.readObject();
            ois.close();
            return (KeyPair) obj;
        }
    
        // 获得公钥
        public Key getPublicKey(){
            return keyPair.getPublic();
        }
    
        // 获得私钥
        public Key getPrivateKey(){
            return keyPair.getPrivate();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    1.2.3、RSAOperation.java

    实现手段:

    jdk自带的加密解密、SHA摘要生成、只有完整性认证是通过接收到的原文生成的SHA与接收到的SHA进行比较是否相同。

    package com;
    
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    
    import javax.crypto.Cipher;
    import java.security.Key;
    import java.security.MessageDigest;
    
    
    //2、RSA包含:加密信息,解密信息,SHA散列值获取,完整性认证。
    public class RSAOperation {
    
        // 参数1:明文,参数2:私钥,作用:加密原始消息
        public String encryption(String str, Key Key) throws Exception {
            //获取一个加密算法为RSA的加解密器对象cipher。
            Cipher cipher = Cipher.getInstance("RSA");
            //设置为加密模式,并将公钥给cipher。
            cipher.init(Cipher.ENCRYPT_MODE, Key);
            //获得密文
            byte[] secret = cipher.doFinal(str.getBytes());
    
            //进行Base64编码并返回
            // 注:使用Base64编码方式,是因为加密后的消息为二进制形式的数据,
            // 而我们传输的应该为字符串,所以要用到Base64的编码与解码。(Base64编码可将字节数组转换为字符串,解码为逆过程)
            return new BASE64Encoder().encode(secret);
        }
    
        // 参数1:密文,参数2:公钥、作用:解密密文
        public String decryption(String secret,Key Key) throws Exception {
            Cipher cipher = Cipher.getInstance("RSA");
            //传递私钥,设置为解密模式。
            cipher.init(Cipher.DECRYPT_MODE, Key);
            //解密器解密由Base64解码后的密文,获得明文字节数组
            byte[] b = cipher.doFinal(new BASE64Decoder().decodeBuffer(secret));
            //转换成字符串
            return new String(b);
        }
    
        // 生成基于原始消息的 SHA 散列值
        public String encoderSHA(String str) throws Exception {
            MessageDigest sha=MessageDigest.getInstance("SHA");
            BASE64Encoder base64en = new BASE64Encoder();
            //加密后的字符串
            // 使用digest方法后的数据为二进制数组,需通过encode变成字符串。后面接收到后逆过程可获得相同的字节数组
            return base64en.encode(sha.digest(str.getBytes("utf-8")));
        }
    
        // 验证消息完整性
        // 比较接收到的SHA摘要和通过接收到的原文产生的SHA摘要是否相同进行判断
        public boolean checkIntegrity(String ReceivedSHA,String CalculatedSHA) throws Exception {
    
            return (encoderSHA(ReceivedSHA)).equals(CalculatedSHA);
    
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    1.2.4、UDPChatReceiver.java

    关于端口号要着重说明一下

    一共是要开启四个端口号,若是两者本地通讯。

    A的发送与接收,B的发送与接收,都需要不同的端口号。其中,我们发送时还需要用到对方的端口号,这时是用到对方接收的端口号(这个不要错了)

    实现手段:

    jdk自带的网络编程知识、额外用到了多线程知识来实现一直接收消息,因接收程序是阻塞式,即接收到了才进行下一步、所以另起一线程。

    package com;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.security.Key;
    
    
    // 继承此接口,表示另外开启一个线程,线程会执行run()方法中的代码。
    public class UDPChatReceiver implements Runnable{
    
        DatagramSocket socket =null;  // 用来创建接收端口号的变量
        public int myPort;       // 端口号
        RSAOperation rsa = null;   // 获得数据后用此类中的方法进行解密
        GUIChat gui = null;      // 获得数据且解密后,用此类显示在图形化界面中
        String heName = "";      // 对方的名字
        Key publicKey = null; // 对方公钥
    
        public String receiveData="";       // 存储接收到的字符串
    
        // 生成接收端口的进程
        public UDPChatReceiver(int myPort,RSAOperation rsa,GUIChat gui,String heName){
            this.myPort=myPort;
            this.rsa = rsa;
            this.gui = gui;
            this.heName = heName;
    
            //在本机地址上建立端口,以接收
            try {
                socket=new DatagramSocket(myPort);
    
                // 获取对方公钥
                publicKey = new PublicPrivateKeyAcquisition().deserialization(heName).getPublic();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    
        @Override
        public void run() {
    
            // 通过循环能够一直保持接收,因为下方接收消息是阻塞式,即只有接收到了才会执行下一步,所以才重新开启一个线程去作为接收消息的程序。
            while (true) {
                try {
    
                    //接收数据包
                    byte[] container = new byte[1024];
                    //构造一个 DatagramPacket用于接收长度的数据包 length 。
                    DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                    //接收来自DatagramPacket的数据包,阻塞式
                    socket.receive(packet);
    
                    //获得包裹中的数据
                    receiveData = new String(packet.getData(), 0, packet.getData().length);
    
                    // 获取到数据后,解密数据,清空GUI中的文本域,然后重新赋值文本域。
                    decryptionAndDisplay(receiveData);
    
                    // 释放资源的判定
                    if(receiveData.equals("exit_0"))break;
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
            //关闭流
            socket.close();
    
        }
    
        // 解密数据并显示数据
        public void decryptionAndDisplay(String str){
    
            try {
                String[] s = str.split(" "); // 分割接收到的信息,方便解读
    
                if (s[0].equals("请求连接")) {
                    receiveData = s[0];
                }else {
    
                    // 解密密文
                    String str2 = rsa.decryption(s[0], publicKey);
    
                    // 将获得的密文与SHA显示在左边的文本域中,记得之前的文本不能删除了。
                    // 可直接在原基础上插入内容
                    gui.text_Ciphertext.append("密文:\n" + s[0] + "\n\n" + "SHA摘要:" + s[1] + "\n\n" + "完整性认证:" + rsa.checkIntegrity(str2, s[1]) + "\n" + "------------------------------------------------" + "\n");
    
                    // 将解密后的明文显示在右边文本域中
                    gui.text_Plaintext.append("\n" + heName + ": " + str2 + "\n");
                    }
                } catch(Exception e){
                    e.printStackTrace();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    1.2.5、UDPChatSender.java

    实现手段:

    jdk自带的API、但此时没有另起线程使用发送功能,因GUI界面点击发送按钮才会进行发送,所以不需要另外开线程。且GUI的程序本身就是一个线程

    package com;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    
    public class UDPChatSender  {
        public int myPort;          // 我自己发送的端口号
        DatagramSocket socket=null;
    
        public UDPChatSender(int myPort){
            this.myPort=myPort;
            //建立一个Socket
            try {
                socket=new DatagramSocket(myPort);
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }
    
    
        public void send(String str,int hePort){//str就是要发送的数据、hePort对方接收的端口号
    
            if (!str.equals("exit_0")) { // 这是一个关闭IO流的操作,一般是结束时关闭
                try {
                    //获取本机地址
                    InetAddress inetAddress = InetAddress.getByName("localhost");
    
                    //参数:数据(Byte类型),发送数据的长度,要发给谁
                    DatagramPacket packet = new DatagramPacket(str.getBytes(), 0, str.getBytes().length, inetAddress, hePort);
    
                    socket.send(packet);//发送包
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }else {
                //关闭流
                socket.close();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    1.2.6、GUIChat.java

    实现手段:

    ​ 利用Swing知识。发送按钮点击后会先分析输入框有没有字符串,有才下一步。之后将要发送的消息在聊天界面先显示,然后再加密原消息,与生成对应的SHA摘要。然后将两者拼接,中间使用空格分开,末尾加上空格。最后调用发送程序。

    package com;
    
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.security.Key;
    
    public class GUIChat extends JFrame {
    
        public JTextArea text_Ciphertext; // 展示密文的文本框
        public JTextArea text_Plaintext;  // 展示明文的文本框
        public JTextField text_Input;     // 输入文本框
        public JButton button_send;       // 发送按钮
        public JScrollPane jScrollPane_C;   // 滚动面板有两个、且放在面板中
        public JScrollPane jScrollPane_P;   // 滚动面板有两个、且放在面板中
        public JPanel jPanel;             // 面板只有一个、且放在滚动面板中中
        Key privateKey=null;                // 自己的私钥
    
        // name是聊天者身份,hePort,对方是端口号,send,是发送按钮点击时需要用的此类进行发送消息
        // 此方法是生成图形化界面
        public void init(String myName, int hePort, UDPChatSender send,RSAOperation rsa){
    
            // 设置生成窗体的位置,宽高
            this.setBounds(600,200,800,600);
            //获得一个容器,只有这样设置颜色才会生效
            Container contentPane = this.getContentPane();
            contentPane.setBackground(Color.yellow);
    
            //绝对布局,依照设定的x,y坐标定位布局组件
            contentPane.setLayout(null);
    
            // 生成面板
            jPanel = new JPanel();
    
            jPanel.setBackground(Color.black);// 设置面板颜色
            jPanel.setLayout(null);//设置面板的布局类型
    
            // 设置面板大小
            jPanel.setBounds(0,0,800,600);
    
    
            // 生成两个文本域、一个文本框、一个按钮
            text_Ciphertext=new JTextArea();   // 20为超过20个字则换行
            text_Plaintext=new JTextArea(20,10);
            text_Input = new JTextField(20);             // 20为文本框的高度
            button_send = new JButton("发送");
    
            // 设置文本域中字体的属性,行楷,字体类型(加粗等,这里是标准),大小
            text_Ciphertext.setFont(new Font("行楷",Font.PLAIN,20));
            text_Plaintext.setFont(new Font("楷书",Font.PLAIN,20));
    
            // 设置文本框和按钮的位置,以生成的(即生成的窗体)JFrame的左上角为(0,0)开始设置的位置
             text_Input.setBounds(400,535,300,30);
             button_send.setBounds(700,535,100,30);
    
    
            // 生成滚动面板、并将对应的文本域放入
            jScrollPane_C = new JScrollPane(text_Ciphertext);
            jScrollPane_P = new JScrollPane(text_Plaintext);
    
            // 设置滚动面板在面板中的位置
            jScrollPane_C.setBounds(0,0,400,565);
            jScrollPane_P.setBounds(400,0,400,535);
    
    
            // 将滚动面板、按钮放入面板
            jPanel.add(jScrollPane_C);
            jPanel.add(jScrollPane_P);
            jPanel.add(text_Input);
            jPanel.add(button_send);
    
            //将面板放入JFarm中
            contentPane.add(jPanel);
    
            setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//点 x 后直接退出
    
            // 设置窗口大小固定
            setResizable(false);
            // 设置窗口名称
            setTitle(myName);
            // 设置可见性
            setVisible(true);
    
            // 从本地的序列化文件中获得自己的私钥
            PublicPrivateKeyAcquisition acquisition = new PublicPrivateKeyAcquisition();
            try {
                // 获取私钥
                privateKey = acquisition.deserialization(myName).getPrivate();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 按钮事件监听器,即点击按钮后会发生文本框中的内容
            button_send.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String text_encryption="";
                    // 先获取文本框内容
                    String text = text_Input.getText();
    
                    // 判断文本框是否存在内容,如果存在则发送
                    if (!text.equals("")){
    
                        try {
    
                            // 将要发送的消息显示在右边文本域中
                            text_Plaintext.append("\n" + myName + ": " + text + "\n");
                            // 加密原消息
                            text_encryption = rsa.encryption(text, privateKey);
    
                            // 给加密后的消息添加SHA摘要
                            text_encryption = text_encryption+" "+rsa.encoderSHA(text)+" ";
                        } catch (Exception exception) {
                            exception.printStackTrace();
                        }
    
                        // 利用UDP发生内容给指定对象
                        send.send(text_encryption,hePort);
    
                        // 最后清空文本框
                        text_Input.setText("");
                    }
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128

    1.2.7、GeneratePublicPrivateKey.java

    实现手段:

    调用生成密钥对、序列化的工具类

    package com.Test;
    
    import com.PublicPrivateKeyAcquisition;
    
    public class GeneratePublicPrivateKey {
        public static void main(String[] args) {
            // 生成公私钥,要生成两对密钥对。
            try {
                PublicPrivateKeyAcquisition acquisition = new PublicPrivateKeyAcquisition();
                acquisition.produceKeyPair();// 生成密钥对
                acquisition.serialize("Bob");// 序列化密钥对
    
                acquisition.produceKeyPair();// 生成密钥对
                acquisition.serialize("Alice");// 序列化密钥对
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.2.8、Bob.java

    实现手段:

    两个用户实现手段差不多,具体看下面Alice的解释。

    package com.Test;
    
    import com.*;
    
    
    public class Bob {
        GUIChat guiChat = null;                 // 获得图形化界面的对象,用来作为启动图形化界面的变量
        UDPChatSender sender = null;            // 发送消息所用到的变量
        RSAOperation rsaOperation = null;       // 数字签名,加密,解密,完整性认证,SHA摘要所用到的变量
        UDPChatReceiver receiver = null;        // 接收消息所用到的变量
    
        // 初始化、即启动图形化界面,启动接收线程。
        public void init(){
    
            // 开启图形化界面
            guiChat = new GUIChat();
    
            // 设置好发送所需要的信息
            sender = new UDPChatSender(9999);
    
            // new出其对象
            rsaOperation = new RSAOperation();
    
            // 初始化图形化界面
            guiChat.init("Bob",8881,sender,rsaOperation);
    
    
            // 先开启接收线程
            receiver = new UDPChatReceiver(9991,rsaOperation,guiChat,"Alice");
            new Thread(receiver).start();
    
            // 尝试连接用户
            initConnection();
    
        }
    
        // 尝试连接用户、即被动等待 A 发送过来的连接信息,接收到后再返回一个回执信息给A
        public void initConnection(){
            boolean flag=true;  // 连接成功后用来退出连接的变量
            int i=0;
            // 不主动发信息,先获得对方的连接信息然后再回复对方一个信息
            // 循环中内容是为了将尝试连接这个消息进行动态变化
            while (flag) {
                i=(++i)%3;
    
                try {
                    Thread.sleep(1000); // 休眠一秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                guiChat.text_Ciphertext.setText("");  //清空文本域
    
                if (i==0)
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.setText("尝试连接中.");
    
                else if (i==1)
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.append("尝试连接中..");
    
                else if (i==2)
                    // 设置文本域显示连接标志
                    guiChat.text_Ciphertext.append("尝试连接中...");
    
                if (receiver.receiveData.equals("请求连接")){
                    flag = false;
    
                    // 发送回执信息
                    sender.send("请求连接 B ",8881);
    
                    guiChat.text_Ciphertext.setText("");  //清空文本域,即设置文本域的内容为空
                    guiChat.text_Ciphertext.setText("连接成功!\n");
                }
    
    
            }
        }
        public static void main(String[] args) {
            new Bob().init();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    1.2.9、Alice.java

    实现手段:

    ​ 先初始化生成我们要用的类、图形化界面类、发送、接收类、RAS工具类。在设置发送、接收类时定义好端口号。

    ​ 然后调用图形化界面的初始化方法,显示图形化界面。

    ​ 开启接收线程、可以及时接收到请求连接的信息。

    ​ 然后调用尝试连接用户的方法,等待连接。

    ​ 连接成功、正常通信。

    package com.Test;
    
    
    import com.*;
    
    public class Alice {
        GUIChat guiChat = null;
        UDPChatSender sender = null;
        RSAOperation rsaOperation = null;
        UDPChatReceiver receiver = null;
        public void init(){
    
            // 开启图形化界面
            guiChat = new GUIChat();
            sender = new UDPChatSender(8888);
            rsaOperation = new RSAOperation();
    
            guiChat.init("Alice",9991,sender,rsaOperation);
    
            // 先开启接收线程
            receiver = new UDPChatReceiver(8881,rsaOperation,guiChat,"Bob");
            new Thread(receiver).start();
    
            // 尝试连接用户
            initConnection();
    
        }
    
        // 尝试连接用户
        public void initConnection(){
            boolean flag=true;
            int i=0;
    
    
            // 隔 3 秒发送一次信息、等待回执信息、获得回执后接束发送。
            while (flag) {
                    i=(++i)%3;
    
                    try {
                        Thread.sleep(1000);
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                        guiChat.text_Ciphertext.setText("");  //清空文本域
                    if (i==1)
                        // 设置文本域显示连接标志
                        guiChat.text_Ciphertext.setText("尝试连接中.");
    
                    else if (i==2)
                        // 设置文本域显示连接标志
                        guiChat.text_Ciphertext.append("尝试连接中..");
    
                    else if (i==0) {
                        // 设置文本域显示连接标志
                        guiChat.text_Ciphertext.append("尝试连接中...");
                        // 发送信息
                        sender.send("请求连接 A ",9991);
                    }
                if (receiver.receiveData.equals("请求连接")){
                    flag = false;
                    guiChat.text_Ciphertext.setText("");  //清空文本域
                    guiChat.text_Ciphertext.setText("连接成功!\n");
    
                }
            }
        }
    
        public static void main(String[] args) {
            new Alice().init();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    1.3、设计架构

    ​ 目标:编写基于RSA进行数字签名和图形化界面的本地通讯程序。
    ​ 要求:1、发送的信息包括原始消息和签名,原始消息通过RSA的私钥加密,签名则是利用SHA散列函数对原始消息进行摘要。
    ​ 2、接收到消息后通过SHA的值检测原始消息的完整性。
    ​ 3、利用图形化界面完成。
    ​ 架构设计:
    ​ 整个程序应分为四部分:1、基于RSA的公私钥生成。2、RSA。3、本地聊天。4、图形化界面。
    ​ 1、公私钥获取:生成公私钥、序列化公私钥、获取公钥、获取私钥。(PublicPrivateKeyAcquisition类)
    ​ 2、RSA包含:加密信息,解密信息,SHA散列值获取,完整性认证。(RSAOperation类)
    ​ 3、UDP。
    ​ 4、GUI。

    1.4、操作步骤

    ​ 1、先生成自己的公私钥并序列化保存(手动运行GeneratePublicPrivateKey类)
    ​ 2、开启聊天软件,等待与对方进行连接(分别启动Alice和Bob类)
    ​ 3、连接成功,进行加密通信

    1.5、程序运行截图

    1.5.1、尝试连接截图:

    请添加图片描述

    1.5.2、连接成功截图

    请添加图片描述

    1.5.3、发送消息截图:

    请添加图片描述

  • 相关阅读:
    3.16 haas506 2.0开发教程-example-JC035串口屏
    Android性能优化,有关内存抖动与解决方案
    matlab 13折线法数据量化编码与解码
    PCL 使用MLS 上采样
    【达摩院OpenVI】几行代码,尽享丝滑视频观感
    openEuler embedded编译镜像报错
    OSPF.综合实验
    来自云仓酒庄品牌雷盛红酒分享为什么高海拔的酒价格更高?
    【解决问题】跨域 图片跨域问题 has been blocked by CORS policy No-Access-Control-Allow-Origin
    R语言安装caret包报错
  • 原文地址:https://blog.csdn.net/qq_43483251/article/details/125470514