Java课程设计——聊天室系统

一、课程设计要求目的

目的

编写一个局域网Java聊天室系统,掌握Java网络通信、多线程、IO文件操作等高级应用编程技能。

要求

以课堂所给示例为基础,编写一个小型Java聊天室系统。

完成如下功能:

  1. 多客户端模式下,实现客户与客户的单独通信,要求信息通过服务器中转
  2. 添加图形界面(选做)或其他新功能
  3. 实现端到端的文件传输
  4. 到端的通信,实现并行通信模式

二、系统设计与实现 

2.1设计原理与思想

Socket套接字通信原理

        Socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。Socket套接字是建立连接的中间件,而连接成立的前提是要有两台不同的机器存在,一般两端分别为服务器端和客户端。

Socket通信过程:

        服务端与客户端都建立一个Socket对象,然后通过Socket对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。

客户端过程:

        创建Socket,连接服务器,将Socket与远程主机连接,发送数据,读取响应数据,直到数据交换完毕,关闭连接,结束TCP对话。

服务端过程:

        服务端先初始化Socket,建立流式套接字,与本机地址及端口进行绑定,然后通知TCP,准备好接收连接,然后调用accept()进行阻塞,等待客户端连接。当客户端与服务器=端建立了连接,客户端发送数据请求,服务端接收请求并处理请求,然后把响应数据发送给客户端,客户端读取数据,直到数据交换完毕。最后关闭连接,交互结束。

ca7376e4eec84ccd8c9bed44ac7904cd.png

 

系统设计思想

  1. 实现并行通信:初始程序,只能实现客户端发一条消息,服务端发一条消息,发送一条消息后,由于要读入对方所发消息,造成阻塞,只能你发一句,我发一句。在本次设计中,选择套接字数组,将每次产生的套接字记录下来,实现每次可发多条消息。
  2. 实现文件传输:与发送文字原理相同。不同的是,一端发送文件时要先选择文件,并发送文件后,在文本框输入“发送文件”,作为触发服务端发文件的开关。另一端接收方可以自己选择好储存位置。
  3. 设置聊天器界面:分为客户端和服务端两种。采用Swing框架,简洁明了。在服务端中,有退出服务器按钮。在客户端有发送、文本框、设置等按钮。

 

2.2总体设计(类与类的关系)

​​​​​​​服务端

        服务端共设计了三个类,分别是:MultiTalkServer类,ServerThread类,Server类,实现不同功能。

        主类是MultiTalkServer类,创建ServerSocket类的对象,在程序运行时,先创建Server类,启动GUI界面,即服务端启动,等待客户端连接。当客户端进行请求连接时,服务端就会新建一个套接字,启动ServerThread线程,实现客户端与客户端之间互发消息。

​​​​​​​客户端

        客户端共设计了两个类,分别是:TalkClient类,SettingView类,实现不同的功能。

        主类是TalkClient类,有消息收发和聊天器页面设计功能,每次运行时,会创建一个新的客户端,出现客户端页面。文本框中,输入所要发送给客户端编号+“ ”+消息内容。发文件时,可以先选择另一客户端文件存储位置,然后再发送文件,若不指定目录,就默认路径。

2.3详细设计

 

TalkClient类

在该类设计中,实现了GUI页面设计,包括发送、设置存储位置、选择文件等按钮。当用户启动TalkClient类时,新建一个客户端,并向服务端发送连接请求,若未连接到服务器,会出现“未连接到服务器”提示语句。若连接成功,会出现“本客户端端号”提示语句。若用户发送消息,在文本框中输入内容,点击“发送”按钮,通过addMouseListener 添加鼠标监听器send.addMouseListener(new MouseAdapter(){}),监听鼠标点击事件public void mouseClicked(MouseEvent e)。如果用户发送消息,调用相应的输出流向服务器写内容,并且向服务端发送“normal”,代表普通信息。如果用户想要发文件,首先通过“发送文件”选中文件,程序获得存储地址,然后用户在文本框中输入“发文件”,向服务端发送“File”,代表文件信息,同时将文件内容发送过去,文件发送成功后也会有相关提示语。相关程序如下:

4c68ccd9ddc7484ca24d829ac0e58a02.png

客户端接收消息时。若消息为普通消息,即文本消息时,直接在文本框中输出消息内容。若消息为文件,则直接存储到相对应设置的路径中。

SettingView类

SettingView类为客户端界面上一个按钮的相关实现,可以设置文件的接收后的存储位置。当点击“选择文件存储位置”的按钮时,会调用ChooseDir()方法,并将文件目录赋值给客户端的目录并显示在文本框里。相关代码如下:

f418309e69b847a5a7bda517d65c9aba.png

