目录
项目介绍
项目截图
服务器与客户端
新用户注册
注册新账号成功
进入聊天室
多人在线
选择发送文件
文件接收提醒
项目代码参考
服务器入口程序
服务器请求处理
原理解析
服务器多人网络连接:
如何实现窗口抖动
获取源码
使用 java swing 开发多人聊天室,分为服务端和客户端,属于BS架构。
功能包括:注册、登录、单聊、群聊、窗口抖动、发送文件、选头像。
服务器:可以看到全部已注册用户的列表,用户登录之后也可以在服务器看到在线信息。
客户端:可以注册新用户,注册时可以填写昵称、密码、性别、头像。
登录成功之后会进入聊天室,在聊天室可以看到其他在线用户,也可以选择某个具体用户进行单聊。
也可以给其他用户发送窗口抖动。
也可以给其他用户发送文件。
可以选择头像
这是服务器启动的主程序,在这里监听了网络端口,循环接收客户端的连接请求。
package server;import java.io.IOException;
import java.net.*;import javax.swing.*;import server.controller.RequestProcessor;
import server.ui.ServerInfoFrame;/** 服务器入口程序 */
public class ServerMain {public static void main(String[] args) {try {//设置外观样式UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (Exception e) {e.printStackTrace();}int port = Integer.parseInt(DataBuffer.configProp.getProperty("port"));//初始化服务器套节字try {DataBuffer.serverSocket = new ServerSocket(port);} catch (IOException e) {e.printStackTrace();}new Thread(new Runnable() {//启动新线程进行客户端连接监听public 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();//启动服务器监控窗体new ServerInfoFrame(); }
}
服务器对收到的消息进行处理,例如注册、登录、窗口震动、发送文件、接收文件、聊天、拒收文件,以及其他类型的消息都在这进行处理。
package server.controller;import java.io.*;
import java.net.Socket;
import java.text.*;
import java.util.concurrent.CopyOnWriteArrayList;
import server.*;
import server.model.service.UserService;
import common.model.entity.*;/** 服务器端请求处理器 */
public class RequestProcessor implements Runnable{private Socket currentClientSocket; //当前正在请求服务器的客户端Socketpublic RequestProcessor(Socket currentClientSocket){this.currentClientSocket = currentClientSocket;}public 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(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 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();}
}
首先来分析一下要实现的流程
值得一提是:该socket是同步阻塞的,因此在socket客户端需要进行创建一个线程,来分别进行向服务器输出,和接收服务器传输的数据。
窗口的左上角是原点,我们首先记录起始坐标,计算出向左上角移动的距离和右下角移动的距离,通过循环语句控制窗口位移,最终将窗口恢复到起始坐标。
上一篇:一个大型网站架构的演变历程
下一篇:01背包入门讲解