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

简易聊天室

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

简易聊天室

全文思维导图

一、基本思路

使用C/S架构进行设计,设置一个服务器,服务器中使用多线程处理每一个客户端,分别对客户端发送的消息进行读取,然后根据广播思想,把消息分别转发给每一个客户端。
此处较为重要的知识点,我认为在于理解**客户端和服务器端之间是如何交互的; 其实本质上就是获取输入输出流 **,然后再对信息进行处理的一个过程。所以在客户端要有输入流输出流,在服务器端要获取客户套接字的输入输出流。网络连接其实都已经封装的很好了,直接用套接字即可,但要注意多线程的运用(因为需要不断监听,while循环太多,需要用多线程处理)。
实现十分简单,代码结构如下:

二、具体实现 1.复用界面的实现

其实服务器端完全没必要有界面,但是老师布置的实验里将服务器和客户端复用了同一个界面,并且服务器本身还能发送消息(类似于将一个客户端兼做服务器)。此处只是单纯令服务器和客户端复用同一个界面,而没有实现服务器发送消息的功能(因为服务器采用的是ServerSocket 不能获取其输入流)。
需要注意的是,此处我是形参id来区分是服务器还是客户端的,所以把服务器的线程也写在UI类里。

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyWindow extends JFrame {
    //显示聊天界面
    private int id;
    private String name;
    
    private Client client = null;
    private Server server = null;
    
    private JTextField inputField = new JTextField(20);
    private JTextArea messageArea = new JTextArea(50, 30);
    private JButton send = new JButton("发送");
    
    public JTextArea getMessageArea() {
        return messageArea;
    }
    
    MyWindow(int id, String name) {
        this.id = id;
        this.name = name;
        
        this.setTitle("QQ聊天室-" + this.name);
        this.setLayout(null);
        
        //消息显示区禁止编辑
        messageArea.setEditable(false);
        //给文本框添加滚动条,注意!此时设置矩形大小是设置jScrollPane大小
        JScrollPane jScrollPane = new JScrollPane(messageArea);
        jScrollPane.setBounds(10, 10, 400, 300);
        
        inputField.setBounds(10, 340, 400, 35);
        send.setBounds(10, 380, 100, 35);
        send.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (client!=null){
                    client.getWriter().println(name + ":" + inputField.getText());
                    client.getWriter().flush();  //刷新客户端输入流
                    inputField.setText("");     //将输入框清空
                    inputField.requestFocus();  //获取鼠标焦点
                }else if (id==0){  //id为0表示是服务器
                    inputField.setText("");     //将输入框清空
                    inputField.requestFocus();  //获取鼠标焦点
                    messageArea.append(inputField.getText());  //这是把自己的消息添加到消息框
                    System.out.println("服务器不能发消息!");
                }
            }
        });
        
        this.add(jScrollPane);
        this.add(inputField);
        this.add(send);
        
        this.setSize(440, 590);
        this.setLocation(300 + id * 10, 100);  //设置初始出现位置
        
        if (id == 0) {  //id为0即为服务器
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            ServerThread serverThread = new ServerThread(this.server);
            serverThread.start();
        }
        this.setResizable(true);
        this.setVisible(true);
    }
    
    //调用setNet方法,则创建客户端
    public void setNet(String ip, int port) {  //如果是客户端,则添加线程
        client = new Client(ip, port,this.messageArea);
    }
    
    public class ServerThread extends Thread {
        Server server;
        ServerThread(Server s){
            this.server = s;
        }
        @Override
        public void run() {
            server = new Server(8888,messageArea);
            System.out.println("服务器已开启");
        }
    }
    
}

2.客户端实现

客户端会使用到两个流,如下:

private BufferedReader reader; //缓冲字符输入流,用于读取服务器转发的消息
private PrintWriter writer; //输出字符流,用于给服务器发消息

并且重写了一个线程类,用来不断读取缓冲流中的数据(其实就是服务器转发的消息),并在读取后将消息(包括自己发的消息)显示到自己的文本框中。
代码如下:

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.io.*;
import java.net.Socket;

public class Client {
    private Socket s;  //创建和服务器连接的套接字
    private BufferedReader reader;  //缓冲字符输入流,用于读取服务器转发的消息
    private PrintWriter writer;  //输出字符流,用于给服务器发消息
    private JTextArea messageArea;