MultiTalkServer类

        MultiTalkServer类为客户端主程序,当客户端请求连接时,为其与服务端创建一个新的线程,并且调用Server类,建立GUI界面。为了保证能多个客户端之间能相互互发消息,消息在服务器实现中转,在该类中创建socket数组。相关代码如下:

a0fe748dd1b14fd9a548798c0c6d081a.png

ServerThread类

ServerThread类负责收发客户端发来的消息,收发原理与客户端类似。根据客户端发来的消息,确定客户端的通信目标,获得目标的输出流,将读入的消息通过输出流发送到相应的客户端。主要代码如下:

bc5d932401104ecd9ae5d62b9866577d.png

Server类

Server类为服务器的页面,主要功能为控制服务器的开关,相关组件有

 69caa204a2ee4426975aa6117e68eaf0.png

三、系统测试

服务端运行测试

启动:

215a5b664af045fbacba13993fbfd1ba.png

点击“结束”按钮即关闭服务端

客户端运行测试

启动:

09c08360a2824985a89bc26358b2bc5a.png

发送文件:

选择文件

358cf1f5f569410f999bb8f19ef89a5a.png

设置文件存储位置 

a9fa8d4136fb4046997e419f727743c1.png

相关功能测试

客户端连接服务端

f7c48cb9e2c744e7b2afcb8ebe33961c.png

客户端未成功连接服务端时,显示“未连接到服务器”

互发消息:

可同时创建多个客户端,每两个客户端之间都可以进行单独通信,互相发送消息。客户端1给2发“你好”,2给1发“你好”,3给1发“你好”

b25991b818634f41952507fdca51abcf.png3c66c3758ef246919e3310c80d1f4ff8.png8de87f7e19bc44f292a1c22819b711b7.png

 

实现并行通信,一段发送消息不受另一端影响,即没有每次只能发一条消息的限制。

1给2可以连发两条消息

e8083a13e02146079fd5522edc54b399.png

发送文件:

选择所要发送的文件

a02ebf1e8fe94684a9d8ba4fb0c093a8.png

 

在文本框中输入“发送文件”,再点击“发送”按钮

 ed74337d96374015a704290ce72f067c.png

5c03469fef824d03ad11996fef6f6b8c.png

78ddc157f7ec4ccba0e7ef24d1d9e5a2.png 

四、实验总结

        在这次课程设计,实现了多客户端的并行通信,文件传输,设置存储目录,设计聊天界面等功能,使用了Java语言编程、socket网络通信编程、多线程技术等等。在设计中,刚开始没有头绪,虽然知道了通信的原理,但不知道从何下手。通过在网上查找资料,慢慢有了思路。用socket数组存储套接字,实现多个客户端同时在线进行通信,用字节流的available方法判断缓冲区是否有可读字节,实现并行通信操作。还有界面的设计,也是从网上寻找资源学习,不过界面方面还有很多可以完善的地方,比如说:字体大小,字体主题等等。在发送消息时,也只是简单实现了私聊功能,没有实现群聊功能,但是完成了并行通信,不受任何一方发送消息数量的多少。除此之外,聊天器中,发送文件的功能还不是很完善,目前只能完成保存到默认路径中,更换存储路径的功能并没有完全实现,这也是要提高的一点。在程序运行时,发现之前运行的正常,但有时发现不能同时运行多个客户端,也通过查找资料发现解决问题的方法。总的来说,这一个课程设计,让我对Java编程了解更多,成为一个小软件开发的启蒙,也希望自己能继续再接再厉,抓住细节,更好地掌握Java。

五、代码附录

MultiTalkClient.java

package multi;

import java.io.*;
import java.net.*;
public class MultiTalkClient {
    public static void main(String args[]) {
        try{
            //向本机的4700端口发出客户请求
            Socket socket=new Socket("127.0.0.1",4700);
            //由系统标准输入设备构造BufferedReader对象
            BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
            //由Socket对象得到输出流,并构造PrintWriter对象
            PrintWriter os=new PrintWriter(socket.getOutputStream());
            //由Socket对象得到输入流,并构造相应的BufferedReader对象
            BufferedReader is=new BufferedReader(new
                    InputStreamReader(socket.getInputStream()));
            String readline;
            readline=sin.readLine(); //从系统标准输入读入一字符串
            while(!readline.equals("bye")){//若从标准输入读入的字符串为 "bye"则停止循环
                //将从系统标准输入读入的字符串输出到Server
                os.println(readline);
                os.flush();//刷新输出流,使Server马上收到该字符串
                //在系统标准输出上打印读入的字符串
                System.out.println("Client:"+readline);
                //从Server读入一字符串,并打印到标准输出上
                System.out.println("Server:"+is.readLine());
                readline=sin.readLine(); //从系统标准输入读入一字符串
            } //继续循环
            os.close(); //关闭Socket输出流
            is.close(); //关闭Socket输入流
            socket.close(); //关闭Socket
        }catch(Exception e) {
            System.out.println("Error"+e); //出错,则打印出错信息
        }
    }
}

