我们每天都在使用着微信、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 MapuserDB=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 ListstList = new ArrayList(); private ChatTools() { } /* * 取得保存处理线程对象的队列 */ public static List 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对象:创建时,传入处理线程列表 Liststs = 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 { Liststs = ChatTools.getAllThread(); public UserInfoTableModel(List 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 } }
这样我们的聊天室就大功告成了。(这个聊天室的客户端界面我还没做,打算对上面代码重新整理之后再写。当然也期待你为这个聊天室添加上一个界面)