Java网络聊天室---个人博客

article/2025/10/23 8:14:00

Java网络聊天室 ———个人博客

一、项目简介

功能描述:

使用图形用户界面和socket通信,能实现一个聊天室中多人聊天,可以两人私聊,可以发送文件。
实现类似QQ用户注册、登录、聊天等功能。

参考git地址或博客地址:

https://github.com/xiao-bailing/CommunicationOnline.git

个人负责任务:

  1. 用Java图形用户界面编写聊天室服务器端, 支持多个客户端连接到一个服务器。
  2. 服务器能够群发系统消息,能够对用户私发消息,能够强行让某些用户下线。
  3. 客户端的上线下线要求能够在其他客户端上面实时刷新。
  4. 服务器能够查看在线用户和注册用户

二、功能架构图

在这里插入图片描述

三、个人任务简述

1.完成的任务与功能:

  1. 多客户端模式下,实现客户与客户的单独通信,要求信息通过服务器中转。
  2. 端到端的通信,实现并行模式
  3. 实现端到端的文件传输。

实现多客户的情况下实现客户与客户之间的通信,关键在于服务器端与客户端连接时,获取与客户端一一对应的socket套接字需要在服务器端中存储起来,在客户端收或者发信息时,只要在服务端找到对应的socket即可建立连接,互相传输信息,从而使服务器在聊天过程中起到中转消息的功能。

遇到的问题:

⑴ 如客户0与客户1聊天,客户0先给客户1发送消息,此时客户1并不能及时地收到,此时,客户1若在未知的情况下回客户0一条信息,之前客户0发的消息便会同时出现。以上过程服务器端运行过程正常。

原因:readline() 方法引起的阻塞问题。以下是客户端收发消息的程序片段。
在这里插入图片描述

readline()是一个阻塞函数,当没有数据读取时就一直阻塞在那,只有当数据流关闭或者读取到“/r”“/n”“/r/n”才会返回。所以在收消息的客户端中,最初系统没有输入就会一直阻塞在if((readline=sin.readline())!=null) 不会继续执行底下收消息的动作,只有在最初本应收消息的客户端也系统输入之后,就不再阻塞,也就可以接收那条最初本应接收的消息。

解决办法:将客户端收发消息的操作分为收消息线程与发消息线程,同时实现要求二实现端到端的并行模式

⑵ 服务器端与接收消息的线程如何判断消息中是否含有文件?以及如何区别是文本消息还是文件消息?
在这里插入图片描述

服务器中转消息

  • 与服务器在中转的流中,除了有文本消息通过PrintWriter 由字符流转化成的字节流,也有文件字节流。对于文本消息,则先转化成字符流,读取需要接收消息客户端的编号,在转换成字节流输出,即先解码再编码的过程;对于文件字节流则是一个先读再写的过程。

2.git提交记录截图

在这里插入图片描述
在这里插入图片描述

代码部分

//DataBuffer.java