    Client(String ip, int port, JTextArea messageArea){
        this.messageArea = messageArea;
        try {
            // 创建客户端对象
            this.s = new Socket(ip, port);

            //与socket建立字符缓冲输入流,用于接收发送框中的消息
            InputStreamReader inputStreamReader = new InputStreamReader(s.getInputStream());
            reader = new BufferedReader(inputStreamReader);
            //与socket建立字符缓冲输出流,用于发送消息
            writer = new PrintWriter(this.s.getOutputStream());
            System.out.println("客户端成功连接到服务器...");
            messageArea.append("客户端成功连接服务器"+"n");
            //开启一个线程,不断读取服务器发送的信息
            RevThread thread = new RevThread();
            thread.start();

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //类中类
    public class RevThread extends Thread{
        private String message;        //用于接受从服务器与客户端1建立的输入流中读出的信息

        //不断接收服务器端发来的消息
        @Override
        public void run() {
            try {
                while ((this.message = reader.readLine()) != null) {  //不断读取服务器转发的消息(包括自己发的)
                    messageArea.append(message+"n");  //在聊天消息框中显示消息
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public String getMessage() {
            return message;
        }
    }

    public BufferedReader getReader() {
        return reader;
    }

    public PrintWriter getWriter() {
        return writer;
    }

}

3.服务器端实现

服务器端会设置一个集合,其元素是客户端的输出流,后面只需要遍历该集合,然后就可以向各个客户端的缓冲流中 转发服务器收到的消息即可实现广播。
服务器在初始化时就会进入while(){ }循环,不断监听客户端的连接,每获取一个客户端,都为其单独开一个线程来不断接收其发送的消息(即获取其套接字的缓冲输入流,然后读取信息)
代码如下:

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
    int port;
    JTextArea messageArea;

    //创建一个数组用来存储客户端的输出流
    ArrayList clientOutPutStreams = new ArrayList();

    Server(int port, JTextArea messageArea) {
        this.port = port;
        this.messageArea = messageArea;
        try {
            //server端创建出与client端通信的socket
            ServerSocket serverSocket = new ServerSocket(port);  //用传入的端口参数创建服务器套接字
            System.out.println("服务器启动成功!");

            while (true) {
                //服务器创建出与客户端通信的socket
                Socket clientSocket = serverSocket.accept();  //这是一个阻塞方法,会等待客户端来连接,有则返回 来连接的客户端的套接字
                String ip = clientSocket.getInetAddress().getHostAddress();
                System.out.println(ip + "加入了聊天室!");//控制台打印进入到聊天室的用户地址并提示

                //获取客户端套接字的输出流,为以后给客户端写数据做准备
                PrintWriter printWriter = new PrintWriter(clientSocket.getOutputStream());
                clientOutPutStreams.add(printWriter);

                //开启一个线程用于与一个客户端进行通信
                Thread thread = new Thread(new clientHandler(clientSocket));

                //开启线程,使服务器一直处于接受消息并转发给所有人
                thread.start();
                System.out.println("有新连接加入");

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //广播转发消息
    public void sendEveryone(String message) {
        for (Object OPStream:clientOutPutStreams) {
            try {
                PrintWriter writer = (PrintWriter) OPStream;
                writer.println(message);
                writer.flush();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    //类中类重写线程类接口
    public class clientHandler implements Runnable {
        BufferedReader reader;
        Socket socket;

        
        public clientHandler(Socket socket) {
            //与客户端建立通信  并  定义接受数据的输入流
            try {
                this.socket = socket;
                //获取客户端套接字的字节输入流(方便读数据)
                InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
                //获取缓冲输入流
                reader = new BufferedReader(inputStreamReader);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        
        @Override
        public void run() {
            //定义准备接受消息变量
            String message = null;

            try {
                //使线程不断从socket上读取数据的状态
                while ((message = reader.readLine()) != null) {
                    System.out.println("MESSAGE_Receive: " + message);
                    messageArea.append(message+"n");  //并显示在自己的窗体中
                    //将数据发送给每一个客户端
                    sendEveryone(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}
4.测试类

下面是用本机连接的测试类,还没有多机实验过。大家可以尝试联机呀 ^ _ ^

package Schoolwork.ChatHouse;

public class JavaMain {
    public static void main(String[] args) {
        MyWindow myWindow = new MyWindow(0,"服务器");
        MyWindow myWindow1 = new MyWindow(1,"xlk");
        myWindow1.setNet("127.0.0.1",8888);
        MyWindow myWindow2 = new MyWindow(2,"windx");
        myWindow2.setNet("127.0.0.1",8888);
    }
}
三、最终效果

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

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

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