Live2D

JAVA-Socket通信 打造属于自己的聊天室(服务端)

(重构版本 重构 JAVA 聊天室 —— CS 模式的简单架构实现, 可以看完本篇后再去看)

我们每天都在使用着微信、QQ等聊天软件,但不知你是否有想过这些聊天软件是如何实现的?是否想过要制作一个属于自己的聊天室?

本篇博客将带你打造一个简单的属于自己的聊天室,将cmd作为聊天窗口,可通过内网,与周围的小伙伴相互通信,当然也可以挂到服务器上,实现通过外网的通信。同时还能通过服务端窗口对连入的用户进行管理。

先来看看我做的效果

这是服务器控制界面

输入端口号,点击启动,再打开cmd,输入telnet localhost 端口号,然后输入账号密码登陆

输入消息

 

下面就来讲讲如何实现的吧

首先我们需要先建立好用户的信息

UserInfo.java

public class UserInfo {
    private String name;
    private String possward;
    
    public String getName(){
        return this.name;
    }
    public void setName(String name) {
        this.name=name;
    }
    
    public void setPwd(String possward) {
        this.possward=possward;
    }
}

一个聊天室,我们可以将其分为服务端和客户端,而通信的简易过程如下图所示

 

对于客户端,我们需要做的是1、验证用户登陆信息。2、接收用户发送的信息并转发给目标用户

服务端目前则使用cmd进行

首先,我们先建立一个简易的储存账号密码的数据库

DaoTools.java

public class DaoTools {
    //内存用户信息数据库
    private static Map<String,UserInfo>userDB=new HashMap();
    
    
    public static boolean checkLogin(UserInfo user) {
        //验证用户名是否存在
        if(userDB.containsKey((user.getName()))){
            return true;
        }
        System.out.println("认证失败!:"+user.getName());
        return false;
    }
    //系统内部自动创建10个账号
    static {
        for(int i=0;i<10;i++) {
            UserInfo user=new UserInfo();
            user.setName("user"+i);
            user.setPwd("pwd"+i);
            userDB.put(user.getName(), user);
        }
    }
    
}

服务端服务器创建

ChatServer.java

public class ChatServer extends Thread {
    private int port;// 端口
    private boolean running = false;// 服务器是否运行中的标记
    private ServerSocket sc;// 服务器对象

    /*
     * 创建服务器对象时,必须传入端口号
     * 
     * @param port:服务器所在端口号
     */
    public ChatServer(int port) {
        this.port = port;
    }

    // 线程中启动服务器
    public void run() {
        setupServer();
    }

