Java网络聊天室 ———个人博客
一、项目简介
功能描述:
使用图形用户界面和socket通信,能实现一个聊天室中多人聊天,可以两人私聊,可以发送文件。
实现类似QQ用户注册、登录、聊天等功能。
参考git地址或博客地址:
https://github.com/xiao-bailing/CommunicationOnline.git
个人负责任务:
- 用Java图形用户界面编写聊天室服务器端, 支持多个客户端连接到一个服务器。
- 服务器能够群发系统消息,能够对用户私发消息,能够强行让某些用户下线。
- 客户端的上线下线要求能够在其他客户端上面实时刷新。
- 服务器能够查看在线用户和注册用户
二、功能架构图
三、个人任务简述
1.完成的任务与功能:
- 多客户端模式下,实现客户与客户的单独通信,要求信息通过服务器中转。
- 端到端的通信,实现并行模式
- 实现端到端的文件传输。
实现多客户的情况下实现客户与客户之间的通信,关键在于服务器端与客户端连接时,获取与客户端一一对应的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流的字节字符流的转化,上课听讲的时候,只是知晓这个概念的存在,而在画示意图和写博客的过程中,发现之前对于二者有些混淆,二者的转化关系也不清晰,通过梳理,算是一种温故而知新。