目录
项目介绍
项目截图
服务器与客户端
新用户注册
注册新账号成功
进入聊天室
多人在线
选择发送文件
文件接收提醒
项目代码参考
服务器入口程序
服务器请求处理
原理解析
服务器多人网络连接:
如何实现窗口抖动
获取源码
使用 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; //当前正在请求服务器的客户端Socket
public 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(); //关闭这个客户端Socket
DataBuffer.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客户端需要进行创建一个线程,来分别进行向服务器输出,和接收服务器传输的数据。
窗口的左上角是原点,我们首先记录起始坐标,计算出向左上角移动的距离和右下角移动的距离,通过循环语句控制窗口位移,最终将窗口恢复到起始坐标。