重构 JAVA 聊天室 —— CS 模式的简单架构实现
前言
自从开始弄起数据挖掘之后,已经很久没写过技术类的博客了,最近学校 JAVA 课设要求实现一个聊天室,想想去年自己已经写了一个了,但是有些要求到的功能我也没实现,但看着原有的代码想了想加功能好像有那么点点难,于是就想着重构,也正好之前有看到别人写的CS架构的代码,感觉扩展性还不错,就试着写了写,写完这个聊天室后,还同时写了一个教学白板,那个白板基于这个聊天室的代码仅仅花了三四个小时就完成了!所以,有一个好的架构还是很重要的。下面就开始介绍我重构后的聊天室(代码已上传到github)
功能介绍
1. 用Java图形用户界面编写聊天室服务器端和客户端, 支持多个客户端连接到一个服务器。每个客户端能够输入账号,包括注册功能。
2. 可以实现群聊(聊天记录显示在所有客户端界面)。
3. 完成好友列表在各个客户端上显示,包括头像和用户名。
4. 可以实现私人聊天,用户可以选择某个其他用户,单独发送信息,同时实现了文件传输,还能发送窗口振动。
5. 服务器能够群发系统消息,能够对用户私发消息,能够强行让某些用户下线。
6. 客户端的上线下线要求能够在其他客户端上面实时刷新。
7.服务器能够查看在线用户和注册用户
(加了下划线的是课设要求之外的)
整体思路
数了数,总共写了27个类,看起来还是蛮多的,但是仔细看一看还是很简单的,我将在下面对其中部分进行解释
工具类
在我之前写的几个socket通信有关的项目里,客户端和服务器传输的都是字符串,而这次,我把要传输的内容封装成了两个类 Response 和 Request,客户端向服务器发起请求,服务器向客户端回应,通过两个类中包含的请求类型来判断需要进行的操作,传输采用ObjectStream。仔细以看其实会发现,这两个类内容很相似
Request
public class Request implements Serializable {
private static final long serialVersionUID = -1237018286305074249L;
/** 请求传送的数据类型 */
private ResponseType type;
/** 请求动作 */
private String action;
/** 请求域中的数据,name-value */
private Map<String, Object> attributesMap; public Request(){
this.attributesMap = new HashMap<String, Object>();
} public ResponseType getType() {
return type;
} public void setType(ResponseType type) {
this.type = type;
} public String getAction() {
return action;
} public void setAction(String action) {
this.action = action;
} public Map<String, Object> getAttributesMap() {
return attributesMap;
} public Object getAttribute(String name){
return this.attributesMap.get(name);
} public void setAttribute(String name, Object value){
this.attributesMap.put(name, value);
} public void removeAttribute(String name){
this.attributesMap.remove(name);
} public void clearAttribute(){
this.attributesMap.clear();
}
}
Request
Response
public class Response implements Serializable {
private static final long serialVersionUID = 1689541820872288991L;
/** 响应状态 */
private ResponseStatus status;
/** 响应数据的类型 */
private ResponseType type; private Map<String, Object> dataMap; /** 响应输出流 */
private OutputStream outputStream; public Response(){
this.status = ResponseStatus.OK;
this.dataMap = new HashMap<String, Object>();
} public ResponseStatus getStatus() {
return status;
} public void setStatus(ResponseStatus status) {
this.status = status;
} public ResponseType getType() {
return type;
} public void setType(ResponseType type) {
this.type = type;
} public Map<String, Object> getDataMap() {
return dataMap;
} public void setDataMap(Map<String, Object> dataMap) {
this.dataMap = dataMap;
} public OutputStream getOutputStream() {
return outputStream;
} public void setOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
} public void setData(String name, Object value){
this.dataMap.put(name, value);
} public Object getData(String name){
return this.dataMap.get(name);
} public void removeData(String name){
this.dataMap.remove(name);
} public void clearData(){
this.dataMap.clear();
}
}
Response
在以上两个类中,传输的内容会包括文件和消息,对于文件和消息,我们需要直到发送者和接受者是谁,需要知道发送时间等等,所以同样封装成了两个类
FileInfo
public class FileInfo implements Serializable {
private static final long serialVersionUID = -5394575332459969403L;
/** 消息接收者 */
private User toUser;
/** 消息发送者 */
private User fromUser;
/** 源文件名 */
private String srcName;
/** 发送时间 */
private Date sendTime;
/** 目标地IP */
private String destIp;
/** 目标地端口 */
private int destPort;
/** 目标文件名 */
private String destName;
public User getToUser() {
return toUser;
}
public void setToUser(User toUser) {
this.toUser = toUser;
}
public User getFromUser() {
return fromUser;
}
public void setFromUser(User fromUser) {
this.fromUser = fromUser;
}
public String getSrcName() {
return srcName;
}
public void setSrcName(String srcName) {
this.srcName = srcName;
}
public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
}
public String getDestIp() {
return destIp;
}
public void setDestIp(String destIp) {
this.destIp = destIp;
}
public int getDestPort() {
return destPort;
}
public void setDestPort(int destPort) {
this.destPort = destPort;
}
public String getDestName() {
return destName;
}
public void setDestName(String destName) {
this.destName = destName;
}
}
FileInfo
Message
public class Message implements Serializable {
private static final long serialVersionUID = 1820192075144114657L;
/** 消息接收者 */
private User toUser;
/** 消息发送者 */
private User fromUser;
/** 消息内容 */
private String message;
/** 发送时间 */
private Date sendTime; public User getToUser() {
return toUser;
}
public void setToUser(User toUser) {
this.toUser = toUser;
}
public User getFromUser() {
return fromUser;
}
public void setFromUser(User fromUser) {
this.fromUser = fromUser;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
} public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
}
}
Message
User
User 类则用于存储用户信息,因为会用于传输,需实现序列化传输
public class User implements Serializable {
private static final long serialVersionUID = 5942011574971970871L;
private long id;
private String password;
private String nickname;
private int head;
private char sex; public User(String password, String nickname, char sex, int head){
this.password = password;
this.sex = sex;
this.head = head;
if(nickname.equals("")||nickname==null)
{
this.nickname = "未命名";
}else{
this.nickname = nickname;
}
} public User(long id, String password){
this.id = id;
this.password = password;
} public long getId(){
return id;
} public void setId(long id){
this.id = id;
} public void setPassword(String password){
this.password = password;
} public String getPassword(){
return password;
} public void setSex(char sex){
this.sex=sex;
} public char getSex(){
return this.sex;
} public void setNickname(String nickname){
this.nickname = nickname;
} public String getNickname(){
return this.nickname;
} public void setHead(int head){
this.head = head;
} public int getHead(){
return this.head;
} public ImageIcon getHeadIcon(){
ImageIcon image = new ImageIcon("images/"+head+".png");
return image;
} @Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + head;
result = prime * result + (int)(id ^ (id >> 32));
result = prime * result + ((nickname == null) ? 0 : nickname.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + sex;
return result;
} @Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
User other = (User) obj;
if(head != other.head || id != other.id || sex != other.sex)
return false;
if(nickname == null){
if(other.nickname != null)
return false;
}else if(!nickname.equals(other.nickname))
return false;
if(password == null){
if(other.password != null)
return false;
}else if(!password.equals(other.password))
return false;
return true;
} @Override
public String toString() {
return this.getClass().getName()
+ "[id=" + this.id
+ ",pwd=" + this.password
+ ",nickname=" + this.nickname
+ ",head=" + this.head
+ ",sex=" + this.sex
+ "]";
}
}
User
剩余的类就不一一介绍了,如果有需要可以到我的github上找到源代码。
Server端
服务器端的代码用到的类如上所示,其中 entity 中的两个类和 ServerInfoFrame 仅用于界面,所以不会进行介绍。
UserService
用于用户账号管理,预先创建几个账号,然后存到文件中,每次服务器执行时,都会将文件中的账号信息读入,同时新创建的用户账号也会存入到文件中去。
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);
}
}
}
UserService
DataBuffer
用于服务器端从文件中读取数据,进行缓存
public class DataBuffer {
// 服务器端套接字
public static ServerSocket serverSocket;
//在线用户的IO Map
public static Map<Long, OnlineClientIOCache> onlineUserIOCacheMap;
//在线用户Map
public static Map<Long, User> onlineUsersMap;
//服务器配置参数属性集
public static Properties configProp;
// 已注册用户表的Model
public static RegistedUserTableModel registedUserTableModel;
// 当前在线用户表的Model
public 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();
}
} }
DataBuffer
RequestProcessor
这时服务器端最重要的一个类了,用于处理客户端发来的消息,并进行回复,对于每一项操作的实现原理无非就是服务器处理内部数据或是向指定客户端发送消息,详细看代码注释
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<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();
}
}
RequestProcessor
Client端
个人感觉做这类项目时,难点是在客户端,之前考虑了很久关于界面的切换,因为涉及到了登陆界面、注册界面、聊天界面,所以如何将客户端的socket与这几个界面联系起来是个值得思考的问题。同时,也思考了好久好友列表的展示方法,最后想到了TIM。下面介绍一下其中的几个类
ClientThread
客户端线程,一个线程表示一个用户,处理服务器发来的消息,在里面用了 currentFrame 这个变量来表示当前窗口。
public class ClientThread extends Thread {
private JFrame currentFrame; //当前窗体 public ClientThread(JFrame frame){
currentFrame = frame;
} public void run() {
try {
while (DataBuffer.clientSeocket.isConnected()) {
Response response = (Response) DataBuffer.ois.readObject();
ResponseType type = response.getType(); System.out.println("获取了响应内容:" + type);
if (type == ResponseType.LOGIN) {
User newUser = (User)response.getData("loginUser");
DataBuffer.onlineUserListModel.addElement(newUser); ChatFrame.onlineCountLbl.setText(
"在线用户列表("+ DataBuffer.onlineUserListModel.getSize() +")");
ClientUtil.appendTxt2MsgListArea("【系统消息】用户"+newUser.getNickname() + "上线了!\n");
}else if(type == ResponseType.LOGOUT){
User newUser = (User)response.getData("logoutUser");
DataBuffer.onlineUserListModel.removeElement(newUser); ChatFrame.onlineCountLbl.setText(
"在线用户列表("+ DataBuffer.onlineUserListModel.getSize() +")");
ClientUtil.appendTxt2MsgListArea("【系统消息】用户"+newUser.getNickname() + "下线了!\n"); }else if(type == ResponseType.CHAT){ //聊天
Message msg = (Message)response.getData("txtMsg");
ClientUtil.appendTxt2MsgListArea(msg.getMessage());
}else if(type == ResponseType.SHAKE){ //振动
Message msg = (Message)response.getData("ShakeMsg");
ClientUtil.appendTxt2MsgListArea(msg.getMessage());
new JFrameShaker(this.currentFrame).startShake();
}else if(type == ResponseType.TOSENDFILE){ //准备发送文件
toSendFile(response);
}else if(type == ResponseType.AGREERECEIVEFILE){ //对方同意接收文件
sendFile(response);
}else if(type == ResponseType.REFUSERECEIVEFILE){ //对方拒绝接收文件
ClientUtil.appendTxt2MsgListArea("【文件消息】对方拒绝接收,文件发送失败!\n");
}else if(type == ResponseType.RECEIVEFILE){ //开始接收文件
receiveFile(response);
}else if(type == ResponseType.BOARD){
Message msg = (Message)response.getData("txtMsg");
ClientUtil.appendTxt2MsgListArea(msg.getMessage());
}else if(type == ResponseType.REMOVE){
ChatFrame.remove();
}
}
} catch (IOException e) {
//e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} /** 发送文件 */
private void sendFile(Response response) {
final FileInfo sendFile = (FileInfo)response.getData("sendFile"); BufferedInputStream bis = null;
BufferedOutputStream bos = null;
Socket socket = null;
try {
socket = new Socket(sendFile.getDestIp(),sendFile.getDestPort());//套接字连接
bis = new BufferedInputStream(new FileInputStream(sendFile.getSrcName()));//文件读入
bos = new BufferedOutputStream(socket.getOutputStream());//文件写出 byte[] buffer = new byte[1024];
int n = -1;
while ((n = bis.read(buffer)) != -1){
bos.write(buffer, 0, n);
}
bos.flush();
synchronized (this) {
ClientUtil.appendTxt2MsgListArea("【文件消息】文件发送完毕!\n");
}
} catch (IOException e) {
e.printStackTrace();
}finally{
IOUtil.close(bis,bos);
SocketUtil.close(socket);
}
} /** 接收文件 */
private void receiveFile(Response response) {
final FileInfo sendFile = (FileInfo)response.getData("sendFile"); BufferedInputStream bis = null;
BufferedOutputStream bos = null;
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(sendFile.getDestPort());
socket = serverSocket.accept(); //接收
bis = new BufferedInputStream(socket.getInputStream());//缓冲读
bos = new BufferedOutputStream(new FileOutputStream(sendFile.getDestName()));//缓冲写出 byte[] buffer = new byte[1024];
int n = -1;
while ((n = bis.read(buffer)) != -1){
bos.write(buffer, 0, n);
}
bos.flush();
synchronized (this) {
ClientUtil.appendTxt2MsgListArea("【文件消息】文件接收完毕!存放在["
+ sendFile.getDestName()+"]\n");
} } catch (IOException e) {
e.printStackTrace();
}finally{
IOUtil.close(bis,bos);
SocketUtil.close(socket);
SocketUtil.close(serverSocket);
}
} /** 准备发送文件 */
private void toSendFile(Response response) {
FileInfo sendFile = (FileInfo)response.getData("sendFile"); String fromName = sendFile.getFromUser().getNickname()
+ "(" + sendFile.getFromUser().getId() + ")";
String fileName = sendFile.getSrcName()
.substring(sendFile.getSrcName().lastIndexOf(File.separator)+1); int select = JOptionPane.showConfirmDialog(this.currentFrame,
fromName + " 向您发送文件 [" + fileName+ "]!\n同意接收吗?",
"接收文件", JOptionPane.YES_NO_OPTION);
try {
Request request = new Request();
request.setAttribute("sendFile", sendFile); if (select == JOptionPane.YES_OPTION) {
JFileChooser jfc = new JFileChooser();
jfc.setSelectedFile(new File(fileName));
int result = jfc.showSaveDialog(this.currentFrame); if (result == JFileChooser.APPROVE_OPTION){
//设置目的地文件名
sendFile.setDestName(jfc.getSelectedFile().getCanonicalPath());
//设置目标地的IP和接收文件的端口
sendFile.setDestIp(DataBuffer.ip);
sendFile.setDestPort(DataBuffer.RECEIVE_FILE_PORT); request.setAction("agreeReceiveFile");
// receiveFile(response);
ClientUtil.appendTxt2MsgListArea("【文件消息】您已同意接收来自 "
+ fromName +" 的文件,正在接收文件 ...\n");
} else {
request.setAction("refuseReceiveFile");
ClientUtil.appendTxt2MsgListArea("【文件消息】您已拒绝接收来自 "
+ fromName +" 的文件!\n");
}
} else {
request.setAction("refuseReceiveFile");
ClientUtil.appendTxt2MsgListArea("【文件消息】您已拒绝接收来自 "
+ fromName +" 的文件!\n");
} ClientUtil.sendTextRequest2(request);
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientThread
ClientUtil
用于客户端向服务器发送消息
public class ClientUtil { /** 发送请求对象,主动接收响应 */
public static Response sendTextRequest(Request request) throws IOException {
Response response = null;
try {
// 发送请求
DataBuffer.oos.writeObject(request);
DataBuffer.oos.flush();
System.out.println("客户端发送了请求对象:" + request.getAction()); if(!"exit".equals(request.getAction())){
// 获取响应
response = (Response) DataBuffer.ois.readObject();
System.out.println("客户端获取到了响应对象:" + response.getStatus());
}else{
System.out.println("客户端断开连接了");
}
} catch (IOException e) {
throw e;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return response;
} /** 发送请求对象,不主动接收响应 */
public static void sendTextRequest2(Request request) throws IOException {
try {
DataBuffer.oos.writeObject(request); // 发送请求
DataBuffer.oos.flush();
System.out.println("客户端发送了请求对象:" + request.getAction());
} catch (IOException e) {
throw e;
}
} /** 把指定文本添加到消息列表文本域中 */
public static void appendTxt2MsgListArea(String txt) {
ChatFrame.msgListArea.append(txt);
//把光标定位到文本域的最后一行
ChatFrame.msgListArea.setCaretPosition(ChatFrame.msgListArea.getDocument().getLength());
}
}
ClientUtil
总结
大体上的细节我就介绍这些,剩下的大部分都是界面相关的代码,我把整个项目放到github上了,感觉现在用的这个框架可以适应学校内布置的涉及到CS架构的一切任务,学会了,别人要好几天搞定的自己几个小时就行了,而且看起来还会比别人的舒服的多。下一篇将会介绍利用这个框架实现另一个项目——教学白板。
重构 JAVA 聊天室 —— CS 模式的简单架构实现的更多相关文章
- 基于Spring 4.0 的 Web Socket 聊天室/游戏服务端简单架构
在现在很多业务场景(比如聊天室),又或者是手机端的一些online游戏,都需要做到实时通信,那怎么来进行双向通信呢,总不见得用曾经很破旧的ajax每隔10秒或者每隔20秒来请求吧,我的天呐(),这尼玛 ...
- [Java聊天室server]实战之二 监听类
前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更 ...
- [Java聊天室server]实战之五 读写循环(服务端)
前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识,但学习之前,更 ...
- [Java聊天室server]实战之三 接收循环
前言 学习不论什么一个稍有难度的技术,要对其有充分理性的分析,之后果断做出决定---->也就是人们常说的"多谋善断":本系列尽管涉及的是socket相关的知识.但学习之前,更 ...
- Java聊天室[长轮询]
今天看到有人分享java实现的聊天室,想起很久以前还在热衷于java的时候也做过一个web聊天室,不拿出来晒晒,可能再也不为人知了,单纯是一个兴趣作品,稳定性不好,也没有考虑连接数和并发的问题,拿出来 ...
- 【Java】Socket+多线程实现控制台聊天室
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827212.html 聊天室程序的结构图: 架构解释: Server服务器相当于一个中转站,Client客户端 ...
- Netty学习笔记(四) 简单的聊天室功能之服务端开发
前面三个章节,我们使用了Netty实现了DISCARD丢弃服务和回复以及自定义编码解码,这篇博客,我们要用Netty实现简单的聊天室功能. Ps: 突然想起来大学里面有个课程实训,给予UDP还是TCP ...
- [Java小程序]聊天室——Socket和ServerSocket的使用
这段小代码是因为担任Java助教给刚学习Java的本科大二的小学弟小学妹们指导,他们的实验作业就是编写一个Java聊天室客户端和服务器,为了避免出纰漏,自己事先写了一下. 客户端Ui代码: packa ...
- Java学习笔记——Java工厂模式之简单工厂
package com.app; import java.util.Date; /* * 工厂模式:简单工厂.工厂方法.抽象工厂 * * */ public class Test0718_Factor ...
随机推荐
- 20191010-6 alpha week 1/2 Scrum立会报告+燃尽图 04
此作业的要求参见[https://edu.cnblogs.com/campus/nenu/2019fall/homework/8749] 一.小组情况 队名:扛把子 组长:迟俊文 组员:宋晓丽 梁梦瑶 ...
- windows系统两个网卡如何同时使用?内网和外网共用,配置教程
有些企业的网络分为内网和外网,一般内网不能访问Internet,但是又不提供外网,自己有上外网的需求,所以可能会用到双网卡,无线网卡+有线网卡或者有线+有线... 本文就是为了解决此问题,主要的做法就 ...
- Mybatis日志体系
承接上一篇关于spring 5.x的日志体系,本篇看看Mybatis的日志体系及实现,Mybatis版本基于3.x. 关于mybatis的官方文档比较友好,分门别类,各有论述,如mybatis官方文档 ...
- 基于Pytorch的简单小案例
神经网络的理论知识不是本文讨论的重点,假设读者们都是已经了解RNN的基本概念,并希望能用一些框架做一些简单的实现.这里推荐神经网络必读书目:邱锡鹏<神经网络与深度学习>.本文基于Pytor ...
- scrapy结合selenium抓取武汉市环保局空气质量日报
1.前言 目标网站:武汉市环境保护局(http://hbj.wuhan.gov.cn/viewAirDarlyForestWaterInfo.jspx).scrapy对接selenium模块抓取空气质 ...
- SpringSecurity环境下配置CORS跨站资源共享规则
一.CORS简述 要说明CORS(Cross Origin Resourse-Sharing) 跨站资源共享,就必须先说同源策略.长话短说,同源策略就是向服务端发起请求的时候,以下三项必须与当前浏览器 ...
- Servlet+Ajax实现搜索框智能提示
简介:搜索框相信大家都不陌生,几乎每天都会在各类网站进行着搜索.有没有注意到,很多的搜索功能,当输入内容时,下面会出现提示.这类提示就叫做搜索框的智能提示,本门课程就为大家介绍如何使用Servlet和 ...
- VMware中linux虚拟机的安装
打开安装的VMware 15,点击新建虚拟机 2.选择典型即可,点击下一步 3.选择“稍后安装操作系统”,点击下一步 4.选择想安的版本,点击下一步 5.设置虚拟机名称及安装位置(路径必须全英文!) ...
- 在flink中使用jackson JSONKeyValueDeserializationSchema反序列化Kafka消息报错解决
在做支付订单宽表的场景,需要关联的表比较多而且支付有可能要延迟很久,这种情况下不太适合使用Flink的表Join,想到的另外一种解决方案是消费多个Topic的数据,再根据订单号进行keyBy,再在逻辑 ...
- 使用 Python 验证数据集中的体温是否符合正态分布
数据集地址:http://jse.amstat.org/datasets/normtemp.dat.txt 数据集描述:总共只有三列:体温.性别.心率 #代码 from scipy import st ...