MultiTalkServer.java

package multi;

import java.io.*;
import java.net.*;

public class MultiTalkServer{
    public static int clientnum=0; //静态成员变量,记录当前客户的个数
    public static Socket[] socket = new Socket[100];
    public static void main(String[] args)  {
        new Server().init();
        ServerSocket serverSocket=null;
        boolean listening=true;
        try{
            //创建一个ServerSocket在端口4700监听客户请求
            serverSocket=new ServerSocket(4700);
        }catch(Exception e) {
            System.out.println("Could not listen on port:4700.");
            //出错,打印出错信息
            System.exit(-1); //退出
        }
        try {
            while (listening) { //循环监听
                //监听到客户请求,根据得到的Socket对象和客户计数创建服务线程,并启动之
                socket[++clientnum]=serverSocket.accept();
                new ServerThread(socket, clientnum).start();

            }
            serverSocket.close(); //关闭ServerSocket
        }
        catch(Exception e){
            System.out.println(e);
        }
    }
}

ServerThread.java

package multi;

import java.io.*;
import java.net.*;
public class ServerThread extends Thread{
    public Socket[] sumsocket;
    public Socket socket; //保存与本线程相关的Socket对象
    public int clientnum; //保存本进程的客户计数
    public ServerThread(Socket[] socket,int num) { //构造函数
        this.socket=socket[num];
        this.sumsocket=socket; //初始化socket变量
        this.clientnum=num; //初始化clientnum变量
    }
    public void run() { //线程主体
        try{
            String readline=null;
            DataInputStream is=new DataInputStream(socket.getInputStream());//消息,文件输入流
            DataOutputStream os=new DataOutputStream(socket.getOutputStream());
            os.writeUTF("本客户端编号:"+clientnum);
            os.flush();
            while(true) {
                if(is.available()>0) {
                    String line=is.readUTF();//读取字符串
                    String[] t=line.split(" ");//以空格为分隔符分割字符
                    int n=Integer.parseInt(t[0]);//接收信息的客户端编号
                    Socket w=sumsocket[n];
                    DataOutputStream tos=new DataOutputStream(w.getOutputStream());//消息,文件输出流
                    readline=is.readUTF();
                    if(readline.equals("File")) {//发送文件
                        tos.writeUTF(t[1]);
                        tos.flush();
                        tos.writeUTF("File");
                        tos.flush();
                       // System.out.println(clientnum+" is send file to "+n+": "+t[1]);
                        int y=512,len=512;
                        byte[] data=new byte[y];//一次读取字节,一般不会太大
                        while(len==y) {
                            len=is.read(data,0,y);
                            tos.write(data,0,len);
                            tos.flush();
                        }
                    }
                    else if(readline.equals("normal")){
                        tos.writeUTF("客户端"+clientnum+":"+t[1]);
                        tos.flush();
                        tos.writeUTF("normal");
                        tos.flush();
                    }
                }
            }
        }
        catch(Exception e){
            System.out.println("Error:"+e);//出错,打印出错信息
        }
    }
}

SettingView.java

package multi;

import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

public class SettingView {
    static JFrame win;
    static JPanel mian;
    static JButton close,choose;
    static JLabel l;
    static JTextField shu;
    static String mu="E:\\2022-2023-1\\class\\java\\聊天室\\src\\multi";
    public SettingView(String mu){
        this.mu=mu;
    }
    static public File ChooseDir(){
        JFileChooser chooser = new JFileChooser();
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int returnVal = chooser.showOpenDialog(chooser);
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            return chooser.getSelectedFile();
        }else return null;
    }
    public void init() {
        win=new JFrame("Setting");
        l=new JLabel("文件接收目录");
        l.setBounds(10,20,230,25);
        choose=new JButton("自定义");
        choose.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                File f=SettingView.ChooseDir();
                if(f!=null) {
                    mu=f.getAbsolutePath();
                    shu.setText(f.getAbsolutePath());
                }
            }
        });
        choose.setFocusable(false);
        choose.setBounds(250,20,80,25);
        shu=new JTextField(mu);
        shu.setBounds(10,60,320,30);
        close=new JButton("确定");
        close.setBounds(145,100,60,30);
        close.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                win.setVisible(false);
                mu=shu.getText();
            }
        });
        mian=new JPanel();
        mian.setLayout(null);
        mian.add(close);
        mian.add(l);
        mian.add(choose);
        mian.add(shu);
        win.setContentPane(mian);
        win.setBounds(760,500,350,200);
        win.setVisible(true);
        win.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                win.setVisible(false);
                mu=shu.getText();
            }
        });
    }
}