    // 在指定端口上启动服务器
    public void setupServer() {
        try {
            ServerSocket sc = new ServerSocket(this.port);
            running = true;
            System.out.println("服务器创建成功:" + port);
            while (running) {
                Socket client = sc.accept();// 等待连结进入
                System.out.println("进入了一个客户机连接" + client.getRemoteSocketAddress());
                // 启动一个处理线程,去处理这个连结对象
                ServerThread ct = new ServerThread(client);
                ct.start();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /*
     * 查询服务器是否在运行
     * 
     * @return: true为运行中
     */
    public boolean isRunning() {
        return this.running;
    }

    // 关闭服务器
    public void stopchatServer() {
        this.running = false;
        try {
            sc.close();
        } catch (Exception e) {}
    }
}

验证用户登陆信息,创建服务器线程

 ServerThread.java

public class ServerThread extends Thread {
    private Socket client;//线程中处理的客户对象
    private OutputStream ous;//输出流对象
    private UserInfo user;//这个线程处理对象代表的用户的信息
    //创建对象时必须传入一个Socket对象
    public ServerThread(Socket client) {
        this.client=client;
    }
    //获取这个线程对象代表的用户对象
    public UserInfo getOwerUser() {
        return this.user;
    }
    public void run() {
        processSocket();
    }
    
    public void sendMsg2Me(String msg) {    
        try {
            msg+="\r\n";
            ous.write(msg.getBytes());
            ous.flush();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    //读取客户机发来的消息
    private void processSocket() {
        try {
            InputStream ins=client.getInputStream();
            ous=client.getOutputStream();
            //将输入流ins封装为可以读取一行字符串也就是以\r\n结尾的字符串
            BufferedReader brd=new BufferedReader(new InputStreamReader(ins));
            sendMsg2Me("欢迎您来聊天!请输入你的用户名:");
            String userName=brd.readLine();
            sendMsg2Me(userName+"请输入你的密码");
            String pws=brd.readLine();
            user=new UserInfo();
            user.setName(userName);
            user.setPwd(pws);
            //调用数据库模块,验证用户是否存在
            boolean loginState=DaoTools.checkLogin(user);
            if(!loginState) {//不存在则账号关闭
                this.closeMe();
                return;
            }
            ChatTools.addClient(this);//认证成功:将这个对象加入服务器队列
            String input=brd.readLine();//一行一行的读取客户机发来的消息
            while(!"bye".equals(input)) {
                System.out.println("服务器收到的是"+input);
                //读到一条消息后,就发送给其他的客户机
                ChatTools.castMsg(this.user, input);
                input=brd.readLine();//读取下一条
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        ChatTools.castMsg(this.user, "我下线了,再见!");
        this.closeMe();
    }
    //关闭这个线程
    public void closeMe() {
        try {
            client.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

通过服务端对用户操作

 ChatTools.java

public class ChatTools {
    // 保存处理线程的队列对象
    private static List<ServerThread> stList = new ArrayList();

    private ChatTools() {
    }

    /*
     * 取得保存处理线程对象的队列
     */
    public static List<ServerThread> getAllThread() {
        return stList;
    }

    /*
     * 将一个客户对应的处理线程对象加入到队列中
     * 
     * @param ct:处理线程对象
     */
    public static void addClient(ServerThread ct) {
        // 通知大家一下,有人上限了
        castMsg(ct.getOwerUser(), "我上线啦!!目前人数" + stList.size());
        stList.add(ct);
        SwingUtilities.updateComponentTreeUI(MainServerUI.table_onlineUser);
    }

    // 给队列中某一个用户发消息
    public static void sendMsg2One(int index, String msg) {
        stList.get(index).sendMsg2Me(msg);
    }

    // 根据表中选中索引,取得处理线程对象对应的用户对象
    public static UserInfo getUser(int index) {
        return stList.get(index).getOwerUser();
    }

    /*
     * 移除队列中指定位置的处理线程对象,界面踢人时调用
     * 
     * @param index:要移除的位置
     */
    public static void removeClient(int index) {
        stList.remove(index).closeMe();
    }

    /*
     * 从队列中移除一个用户对象对应的处理线程
     * 
     * @param user:要一处的用户对象
     */
    public static void removeAllClient(UserInfo user) {
        for (int i = 0; i < stList.size(); i++) {
            ServerThread ct = stList.get(i);
            stList.remove(i);
            ct.closeMe();
            ct = null;
            castMsg(user, "我下线啦");
        }
    }

    /*
     * 服务器关闭时,调用这个方法,移除,停止队列中所有处理线程对象
     */
    public static void removeAllClient() {
        for (int i = 0; i < stList.size(); i++) {
            ServerThread st = stList.get(i);
            st.sendMsg2Me("系统消息:服务器即将关闭");
            st.closeMe();
            stList.remove(i);
            System.out.println("关闭了一个..." + i);
            st = null;
        }
    }

    /*
     * 将一条消息发送给队列中的其他客户机处理对象
     * 
     * @param sender:发送者用户对象
     * 
     * @param msg:要发送的消息内容
     */
    public static void castMsg(UserInfo sender, String msg) {
        msg = sender.getName() + "说:" + msg;
        for (int i = 0; i < stList.size(); i++) {
            ServerThread st = stList.get(i);

            // ServerThread类中定义有一个将消息发送出去的方法
                st.sendMsg2Me(msg);// 发送给每一个客户机
        }
    }
}

服务端界面

MainServerUI.java

package MyChatV1;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/*
 * 服务器端管理界面程序
 * 1.启/停 
 * 2.发布公告消息
 * 3.显示在线用户信息 
 * 4.踢人 
 * 5.对某一个人发消息 
 * 
 */
public class MainServerUI {

    private ChatServer cserver;// 服务器对象
    private JFrame jf;// 管理界面
    static JTable table_onlineUser;// 在线用户表
    private JTextField jta_msg;// 发送消息输入框
    private JTextField jta_port;// 服务器端口号输入端
    private JButton bu_control_chat;// 启动服务器的按钮

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MainServerUI mu = new MainServerUI();
        mu.showUI();
    }

    // 初始化界面
    public void showUI() {
        jf = new JFrame("javaKe服务器管理程序");
        jf.setSize(500, 300);
        FlowLayout f1 = new FlowLayout();
        jf.setLayout(f1);

        JLabel la_port = new JLabel("服务器端口:");
        jf.add(la_port);
        jta_port = new JTextField(4);
        jta_port.addKeyListener(new KeyAdapter() {
            public void keyTyped(KeyEvent e) {
                int keyChar = e.getKeyChar();
                if (keyChar >= KeyEvent.VK_0 && keyChar <= KeyEvent.VK_9) {

                } else {
                    e.consume();// 屏蔽掉非法输入
                }
            }
        });
        jf.add(jta_port);
        bu_control_chat = new JButton("启动服务器");
        jf.add(bu_control_chat);
        //启动的事件监听器
        bu_control_chat.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                actionServer();
            }
        });

        JLabel la_msg = new JLabel("要发送的消息");
        jf.add(la_msg);
        // 服务器要发送消息的输入框
        jta_msg = new JTextField(30);
        // 定义一个监听器对象:发送广播消息
        ActionListener sendCaseMsgAction = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                sendAllMsg();
            }
        };

        // 给输入框加航事件监听器,按回车时发送
        jta_msg.addActionListener(sendCaseMsgAction);
        JButton bu_send = new JButton("Send");
        // 给按钮加上发送广播消息的监听器
        bu_send.addActionListener(sendCaseMsgAction);
        jf.add(jta_msg);
        jf.add(bu_send);

        // 界面上用以显示在线用户列表的表格
        table_onlineUser = new JTable();
        // 创建我们自己的Model对象:创建时,传入处理线程列表
        List<ServerThread> sts = ChatTools.getAllThread();
        UserInfoTableModel utm = new UserInfoTableModel(sts);
        table_onlineUser.setModel(utm);
        // 将表格放到滚动面板对象上
        JScrollPane scrollPane = new JScrollPane(table_onlineUser);
        // 设定表格在面板上的大小
        table_onlineUser.setPreferredScrollableViewportSize(new Dimension(400, 100));
        // 超出大小后,JScrollPane自动出现滚动条
        scrollPane.setAutoscrolls(true);
        jf.add(scrollPane);// 将scrollPane对象加到界面上

        // 取得表格上的弹出菜单对象,加到表格上
        JPopupMenu pop = getTablePop();
        table_onlineUser.setComponentPopupMenu(pop);
        jf.setVisible(true);
        jf.setDefaultCloseOperation(3);// 界面关闭时,程序退出

    }

    /*
     * 创建表格上的弹出菜单对象,实现发信,踢人功能
     */
    private JPopupMenu getTablePop() {
        JPopupMenu pop = new JPopupMenu();// 弹出菜单对象
        JMenuItem mi_send = new JMenuItem("发信");
        ;// 菜单项对象
        mi_send.setActionCommand("send");// 设定菜单命令关键字
        JMenuItem mi_del = new JMenuItem("踢掉");// 菜单项对象
        mi_del.setActionCommand("del");// 设定菜单命令关键字
        // 弹出菜单上的事件监听器对象
        ActionListener al = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String s = e.getActionCommand();
                // 哪个菜单项点击了,这个s就是其设定的ActionCommand
                popMenuAction(s);
            }
        };
        mi_send.addActionListener(al);
        mi_del.addActionListener(al);// 给菜单加上监听器
        pop.add(mi_send);
        pop.add(mi_del);
        return pop;
    }

    // 处理弹出菜单上的事件
    private void popMenuAction(String command) {
        // 得到在表格上选中的行
        final int selectIndex = table_onlineUser.getSelectedRow();
        if (selectIndex == -1) {
            JOptionPane.showMessageDialog(jf, "请选中一个用户");
            return;
        }
        if (command.equals("del")) {
            // 从线程中移除处理线程对象
            ChatTools.removeClient(selectIndex);
        } else if (command.equals("send")) {
            UserInfo user = ChatTools.getUser(selectIndex);
            final JDialog jd = new JDialog(jf, true);// 发送对话框
            jd.setLayout(new FlowLayout());
            jd.setTitle("您将对" + user.getName() + "发信息");
            jd.setSize(200, 100);
            final JTextField jtd_m = new JTextField(20);
            //jtd_m.setPreferredSize(new Dimension(150,30));
            JButton jb = new JButton("发送!");
        //    jb.setPreferredSize(new Dimension(50,30));
            jd.add(jtd_m);
            jd.add(jb);
            // 发送按钮的事件实现
            jb.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    System.out.println("发送了一条消息啊...");
                    String msg = "系统悄悄说:" + jtd_m.getText();
                    ChatTools.sendMsg2One(selectIndex, msg);
                    jtd_m.setText("");// 清空输入框
                    jd.dispose();
                }
            });
            jd.setVisible(true);
        } else {
            JOptionPane.showMessageDialog(jf, "未知菜单:" + command);
        }
        // 刷新表格
        SwingUtilities.updateComponentTreeUI(table_onlineUser);
    }

    // 按下发送服务器消息的按钮,给所有在线用户发送消息
    private void sendAllMsg() {
        String msg = jta_msg.getText();// 得到输入框的消息
        UserInfo user = new UserInfo();
        user.setName("系统");
        user.setPwd("pwd");
        ChatTools.castMsg(user, msg); // 发送
        jta_msg.setText("");// 清空输入框

    }

    // 响应启动/停止按钮事件
    private void actionServer() {
        // 1.要得到服务器状态
        if (null == cserver) {
            String sPort = jta_port.getText();// 得到输入的端口
            int port = Integer.parseInt(sPort);
            cserver = new ChatServer(port);
            cserver.start();
            jf.setTitle("QQ服务器管理程序 :正在运行中");
            bu_control_chat.setText("Stop!");
        } else if (cserver.isRunning()) {// 己经在运行
            cserver.stopchatServer();
            cserver = null;
            // 清除所有己在运行的客户端处理线程
            ChatTools.removeAllClient();
            jf.setTitle("QQ服务器管理程序 :己停止");
            bu_control_chat.setText("Start!");
        }

    }
}

其中用到了一个自己创建的类UserInfoTableModel,用于菜单中的信息显示

public class UserInfoTableModel implements TableModel {
    List<ServerThread> sts = ChatTools.getAllThread();
    public UserInfoTableModel(List<ServerThread> sts) {
        this.sts=sts;
        
    }
    @Override
    public void addTableModelListener(TableModelListener l) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        // TODO Auto-generated method stub
        return String.class;
    }

    @Override
    public int getColumnCount() {
        // TODO Auto-generated method stub
        return 1;
    }

    @Override
    public String getColumnName(int columnIndex) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getRowCount() {
        // TODO Auto-generated method stub
        return sts.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
            return sts.get(rowIndex).getOwerUser().getName();
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void removeTableModelListener(TableModelListener l) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO Auto-generated method stub
        
    }

}

这样我们的聊天室就大功告成了。(这个聊天室的客户端界面我还没做,打算对上面代码重新整理之后再写。当然也期待你为这个聊天室添加上一个界面)

 

posted @ 2018-09-26 21:16  ITryagain  阅读(12233)  评论(3编辑  收藏  举报