//服务器端从文件中读取数据,进行缓存
package server;import common.model.entity.User;
import server.model.entity.OnlineUserTableModel;
import server.model.entity.RegistedUserTableModel;import java.awt.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentSkipListMap;public class DataBuffer {// 服务器端套接字public static ServerSocket serverSocket;//在线用户的IO Mappublic static Map<Long, OnlineClientIOCache> onlineUserIOCacheMap;//在线用户Mappublic static Map<Long, User> onlineUsersMap;//服务器配置参数属性集public static Properties configProp;// 已注册用户表的Modelpublic static RegistedUserTableModel registedUserTableModel;// 当前在线用户表的Modelpublic static OnlineUserTableModel onlineUserTableModel;// 当前服务器所在系统的屏幕尺寸public static Dimension screenSize;static{// 初始化onlineUserIOCacheMap = new ConcurrentSkipListMap<Long,OnlineClientIOCache>();onlineUsersMap = new ConcurrentSkipListMap<Long, User>();configProp = new Properties();registedUserTableModel = new RegistedUserTableModel();onlineUserTableModel = new OnlineUserTableModel();screenSize = Toolkit.getDefaultToolkit().getScreenSize();// 加载服务器配置文件try {configProp.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("serverconfig.properties"));} catch (IOException e) {e.printStackTrace();}}}

//UserService.java

/*用于用户账号管理,预先创建几个账号,然后存到文件中,每次服务器执行时,都会将文件中的账号信息读入,同时新创建的用户账号也会存入到文件中去。*/
package server.model.service;import common.model.entity.User;
import common.util.IOUtil;
import server.DataBuffer;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class UserService {private static int idCount = 3; //id/** 新增用户 */public void addUser(User user){user.setId(++idCount);List<User> users = loadAllUser();users.add(user);saveAllUser(users);}/** 用户登录 */public User login(long id, String password){User result = null;List<User> users = loadAllUser();for (User user : users) {if(id == user.getId() && password.equals(user.getPassword())){result = user;break;}}return result;}/** 根据ID加载用户 */public User loadUser(long id){User result = null;List<User> users = loadAllUser();for (User user : users) {if(id == user.getId()){result = user;break;}}return result;}/** 加载所有用户 */@SuppressWarnings("unchecked")public List<User> loadAllUser() {List<User> list = null;ObjectInputStream ois = null;try {ois = new ObjectInputStream(new FileInputStream(DataBuffer.configProp.getProperty("dbpath")));list = (List<User>)ois.readObject();} catch (Exception e) {e.printStackTrace();}finally{IOUtil.close(ois);}return list;}private void saveAllUser(List<User> users) {ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(new FileOutputStream(DataBuffer.configProp.getProperty("dbpath")));//写回用户信息oos.writeObject(users);oos.flush();} catch (Exception e) {e.printStackTrace();}finally{IOUtil.close(oos);}}/** 初始化几个测试用户 */public void initUser(){User user = new User("admin", "Admin", 'm', 0);user.setId(1);User user2 = new User("123", "yong", 'm', 1);user2.setId(2);User user3 = new User("123", "anni", 'f', 2);user3.setId(3);List<User> users = new CopyOnWriteArrayList<User>();users.add(user);users.add(user2);users.add(user3);this.saveAllUser(users);}public static void main(String[] args){new UserService().initUser();List<User> users = new UserService().loadAllUser();for (User user : users) {System.out.println(user);}}
}

服务端的界面

//OnlineUserTableModel.java

package server.model.entity;import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;public class OnlineUserTableModel extends AbstractTableModel {private static final long serialVersionUID = -444245379288364831L;/** 列名标题 */private String[] title = {"账号", "昵称", "性别"};/** 数据行 */private List<String[]> rows = new ArrayList<String[]>();@Overridepublic int getRowCount() {return rows.size();}@Overridepublic int getColumnCount() {return title.length;}@Overridepublic String getColumnName(int column) {return title[column];}@Overridepublic String getValueAt(int row, int column) {return (rows.get(row))[column];}public void add(String[] value) {int row = rows.size();rows.add(value);fireTableRowsInserted(row, row);}public void remove(long id) {int row = -1;for (int i = 0; i <= rows.size(); i++) {if (String.valueOf(id).equals(getValueAt(i , 0))) {row = i;break;}}rows.remove(row);fireTableRowsDeleted(2, 3);}
}

//RegistedUserTableModel.java

package server.model.entity;import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;public class RegistedUserTableModel extends AbstractTableModel {private static final long serialVersionUID = -6299791067241594227L;//列名标题private String[] title = {"账号","密码","昵称","性别"};//数据行private List<String[]> rows = new ArrayList<String[]>();@Overridepublic int getRowCount() {return rows.size();}@Overridepublic int getColumnCount() {return title.length;}public String getColumnName(int column){return title[column];}@Overridepublic String getValueAt(int rowIndex, int columnIndex) {return (rows.get(rowIndex))[columnIndex];}public void add(String[] value){int row = rows.size();rows.add(value);fireTableRowsInserted(row, row);}public void remove(long id){int row = -1;for(int i=0;i<=rows.size();i++){if (String.valueOf(id).equals(getValueAt(i, 0))) {row = i;break;}}rows.remove(row);fireTableRowsDeleted(2, 3);}
}

//ServerInfoFrame.java

package server.ui;import common.model.entity.User;
import server.DataBuffer;
import server.controller.RequestProcessor;
import server.model.service.UserService;import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimerTask;public class ServerInfoFrame extends JFrame {private static final long serialVersionUID = 6274443611957724780L;private JTextField jta_msg;private JTable onlineUserTable ;private JTable registedUserTable ;public ServerInfoFrame() {init();loadData();setVisible(true);}public void init() {  //初始化窗体this.setTitle("服务器启动");//设置服务器启动标题this.setBounds((DataBuffer.screenSize.width - 700)/2,(DataBuffer.screenSize.height - 475)/2, 700, 475);this.setLayout(new BorderLayout());JPanel panel = new JPanel();Border border = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);panel.setBorder(BorderFactory.createTitledBorder(border,"服务器监控", TitledBorder.LEFT,TitledBorder.TOP));this.add(panel, BorderLayout.NORTH);JLabel label = new JLabel("服务器端口: ");panel.add(label);JButton exitBtn = new JButton("关闭服务器");//关闭关闭服务器按钮panel.add(exitBtn);JLabel la_msg = new JLabel("要发送的消息");panel.add(la_msg);// 服务器要发送消息的输入框jta_msg = new JTextField(30);// 定义一个监听器对 象:发送广播消息ActionListener sendCaseMsgAction = new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {try {sendAllMsg();} catch (IOException e1) {e1.printStackTrace();}}};// 给输入框加航事件监听器,按回车时发送jta_msg.addActionListener(sendCaseMsgAction);JButton bu_send = new JButton("Send");// 给按钮加上发送广播消息的监听器bu_send.addActionListener(sendCaseMsgAction);panel.add(jta_msg);panel.add(bu_send);//使用服务器缓存中的TableModelonlineUserTable = new JTable(DataBuffer.onlineUserTableModel);registedUserTable = new JTable(DataBuffer.registedUserTableModel);// 取得表格上的弹出菜单对象,加到表格上JPopupMenu pop = getTablePop();onlineUserTable.setComponentPopupMenu(pop);//选项卡JTabbedPane tabbedPane = new JTabbedPane();tabbedPane.addTab("在线用户列表", new JScrollPane(onlineUserTable));tabbedPane.addTab("已注册用户列表", new JScrollPane(registedUserTable));tabbedPane.setTabComponentAt(0, new JLabel("在线用户列表"));this.add(tabbedPane, BorderLayout.CENTER);final JLabel stateBar = new JLabel("", SwingConstants.RIGHT);stateBar.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));//用定时任务来显示当前时间new java.util.Timer().scheduleAtFixedRate(new TimerTask(){DateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");@Overridepublic void run() {stateBar.setText("当前时间:" + df.format(new Date()) + "  ");}}, 0, 1000);this.add(stateBar, BorderLayout.SOUTH); //把状态栏添加到窗体的南边//关闭窗口this.addWindowListener(new WindowAdapter(){@Overridepublic void windowClosing(WindowEvent e) {logout();}});/* 添加关闭服务器按钮事件处理方法 */exitBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(final ActionEvent event) {logout();}});}/** 创建表格上的弹出菜单对象,实现发信,踢人功能*/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() {@Overridepublic void actionPerformed(ActionEvent e) {String s = e.getActionCommand();// 哪个菜单项点击了,这个s就是其设定的ActionCommandpopMenuAction(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 = onlineUserTable.getSelectedRow();String usr_id = (String)onlineUserTable.getValueAt(selectIndex,0);System.out.println(usr_id);if (selectIndex == -1) {JOptionPane.showMessageDialog(this, "请选中一个用户");return;}if (command.equals("del")) {// 从线程中移除处理线程对象try {RequestProcessor.remove(DataBuffer.onlineUsersMap.get(Long.valueOf(usr_id)));} catch (IOException e) {e.printStackTrace();}} else if (command.equals("send")) {final JDialog jd = new JDialog(this, true);// 发送对话框jd.setLayout(new FlowLayout());
//			jd.setTitle("您将对" + user.getNickname() + "发信息");jd.setSize(200, 100);final JTextField jtd_m = new JTextField(20);JButton jb = new JButton("发送!");jd.add(jtd_m);jd.add(jb);// 发送按钮的事件实现jb.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("发送了一条消息啊...");String msg = jtd_m.getText();
//					ChatTools.sendMsg2One(selectIndex, msg);try {
//                        System.out.println(DataBuffer.onlineUsersMap.get((long) onlineUserTable.get));RequestProcessor.chat_sys(msg,DataBuffer.onlineUsersMap.get(Long.valueOf(usr_id)));} catch (IOException e1) {e1.printStackTrace();}jtd_m.setText("");// 清空输入框jd.dispose();}});jd.setVisible(true);} else {JOptionPane.showMessageDialog(this, "未知菜单:" + command);}// 刷新表格SwingUtilities.updateComponentTreeUI(onlineUserTable);}// 按下发送服务器消息的按钮,给所有在线用户发送消息private void sendAllMsg() throws IOException {RequestProcessor.board(jta_msg.getText());jta_msg.setText("");// 清空输入框}/** 把所有已注册的用户信息加载到RegistedUserTableModel中 */private void loadData(){List<User> users = new UserService().loadAllUser();for (User user : users) {DataBuffer.registedUserTableModel.add(new String[]{String.valueOf(user.getId()),user.getPassword(),user.getNickname(),String.valueOf(user.getSex())});}}/** 关闭服务器 */private void logout() {int select = JOptionPane.showConfirmDialog(ServerInfoFrame.this,"确定关闭吗?\n\n关闭服务器将中断与所有客户端的连接!","关闭服务器",JOptionPane.YES_NO_OPTION);//如果用户点击的是关闭服务器按钮时会提示是否确认关闭。if (select == JOptionPane.YES_OPTION) {System.exit(0);//退出系统}else{//覆盖默认的窗口关闭事件动作setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);}}
}

服务端的核心

//RequestProcessor.java

//用于处理客户端发来的消息,并进行回复,对于每一项操作的实现原理无非就是服务器处理内部数据或是向指定客户端发送消息
package server.controller;import common.model.entity.*;
import server.DataBuffer;
import server.OnlineClientIOCache;
import server.model.service.UserService;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArrayList;public class RequestProcessor implements Runnable {//当前正在请求服务器的客户端Socketprivate Socket currentClientSocket;public RequestProcessor(Socket currentClientSocket){this.currentClientSocket = currentClientSocket;}@Overridepublic void run() {//是否不间断监听boolean flag = true;try{OnlineClientIOCache currentClientIoCache = new OnlineClientIOCache(new ObjectInputStream(currentClientSocket.getInputStream()),new ObjectOutputStream(currentClientSocket.getOutputStream()));while(flag){//不停地读取客户端发过来的请求对象//从请求输入流中读取到客户端提交的请求对象Request request = (Request)currentClientIoCache.getOis().readObject();System.out.println("Server读取了客户端的请求:" + request.getAction());//获取请求中的动作String actionName = request.getAction();//用户注册if(actionName.equals("userRegiste")){registe(currentClientIoCache, request);//用户登录}else if(actionName.equals("userLogin")){login(currentClientIoCache, request);}else if("exit".equals(actionName)){       //请求断开连接flag = logout(currentClientIoCache, request);}else if("chat".equals(actionName)){       //聊天chat(request);}else if("shake".equals(actionName)){      //振动shake(request);}else if("toSendFile".equals(actionName)){ //准备发送文件toSendFile(request);}else if("agreeReceiveFile".equals(actionName)){ //同意接收文件agreeReceiveFile(request);}else if("refuseReceiveFile".equals(actionName)){ //拒绝接收文件refuseReceiveFile(request);}}}catch(Exception e){e.printStackTrace();}}/** 拒绝接收文件 */private void refuseReceiveFile(Request request) throws IOException {FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");Response response = new Response();  //创建一个响应对象response.setType(ResponseType.REFUSERECEIVEFILE);response.setData("sendFile", sendFile);response.setStatus(ResponseStatus.OK);//向请求方的输出流输出响应OnlineClientIOCache ocic = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());this.sendResponse(ocic, response);}/** 同意接收文件 */private void agreeReceiveFile(Request request) throws IOException {FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");//向请求方(发送方)的输出流输出响应Response response = new Response();  //创建一个响应对象response.setType(ResponseType.AGREERECEIVEFILE);response.setData("sendFile", sendFile);response.setStatus(ResponseStatus.OK);OnlineClientIOCache sendIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());this.sendResponse(sendIO, response);//向接收方发出接收文件的响应Response response2 = new Response();  //创建一个响应对象response2.setType(ResponseType.RECEIVEFILE);response2.setData("sendFile", sendFile);response2.setStatus(ResponseStatus.OK);OnlineClientIOCache receiveIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());this.sendResponse(receiveIO, response2);}/** 客户端退出 */public boolean logout(OnlineClientIOCache oio, Request request) throws IOException{System.out.println(currentClientSocket.getInetAddress().getHostAddress()+ ":" + currentClientSocket.getPort() + "走了");User user = (User)request.getAttribute("user");//把当前上线客户端的IO从Map中删除DataBuffer.onlineUserIOCacheMap.remove(user.getId());//从在线用户缓存Map中删除当前用户DataBuffer.onlineUsersMap.remove(user.getId());Response response = new Response();  //创建一个响应对象response.setType(ResponseType.LOGOUT);response.setData("logoutUser", user);oio.getOos().writeObject(response);  //把响应对象往客户端写oio.getOos().flush();currentClientSocket.close();  //关闭这个客户端SocketDataBuffer.onlineUserTableModel.remove(user.getId()); //把当前下线用户从在线用户表Model中删除iteratorResponse(response);//通知所有其它在线客户端return false;  //断开监听}/** 注册 */public void registe(OnlineClientIOCache oio, Request request) throws IOException {User user = (User)request.getAttribute("user");UserService userService = new UserService();userService.addUser(user);Response response = new Response();  //创建一个响应对象response.setStatus(ResponseStatus.OK);response.setData("user", user);oio.getOos().writeObject(response);  //把响应对象往客户端写oio.getOos().flush();//把新注册用户添加到RegistedUserTableModel中DataBuffer.registedUserTableModel.add(new String[]{String.valueOf(user.getId()),user.getPassword(),user.getNickname(),String.valueOf(user.getSex())});}/** 登录 */public void login(OnlineClientIOCache currentClientIO, Request request) throws IOException {String idStr = (String)request.getAttribute("id");String password = (String) request.getAttribute("password");UserService userService = new UserService();User user = userService.login(Long.parseLong(idStr), password);Response response = new Response();  //创建一个响应对象if(null != user){if(DataBuffer.onlineUsersMap.containsKey(user.getId())){ //用户已经登录了response.setStatus(ResponseStatus.OK);response.setData("msg", "该 用户已经在别处上线了!");currentClientIO.getOos().writeObject(response);  //把响应对象往客户端写currentClientIO.getOos().flush();}else { //正确登录DataBuffer.onlineUsersMap.put(user.getId(), user); //添加到在线用户//设置在线用户response.setData("onlineUsers",new CopyOnWriteArrayList<User>(DataBuffer.onlineUsersMap.values()));response.setStatus(ResponseStatus.OK);response.setData("user", user);currentClientIO.getOos().writeObject(response);  //把响应对象往客户端写currentClientIO.getOos().flush();//通知其它用户有人上线了Response response2 = new Response();response2.setType(ResponseType.LOGIN);response2.setData("loginUser", user);iteratorResponse(response2);//把当前上线的用户IO添加到缓存Map中DataBuffer.onlineUserIOCacheMap.put(user.getId(),currentClientIO);//把当前上线用户添加到OnlineUserTableModel中DataBuffer.onlineUserTableModel.add(new String[]{String.valueOf(user.getId()),user.getNickname(),String.valueOf(user.getSex())});}}else{ //登录失败response.setStatus(ResponseStatus.OK);response.setData("msg", "账号或密码不正确!");currentClientIO.getOos().writeObject(response);currentClientIO.getOos().flush();}}/** 聊天 */public void chat(Request request) throws IOException {Message msg = (Message)request.getAttribute("msg");Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.CHAT);response.setData("txtMsg", msg);if(msg.getToUser() != null){ //私聊:只给私聊的对象返回响应OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());sendResponse(io, response);}else{  //群聊:给除了发消息的所有客户端都返回响应for(Long id : DataBuffer.onlineUserIOCacheMap.keySet()){if(msg.getFromUser().getId() == id ){	continue; }sendResponse(DataBuffer.onlineUserIOCacheMap.get(id), response);}}}/*广播*/public static void board(String str) throws IOException {User user = new User(1,"admin");Message msg = new Message();msg.setFromUser(user);msg.setSendTime(new Date());DateFormat df = new SimpleDateFormat("HH:mm:ss");StringBuffer sb = new StringBuffer();sb.append(" ").append(df.format(msg.getSendTime())).append(" ");sb.append("系统通知\n  "+str+"\n");msg.setMessage(sb.toString());Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.BOARD);response.setData("txtMsg", msg);for (Long id : DataBuffer.onlineUserIOCacheMap.keySet()) {sendResponse_sys(DataBuffer.onlineUserIOCacheMap.get(id), response);}}/*踢除用户*/public static void remove(User user_) throws IOException{User user = new User(1,"admin");Message msg = new Message();msg.setFromUser(user);msg.setSendTime(new Date());msg.setToUser(user_);StringBuffer sb = new StringBuffer();DateFormat df = new SimpleDateFormat("HH:mm:ss");sb.append(" ").append(df.format(msg.getSendTime())).append(" ");sb.append("系统通知您\n  "+"您被强制下线"+"\n");msg.setMessage(sb.toString());Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.REMOVE);response.setData("txtMsg", msg);OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());sendResponse_sys(io, response);}/*私信*/public static void chat_sys(String str,User user_) throws IOException{User user = new User(1,"admin");Message msg = new Message();msg.setFromUser(user);msg.setSendTime(new Date());msg.setToUser(user_);DateFormat df = new SimpleDateFormat("HH:mm:ss");StringBuffer sb = new StringBuffer();sb.append(" ").append(df.format(msg.getSendTime())).append(" ");sb.append("系统通知您\n  "+str+"\n");msg.setMessage(sb.toString());Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.CHAT);response.setData("txtMsg", msg);OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());sendResponse_sys(io, response);}/** 发送振动 */public void shake(Request request)throws IOException {Message msg = (Message) request.getAttribute("msg");DateFormat df = new SimpleDateFormat("HH:mm:ss");StringBuffer sb = new StringBuffer();sb.append(" ").append(msg.getFromUser().getNickname()).append("(").append(msg.getFromUser().getId()).append(") ").append(df.format(msg.getSendTime())).append("\n  给您发送了一个窗口抖动\n");msg.setMessage(sb.toString());Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.SHAKE);response.setData("ShakeMsg", msg);OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());sendResponse(io, response);}/** 准备发送文件 */public void toSendFile(Request request)throws IOException{Response response = new Response();response.setStatus(ResponseStatus.OK);response.setType(ResponseType.TOSENDFILE);FileInfo sendFile = (FileInfo)request.getAttribute("file");response.setData("sendFile", sendFile);//给文件接收方转发文件发送方的请求OnlineClientIOCache ioCache = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());sendResponse(ioCache, response);}/** 给所有在线客户都发送响应 */private void iteratorResponse(Response response) throws IOException {for(OnlineClientIOCache onlineUserIO : DataBuffer.onlineUserIOCacheMap.values()){ObjectOutputStream oos = onlineUserIO.getOos();oos.writeObject(response);oos.flush();}}/** 向指定客户端IO的输出流中输出指定响应 */private void sendResponse(OnlineClientIOCache onlineUserIO, Response response)throws IOException {ObjectOutputStream oos = onlineUserIO.getOos();oos.writeObject(response);oos.flush();}/** 向指定客户端IO的输出流中输出指定响应 */private static void sendResponse_sys(OnlineClientIOCache onlineUserIO, Response response)throws IOException {ObjectOutputStream oos = onlineUserIO.getOos();oos.writeObject(response);oos.flush();}
}

//MainServer.java

package server;import server.controller.RequestProcessor;
import server.ui.ServerInfoFrame;import javax.swing.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class MainServer {public static void main(String[] args) {int port = Integer.parseInt(DataBuffer.configProp.getProperty("port"));//初始化服务器套节字try {DataBuffer.serverSocket = new ServerSocket(port);} catch (IOException e) {e.printStackTrace();}//启动新线程进行客户端连接监听new Thread(new Runnable() {@Overridepublic void run() {try {while (true) {// 监听客户端的连接Socket socket = DataBuffer.serverSocket.accept();System.out.println("客户来了:"+ socket.getInetAddress().getHostAddress()+ ":" + socket.getPort());//针对每个客户端启动一个线程,在线程中调用请求处理器来处理每个客户端的请求new Thread(new RequestProcessor(socket)).start();}} catch (IOException e) {e.printStackTrace();}}}).start();//设置外观感觉JFrame.setDefaultLookAndFeelDecorated(true);JDialog.setDefaultLookAndFeelDecorated(true);try {UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());} catch (Exception e) {e.printStackTrace();}//启动服务器监控窗体new ServerInfoFrame();}
}

//OnlineClientIOCache.java

package server;import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class OnlineClientIOCache {//针对同一个Socket中获取的流在全局范围中最好只包装一次,以免出错private ObjectInputStream ois; // 对象输入流private ObjectOutputStream oos; // 对象输出流public OnlineClientIOCache(ObjectInputStream ois, ObjectOutputStream oos){this.ois = ois;this.oos = oos;}public ObjectOutputStream getOos(){return oos;}public ObjectInputStream getOis() {return ois;}}

多线程实现多人聊天项目

1.创建客户端窗口

要求文字内容区域不能编辑

ta.setEditable(false);

输入文字按回车发出在ta里 ,通过写一段监听,拿到tf的内容,append到ta

tf.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO Auto-generated method stubString strSend = tf.getText();if(strSend.trim().length()==0) {return;}tf.setText("");ta.append(strSend + "\n");}});

2.创建服务器端窗口,与客户端连接上

服务器启动,一个客户端可以连上,再考虑发送信息到服务器上

一个客户端可以发一句话

⑴利用多线程实现连接多个客户端

并行模式

主要是对客户端收发消息的功能进行改动,客户端在与服务器通过获取对方socket套接字建立连接之后,就创建两个线程分别收发消息。收发消息分两个线程,实现并行模式使得收发消息互不影响。

TCP/IP

在这里插入图片描述
在这里插入图片描述

建立连接

在这里插入图片描述

三次握手

在这里插入图片描述

四次挥手

在这里插入图片描述

心得总结

之前课本上的基础知识自我感觉掌握良好,但是当在运用的过程中却发现自己需要努力的空间还有很大。例如,socket套接字的网络编程与多线程的综合运用,把二者放在一起运用时,起初还摸不着头脑,思路混乱。完成这个小型的聊天系统,加深了我对知识点的理解,使我能够初步掌握运用。并且在此过程中,真实遇到了许多问题,一些类的方法及其特殊的性质起初不太清楚,如阻塞函数readline(), 通过查询JavaJDK的API网页,我对于这个点理解与记忆更加深刻。由此可见,实践非常重要,可以从实际遇到的问题中学到上课学不到的知识。

还有一点是总结与梳理。在完成了代码之后,画示意图和写博客的过程也是一个自我梳理,再一次巩固学习的过程。例如,io流的字节字符流的转化,上课听讲的时候,只是知晓这个概念的存在,而在画示意图和写博客的过程中,发现之前对于二者有些混淆,二者的转化关系也不清晰,通过梳理,算是一种温故而知新。


http://chatgpt.dhexx.cn/article/AxIU5kcp.shtml

相关文章

Java实现ChatRoom

基于连接通信Socket、多线程的Java聊天室 1、开发环境&#xff1a; IDEA2018.1JDK1.8 2、实现功能&#xff1a; 实现了模拟登录注册、群聊、私聊、显示当前在线人数列表&#xff1b; 在发送信息时&#xff0c;会向对方发送者及显示发送时间&#xff1b; 显示在线人数列表…

Java聊天室

项目介绍&#xff1a; Java聊天室是期末设计&#xff0c; 阿里巴巴druidmysql多线程GUImvn项目java Socket 服务端模块&#xff1a;踢出聊天室&#xff1a;管理员可以踢出发言不当的用户。只有当开启服务端的时候&#xff0c;客户端才能起到作用。 客户端模块&#xff1a; 注册…

用Java实现简易聊天室

说明&#xff1a;如果一个 类&#xff0c;需要有界面的显示&#xff0c;那么该类就需要继承自JFrame&#xff0c;此时&#xff0c;该类就可以被称为一个“窗体类"。 服务端代码&#xff1a; package cn.qy.chat;import javax.swing.*; import java.awt.*; import java.aw…

微信小程序购物车功能实现(干货满满)

微信小程序定制好看的购物车页面&#xff0c;实现购物车功能&#xff0c;希望对您有所帮助&#xff01; 1. 应用场景 2. 思路分析 3. 代码分析 4. 具体实现代码 效果截图&#xff1a; 1.应用场景 适用于商城、秒杀、商品购买等类型的小程序&#xff0c;负责将顾客浏览的商…

微信小程序微商城(八):缓存实现商品购物车功能

IT实战联盟博客&#xff1a;http://blog.100boot.cn 上一篇&#xff1a;微信小程序微商城&#xff08;七&#xff09;&#xff1a;动态API实现商品分类 看效果 购物车.gif 开发计划 1、商品详情页将商品信息放入缓存 2、购物车页面读取缓存获取商品信息 3、购物车商品计算…

微信小程序开发一个小型商城(六、购物车页面)

上一篇文章&#xff1a;微信小程序开发一个小型商城&#xff08;五、商品详情&#xff09; 当我们在商品详情界面中点击添加购物后&#xff0c;会跳转到购物车界面&#xff0c;购物车界面是一个tabbar&#xff0c;在跳转的时候需要加上ope-type。看下购物车的静态页面把&#x…

微信小程序实现一个购物车页面的简易列表效果

本文只是简单的模仿天猫APP的购物车列表的样式效果&#xff0c;并实现了部分事件功能&#xff0c;功能并不完善&#xff0c;请降低期待观看。 天猫APP的购物车效果&#xff1a; 小程序模仿的实现效果&#xff1a; wxml部分的代码&#xff1a; <view wx:if"{{!isCartEmp…

【Python之pymysql库学习】一、分析fetchone()、fetchmany()、fetchall()(保姆级图文+实现代码)

目录 实现效果实现思路实现代码总结 欢迎关注 『Python之pymysql库学习』 系列&#xff0c;持续更新中 欢迎关注 『Python之pymysql库学习』 系列&#xff0c;持续更新中 实现效果 实现思路 其实有半数代码是创建数据库和创建数据表并插入数据这些环境配置部分我都写好了&…

fetchone、fetchall

fetchone(): 该方法获取下一个查询结果集。结果集是一个对象,读取一行结果&#xff0c;读取完指向下一行&#xff0c;到空为止 fetchall():接收全部的返回结果行&#xff0c;到空为止 fetchone() &#xff1a; 返回单个的元组&#xff0c;也就是一条记录(row)&#xff0c;如果没…

python fetchall方法_Python连接MySQL并使用fetchall()方法过滤特殊字符

python3.3从mysql里取出的数据莫名其妙有括号和逗号每天跟自己喜欢的人一起&#xff0c;通电话&#xff0c;旅行&#xff0c;重复一个承诺和梦想&#xff0c;听他第二十八次提起童年往事&#xff0c;每年的同一天和他庆祝生日&#xff0c;每年的情人节圣诞节除夕&#xff0c;也…

pdo fetchAll

作用 fetchAll()方法是获取结果集中的所有行.其返回值是一个包含结果集中所有数据的二维数组。 PDOStatement::fetchAll ([ int $fetch_style [, mixed $fetch_argument[, array$ctor_args array() ]]] ) fetch_style:控制结果的返回方式 PDO::FETCH_ASSOC 关联数组形式 PD…

记录一个常用函数fetchall()的使用过程

fetchall() 作用是返回多个元组&#xff0c;即对应数据库里的多条数据概念&#xff1b; 常见用法是 cursor.execute(‘select * from table’) value cursor.fetchall() 此时&#xff0c;print(value)则会输出以下二维元组&#xff0c;如下图 拓展&#xff1a; 同类函数fet…

Python从Oracle数据库中获取数据——fetchall(),fetchone(),fetchmany()函数功能分析

Python从Oracle数据库中获取数据——fetchall(),fetchone(),fetchmany()函数功能分析 一、fetchall()&#xff0c;fetchone()&#xff0c;fetchmany()简单介绍 1、fetchall()函数,它的返回值是多个元组,即返回多个行记录,如果没有结果,返回的是() 2、fetchone()函数,它的返回…

KITTI数据集可视化(一):点云多种视图的可视化实现

如有错误&#xff0c;恳请指出。 在本地上&#xff0c;可以安装一些软件&#xff0c;比如&#xff1a;Meshlab&#xff0c;CloudCompare等3D查看工具来对点云进行可视化。而这篇博客是将介绍一些代码工具将KITTI数据集进行可视化操作&#xff0c;包括点云鸟瞰图&#xff0c;FOV…

KITTI数据集的点云格式转PCD格式

参考文章&#xff1a;https://blog.csdn.net/xinguihu/article/details/78922005 KITTI数据集应该不用多做介绍了&#xff0c;基本上做自动驾驶的都知道这个东西。最近本人用到这个数据集想看看里面的点云长什么模样&#xff0c;却发现有点别扭&#xff0c;没有直接可以看的工…

使用kitti数据集实现自动驾驶——发布照片、点云、IMU、GPS、显示2D和3D侦测框

本次内容主要是使用kitti数据集来可视化kitti车上一些传感器&#xff08;相机、激光雷达、IMU&#xff09;采集的资料以及对行人和车辆进行检测并在图像中画出行人和车辆的2D框、在点云中画出行人和车辆的3D框。 首先先看看最终实现的效果&#xff1a; 自动驾驶视频 看了上面的…

KITTI数据集-label解析笔记

笔记摘自&#xff1a;KITTI数据集--label解析与传感器间坐标转换参数解析_苏源流的博客-CSDN博客 KITTI数据集是自动驾驶领域最知名的数据集之一。 一、kitti数据集&#xff0c;label解析 16个数代表的含义&#xff1a; 第1个字符串&#xff1a;代表目标的类别 Car, Van, Tru…

16个车辆信息检测数据集收集汇总(简介及链接)

16个车辆信息检测数据集收集汇总&#xff08;简介及链接) 转载自&#xff1a;https://blog.csdn.net/u014546828/article/details/109089621?utm_mediumdistribute.pc_relevant.none-task-blog-baidujs_baidulandingword-1&spm1001.2101.3001.4242 目录 1. UA-DETRAC …

双目网络公开数据集的特性

文章目录 概述SceneFlowKITTI 2012 & 2015ETH3D 2017Middlebury 2014 概述 参考文章&#xff1a;Rethinking Training Strategy in Stereo Matching 主流双目公开数据集有&#xff1a;SceneFlow、KITTI、ETH3D、MB。 各个双目网络主流训练数据视差分布的直方图&#xff1a;…

KITTI数据集下载及介绍

KITTI数据集下载及介绍 KITTI数据集由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创办&#xff0c;是目前国际上最大的自动驾驶场景下的计算机视觉算法评测数据集。该数据集用于评测立体图像(stereo)&#xff0c;光流(optical flow)&#xff0c;视觉测距(visual odometry…