TalkClient.java

package multi;

import java.net.Socket;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.net.*;
import javax.swing.*;

public class TalkClient {//客户端主程序
    static JFrame top;
    static JButton send,setting,sf;
    static JTextArea read,write;
    static Font form;
    static JScrollPane js;
    final static String ip="127.0.0.1";
    static String mu="E:\\2022-2023-1\\class\\java\\聊天室\\src\\multi";
    static File fi;
    final static int port=4700;
    static public File ChooseFile(){
        JFileChooser chooser = new JFileChooser();
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        int returnVal = chooser.showOpenDialog(chooser);
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            return chooser.getSelectedFile();
        }else return null;
    }
    public static void main(String[] args) {
        top = new JFrame();
        top.setContentPane(new JPanel());
        top.setTitle("JAVA--聊天器");
        top.setSize(500, 500);
        top.setLayout(null);
        top.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        send=new JButton("发送");
        sf=new JButton("发送文件");
        read=new JTextArea();
        write=new JTextArea();
        send.setBounds(350,260,100,50);
        sf.setBounds(0,260,100,50);
        form = new Font(" ",Font.PLAIN, 20);
        top.add(send);
        sf.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                fi=TalkClient.ChooseFile();
            }
        });
        top.add(sf);
        write.setBounds(10,320,450,100);
        write.setFont(form);
        top.add(write);
        read.setEditable(false);
        read.setFont(form);
        setting=new JButton("设置文件存储位置");
        setting.setBorder(BorderFactory.createBevelBorder(0));
        setting.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                new SettingView(mu).init();
            }
        });
        setting.setBounds(150,260,100,50);
        top.add(setting);
        JScrollPane js = new JScrollPane(read);
        js.setBounds(10,10,450,230);
        top.getContentPane().add(js);
        top.setVisible(true);
        try {
            Socket socket=new Socket(ip,port);
            DataInputStream is=new DataInputStream(socket.getInputStream());
            DataOutputStream os=new DataOutputStream(socket.getOutputStream());
            String q=is.readUTF();
            read.append(q+"\n");
            char num=q.charAt(7);
            send.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    String t=write.getText();
                    System.out.println(t);
                    write.setText("");
                    try {
                        char s=t.charAt(0);
                        char s1=t.charAt(1);
                        if(s<='9'&&s>='0'&&s1==' '&&s-'0'!=num){
                            String[] tt=t.split(" ");

                            if(tt[1].equals("发送文件")) {
                                read.append("客户端"+num+":"+"文件已发送\n");
                                os.writeUTF(tt[0]+" "+fi.toString());
                                os.flush();
                                os.writeUTF("File");
                                os.flush();
                                File f=fi;
                                System.out.print(fi);
                                FileInputStream fis=new FileInputStream(f);
                                int n=512,len=512;
                                byte[] data=new byte[len];
                                while(len==n) {
                                    len=fis.read(data,0,n);
                                    os.write(data,0,len);
                                    os.flush();
                                }
                            }
                            else {
                                os.writeUTF(t);
                                read.append("客户端"+num+":"+tt[1]+'\n');
                                os.writeUTF("normal");
                                os.flush();
                            }
                        }
                        else {
                            read.append(" "+"\n");
                        }
                    }
                    catch(Exception r) {
                        System.out.println(r);
                    }
                }
            });
            while(true) {
                if(is.available()>0) {
                    String t=is.readUTF();
                    System.out.println(t);
                    String flag=is.readUTF();
                    System.out.println(flag);
                    if(flag.equals("File")) {
                        read.append("客户端"+num+":"+"已经发送到相关目录"+'\n');
                        int n=512,len=512;
                        String[] t1=t.split("\\\\");
                        File f=new File(mu+"\\"+t1[t1.length-1]);
                        if(!f.exists()) {
                            f.createNewFile();
                        }
                        FileOutputStream fos=new FileOutputStream(f);
                        byte[] data=new byte[512];
                        try {
                            while(n==len) {
                                len=is.read(data,0,n);
                                fos.write(data);
                            }
                        }
                        catch(Exception e) {
                            System.out.println(e);
                        }
                    }
                    else {
                        System.out.println(1);
                        read.append(t+"\n");
                    }
                }
            }
        }
        catch(Exception e) {
            System.out.println(e);
        }
        read.append("未连接到服务器"+'\n');
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值