- 面向对象编程
- 网络编程
- 多线程
- IO流
- 集合
- 框架设计
- 用户登录
- 拉取在线用户列表
- 无异常退出(客户端, 服务器端)
- 私聊
- 群发
- 发文件
- 服务器端推送消息
登陆多个用户
3.私聊发送方
接收方
服务端显示
4.群发消息发送方
接收者
服务器端显示
5.发送文件发送方
接收方文件本地地址
服务器端
6.服务器推送消息服务器端显示
7.离线聊天发送文件发送方
接收方上线
客户端代码客户端结构总览
QQView主界面package com.wu.qqclient.view;
import com.wu.qqclient.service.FileClientService;
import com.wu.qqclient.service.MessageClientService;
import com.wu.qqclient.service.UserClientService;
import com.wu.qqclient.utils.Utility;
public class QQView {
private boolean loop = true;//控制主菜单中循环
private String key = "";
private UserClientService userClientService = new UserClientService();//负责登陆,拉取用户列表,退出功能
private MessageClientService messageClientService = new MessageClientService();//消息传输相关功能实现
private FileClientService fileClientService = new FileClientService();
//客户端主函数
public static void main(String[] args) {
new QQView().mainMenu();
System.out.println("退出系统");
}
private void mainMenu() {
while (loop) {
System.out.println("=============欢迎登录网络通信系统=============");
System.out.println("ttt 1 登录系统");
System.out.println("ttt 9 退出系统");
System.out.print("请输入你的选择:");
key = Utility.readString(1);
switch (key) {
//用户登录
case "1":
System.out.print("请输入用户名: ");
String userId = Utility.readString(50);
System.out.print("请输入密 码: ");
String pwd = Utility.readString(20);
//检测用户名和密码
if (this.userClientService.checkUser(userId, pwd)) {
System.out.println("欢迎" + userId + "用户登录成功");
//二级菜单
while (loop) {
System.out.println("n=============网络通信系统二级菜单(用户:" + userId + ")============="");
System.out.println("ttt 1 显示在线用户列表");
System.out.println("ttt 2 群发消息");
System.out.println("ttt 3 私聊消息");
System.out.println("ttt 4 发送文件");
System.out.println("ttt 9 退出系统");
System.out.print("请输入你的选择: ");
key = Utility.readString(1);
switch (key) {
//拉取在线用户列表
case "1":
userClientService.onlineFriendList();
break;
//群发消息
case "2":
System.out.print("输入群聊消息:");
String info = Utility.readString(50);
messageClientService.groupChat(info, userId);
break;
//发送私聊消息
case "3":
System.out.print("输入想要私聊的用户名(在线): ");
String getterId = Utility.readString(50);
System.out.print("请输入内容");
String content = Utility.readString(100);
messageClientService.privateChat(content, userId, getterId);
break;
//发送文件
case "4":
System.out.print("输入想要发送文件的用户名:");
String fileGetterId = Utility.readString(50);
System.out.print("输入想要传输的文件地址");
String src = Utility.readString(50);
fileClientService.sendFile(src, userId, fileGetterId);
break;
//退出系统
case "9":
userClientService.exitQQClient();
break;
}
}
} else {
System.out.println("=============登 录 失 败=============");
}
break;
case "9":
loop = false;
break;
}
}
}
}
Service服务类
1.UserClientService用户请求处理类
package com.wu.qqclient.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class UserClientService {
private User user = new User();
private boolean flag = false;
private Socket socket;
public boolean checkUser(String userId, String pwd) {
user.setUserId(userId);
user.setPasswd(pwd);
try {
//以本机为服务器.端口为9999的socket对象
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
//得到输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//将user传输给服务器
oos.writeObject(user);
//socket输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//读取数据报
Message ms = (Message)ois.readObject();
//登录成功
if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCED)) {
//创建一个线程对象
ClientConnectService clientConnectService = new ClientConnectService(socket);
//开启线程
clientConnectService.start();
//将线程加入线程池中,以userId(用户名)为键值,线程对象为Value
ManageClientConnectServiceThread.addClientConnectServiceThread(userId, clientConnectService);
//成功登陆,返回值设为true
flag = true;
} else {
//登录失败,关闭socket
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
//返回值
return flag;
}
public void onlineFriendList() {
Message message = new Message();//创建一个数据报对象message
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);//设置消息雷响
message.setSender(user.getUserId());//设置发送用户信息
try {
//通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
getClientConnectServiceThread(this.user.getUserId()).getSocket().getOutputStream());
//写入数据
oos.writeObject(message);
} catch (IOException var3) {
var3.printStackTrace();
}
}
public void exitQQClient() {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);//消息类型
message.setSender(user.getUserId());//发送者信息
try {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
System.out.println("用户:"+user.getUserId()+"退出系统");
//ManageClientConnectServiceThread.removeClientServiceThread(user.getUserId());
System.exit(0);//退出
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.ClientConnectService客户端连接线程类
package com.wu.qqclient.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ClientConnectService extends Thread {
private Socket socket;
//private ObjectInputStream ois = null;
public ClientConnectService(Socket socket) {
this.socket = socket;
}
//线程
@Override
public void run() {
while (true) {
try {
//创建输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//读取对象
Message message = (Message) ois.readObject();
//拉取用户列表
if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
System.out.println("n当前在线用户列表:");
String[] onlineUsers = message.getContent().split(" ");
for (String s : onlineUsers) {
System.out.println("用户: " + s);
}
//私聊消息
} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
System.out.println("============================================");
//群聊消息
} else if (message.getMesType().equals(MessageType.MESSAGE_GROUP_CHAT_MES)) {
System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
System.out.println("============================================");
//用户不存在
} else if (message.getMesType().equals(MessageType.MESSAGE_NOT_ONLINE_USER)) {
System.out.println("n该用户未在线,用户上线时将通知");
} else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
System.out.println(message.getSender() + "发来文件");
FileClientService.readFile(message.getGetter(), message.getFileMessage());
} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES_TOALL)) {
System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
} else if (message.getMesType().equals(MessageType.MESSAGE_NOT_FOUND_USER)) {
System.out.println("n该用户不存在");
} else if (message.getMesType().equals(MessageType.MESSAGE_OFFLINE_MES)) {
if (message.getFileMessage() != null) {
System.out.println("n" + message.getSender() + "发来文件,地址为:" + FileClientService.getFilePath
(message.getGetter(), message.getFileMessage()));
FileClientService.readFile(message.getGetter(), message.getFileMessage());
} else {
System.out.println("n" + message.getSender() + "发来消息:" + message.getContent());
}
}
// else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
// System.out.println("用户" + message.getGetter() + "退出");
// ManageClientConnectServiceThread.removeClientServiceThread(message.getGetter());
// ois.close();
// socket.close();
// return;
// }
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return this.socket;
}
}
3.ManageClientConnectServiceThread管理客户端连接到服务端的线程的类
package com.wu.qqclient.service;
import java.util.HashMap;
public class ManageClientConnectServiceThread {
//创建一个HashMap的集合来存储管理线程
private static HashMap hm = new HashMap();
public static void addClientConnectServiceThread(String userId, ClientConnectService clientConnectService) {
hm.put(userId, clientConnectService);
}
public static ClientConnectService getClientConnectServiceThread(String userId) {
return hm.get(userId);
}
public static void removeClientServiceThread(String userId) {
hm.remove(userId);
}
}
4.FileClientService文件请求处理类
package com.wu.qqclient.service;
import com.wu.qqclient.utils.StreamUtils;
import com.wu.qqcommon.FileMessage;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import java.io.*;
import java.util.Date;
public class FileClientService {
private static final String ABSPATH = "E:\java\java_qqproject\qqclientfile";
public void sendFile(String src, String dest, String senderId, String getterId) {
Message message = new Message();
message.setSender(senderId);
message.setGetter(getterId);
message.setSendTime(new Date().toString());
message.setMesType(MessageType.MESSAGE_FILE_MES);
FileInputStream fileInputStream = null;
File file = new File(src);
if (file.exists()) {
try {
fileInputStream = new FileInputStream(file);
byte[] bytes = StreamUtils.streamToByteArray(fileInputStream);
int fileLen = bytes.length;
message.setFileMessage(new FileMessage(bytes, fileLen, src, dest));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
//通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
getClientConnectServiceThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("该文件不存在");
}
}
public void sendFile(String src, String senderId, String getterId) {
Message message = new Message();
message.setSender(senderId);
message.setGetter(getterId);
message.setSendTime(new Date().toString());
message.setMesType(MessageType.MESSAGE_FILE_MES);
FileInputStream fileInputStream = null;
File file = new File(src);
if (file.exists()) {
try {
fileInputStream = new FileInputStream(file);
byte[] bytes = StreamUtils.streamToByteArray(fileInputStream);
int fileLen = bytes.length;
message.setFileMessage(new FileMessage(bytes, fileLen, src));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
//通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
getClientConnectServiceThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("该文件不存在");
}
}
static void readFile(String getterId, FileMessage fileMessage) {
String dest = getFilePath(getterId, fileMessage);
fileMessage.setDest(dest);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(fileMessage.getDest());
fileOutputStream.write(fileMessage.getFileBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
static String getFilePath(String getterId, FileMessage fileMessage) {
File file = new File(ABSPATH + "\" + getterId);
if (!file.exists()) {
file.mkdir();
}
File file1 = new File(fileMessage.getSrc().trim());
String fileName = file1.getName();
return ABSPATH + "\" + getterId + "\" + fileName;
}
}
5.MessageClientService消息请求处理类
package com.wu.qqclient.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MessageClientService {
public void privateChat(String content, String senderId, String getterId) {
Message message = new Message();//可以用构造器
message.setSender(senderId);//发送用户
message.setGetter(getterId);//接收用户
message.setContent(content);//内容
message.setSendTime(getTime());//当前时间
System.out.println(senderId + "对" + getterId + "说:" + content);
message.setMesType(MessageType.MESSAGE_COMM_MES);//消息类型为私聊
try {
//通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
getClientConnectServiceThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void groupChat(String content, String senderId) {
Message message = new Message();
message.setSender(senderId);//发送用户
message.setContent(content);//接收用户
message.setSendTime(getTime());//当前时间
System.out.println(senderId + "对所有人说:" + content);
message.setMesType(MessageType.MESSAGE_GROUP_CHAT_MES);
try {
//通过用户名得到线程池中对应线程,再通过该线程得到的socket对象get输出流
ObjectOutputStream oos = new ObjectOutputStream(ManageClientConnectServiceThread.
getClientConnectServiceThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
private String getTime() {
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return dateTimeFormatter.format(ldt);
}
}
服务器端代码
服务器端结构总览
服务器启动主程序package com.wu;
import com.wu.qqserver.service.QQServer;
public class main {
public static void main(String[] args) {
new QQServer();
}
}
QQServer服务器接口端
package com.wu.qqserver.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.qqcommon.User;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
public class QQServer {
//用户信息集合
private static ConcurrentHashMap hashMap = new ConcurrentHashMap<>();
private ServerSocket serverSocket = null;
//添加用户信息
static {
hashMap.put("110", new User("110", "123456"));
hashMap.put("120", new User("120", "123456"));
hashMap.put("130", new User("130", "123456"));
hashMap.put("刹那", new User("刹那", "123456"));
hashMap.put("菲尔特", new User("菲尔特", "123456"));
hashMap.put("高达", new User("高达", "123456"));
}
public boolean checkUser(String userId, String pwd) {
User user = hashMap.get(userId);
if (user == null) {
return false;
}
return user.getPasswd().equals(pwd);
}
public static ConcurrentHashMap getHashMap() {
return hashMap;
}
public QQServer() {
//端口信息可以在配置文件中记录
try {
System.out.println("在9999端口监听");
new Thread(new SendNewsToAllService()).start();
serverSocket = new ServerSocket(9999);
while (true) {//在与某个客户端连接后,依然可以持续监听
Socket socket = serverSocket.accept();
//得到socket关联的对象输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//得到socket关联的对象输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//读取对象
User user = (User) ois.readObject();
//创建message对象,准备进行回复
Message message = new Message();
//判断用户信息是否正确
if (checkUser(user.getUserId(), user.getPasswd())) {//正确则回复对应报文,并开启一个与之联系的线程服务
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCED);
//回复报文
oos.writeObject(message);
//建立一个以userId为区分的线程服务对象
ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, user.getUserId());
//开启线程
serverConnectClientThread.start();
//将线程键入一个线程集合中
ManageServerThread.addServerThread(user.getUserId(), serverConnectClientThread);
} else {//错误则回复对应报文,并关闭socket
System.out.println("用户:" + user.getUserId() + "密码:" + user.getPasswd() + "服务器登录失败");
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
oos.writeObject(message);
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Socket线程相关
1.ServerConnectClientThread处理用户请求类
package com.wu.qqserver.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class ServerConnectClientThread extends Thread {
private Socket socket;
private String userId;
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
public Socket getSocket() {
return socket;
}
//线程
@Override
public void run() {
while (true) {
try {
System.out.println("服务器端和客户端" + userId + "保持通信,读取信息");
if(ManageOfflineMessage.getMessagehm().containsKey(userId)){
ManageOfflineMessage.getOfflineMes(userId);
ManageOfflineMessage.removeOffMes(userId);
}
//获取输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {//拉取在线用户列表
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//输出流
System.out.println(message.getSender() + "请求的在线用户列表");
Message messageUserList = new Message();//创建一个数据报对象
String onlineUser = ManageServerThread.getOnlineUser();//在线用户的userId拼接的字符串
// messageUserList.setContent(onlineUser);
// messageUserList.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
messageUserList.setGetter(message.getSender());//设置接收者
oos.writeObject(MessageService.setReturnOnlineLstMes(messageUserList, onlineUser));//设置数据报并写入数据
} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//私聊消息
//获取对应用户的socket线程
ServerConnectClientThread serverThread = ManageServerThread.getServerThread(message.getGetter());
if (serverThread != null) {//该用户在线
System.out.println(message.getSender() + "向" + message.getGetter() + "发送消息");
ObjectOutputStream oosRetrains = new ObjectOutputStream(serverThread.socket.getOutputStream());
oosRetrains.writeObject(message);
} else if (QQServer.getHashMap().containsKey(message.getGetter())) {//用户不在线
ManageOfflineMessage.addOfflineMes(message.getGetter(), message);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
message.setMesType(MessageType.MESSAGE_NOT_ONLINE_USER);
oos.writeObject(message);
} else {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
message.setMesType(MessageType.MESSAGE_NOT_FOUND_USER);
oos.writeObject(message);
}
} else if (message.getMesType().equals(MessageType.MESSAGE_GROUP_CHAT_MES)) {//群聊消息
System.out.println(message.getSender() + "向全体在线用户发消息");
String onlineUser = ManageServerThread.getOnlineUser();//在线用户的userId拼接的字符串
String[] users = onlineUser.split(" ");//得到在线用户userId的字符串数组
//message.setMesType(MessageType.MESSAGE_COMM_MES);
for (String s : users) {
if (!s.equals(message.getSender())) {//向除了自己以外的所有在线用户发送消息
ObjectOutputStream oos = new ObjectOutputStream(ManageServerThread.getServerThread(s).socket.getOutputStream());
oos.writeObject(message);
}
}
} else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//发送文件
ServerConnectClientThread serverThread = ManageServerThread.getServerThread(message.getGetter());
if (serverThread != null) {//该用户在线
System.out.println(message.getSender() + "向" + message.getGetter() + "发送文件");
ObjectOutputStream oosRetrains = new ObjectOutputStream(serverThread.socket.getOutputStream());
oosRetrains.writeObject(message);
} else if (QQServer.getHashMap().containsKey(message.getGetter())) {//用户不在线
ManageOfflineMessage.addOfflineMes(message.getGetter(), message);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
message.setMesType(MessageType.MESSAGE_NOT_ONLINE_USER);
oos.writeObject(message);
} else {
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
message.setMesType(MessageType.MESSAGE_NOT_FOUND_USER);
oos.writeObject(message);
}
} else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//退出系统
System.out.println(message.getSender() + "用户退出系统");
Message messageExit = new Message();
// messageExit.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
messageExit.setGetter(message.getSender());
//oos.writeObject(MessageService.setClientExitMes(messageExit));
ManageServerThread.removeServerThread(message.getSender());
ois.close();
socket.close();
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
}
2.ManageServerThread管理socket线程类
package com.wu.qqserver.service;
import com.wu.qqcommon.User;
import java.net.Socket;
import java.util.HashMap;
import java.util.Set;
public class ManageServerThread {
//userId为键值,socket线程对象为Value的HashMap集合
private static HashMap hm = new HashMap<>();
public static void addServerThread(String userId, ServerConnectClientThread serverConnectClientThread) {
hm.put(userId, serverConnectClientThread);
}
public static ServerConnectClientThread getServerThread(String userId) {
return hm.get(userId);
}
public static void removeServerThread(String userId) {
hm.remove(userId);
}
public static String getOnlineUser() {
StringBuilder userList = new StringBuilder();
Set set = hm.keySet();//keySet获得键值用户名
for (String s : set) {
userList.append(s).append(" ");
}
return userList.toString();
}
}
服务器端服务处理类
1.MessageService数据报加工类
package com.wu.qqserver.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
public class MessageService {
//在线用户列表数据报(服务器端)
public static Message setReturnOnlineLstMes(Message message, String onlineUser) {
message.setContent(onlineUser);
message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message.setSender("服务器端口9999");
return message;
}
//退出系统数据报(服务器端)
public static Message setClientExitMes(Message message) {
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender("服务器端口9999");
return message;
}
}
2.SendNewsToAllService服务器推送消息类
package com.wu.qqserver.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import com.wu.utils.Utility;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
public class SendNewsToAllService implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("请输入想要推送的消息(输入exit退出推送系统):");
String news = Utility.readString(100);
if (news.equals("exit")) {
break;
}
Message message = new Message();
message.setSender("服务器");
message.setContent(news);
message.setSendTime(new Date().toString());
message.setMesType(MessageType.MESSAGE_COMM_MES_TOALL);
String onlineUser = ManageServerThread.getOnlineUser();
String[] users = onlineUser.split(" ");
for (String s : users) {
try {
ObjectOutputStream oos = new ObjectOutputStream(
ManageServerThread.getServerThread(s).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.ManageOfflineMessage处理离线消息类
package com.wu.qqserver.service;
import com.wu.qqcommon.Message;
import com.wu.qqcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
public class ManageOfflineMessage {
private static ConcurrentHashMap> messagehm = new ConcurrentHashMap<>();
public static void addOfflineMes(String getterId, Message message) {
ArrayList tempMessages = messagehm.get(getterId);
if (tempMessages != null) {
tempMessages.add(message);
} else {
ArrayList messages = new ArrayList<>();
messages.add(message);
messagehm.put(getterId, messages);
}
}
public static void getOfflineMes(String getterId) {
ArrayList messages = messagehm.get(getterId);
for (Message mes : messages) {
try {
mes.setMesType(MessageType.MESSAGE_OFFLINE_MES);
ObjectOutputStream oos = new ObjectOutputStream(
ManageServerThread.getServerThread(getterId).getSocket().getOutputStream());
oos.writeObject(mes);
Thread.sleep(50);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
public static void removeOffMes(String getterId) {
ArrayList messages = messagehm.get(getterId);
messagehm.remove(getterId, messages);
}
public static ConcurrentHashMap> getMessagehm() {
return messagehm;
}
}
公共代码
客户端和服务器端公有文件,此处仅贴一份.
实体类 1.User用户信息类package com.wu.qqcommon;
import java.io.Serializable;
import java.util.Objects;
public class User implements Serializable {
private String userId;//用户名
private String passwd;//密码
private static final long serialVersionUID = 1L;
public User(String userId, String passwd) {
this.userId = userId;
this.passwd = passwd;
}
public User() {
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
2.Message数据报类
package com.wu.qqcommon;
import java.io.Serializable;
public class Message implements Serializable {
private String sender;//发送者
private String getter;//接收者
private String content;//内容
private String sendTime;//发送时间
private String mesType;//信息类型
private FileMessage fileMessage;//文件信息
private static final long serialVersionUID = 1L;
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getGetter() {
return getter;
}
public void setGetter(String getter) {
this.getter = getter;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
public FileMessage getFileMessage() {
return fileMessage;
}
public void setFileMessage(FileMessage fileMessage) {
this.fileMessage = fileMessage;
}
}
3.FileMessage文件信息类
package com.wu.qqcommon;
import java.io.Serializable;
public class FileMessage implements Serializable {
private byte[] fileBytes;
private int fileLen = 0;
private String src;
private String dest;
public FileMessage(byte[] fileBytes, int fileLen, String src, String dest) {
this.fileBytes = fileBytes;
this.fileLen = fileLen;
this.src = src;
this.dest = dest;
}
public FileMessage(byte[] fileBytes, int fileLen, String src) {
this.fileBytes = fileBytes;
this.fileLen = fileLen;
this.src = src;
}
public FileMessage(String src, String dest) {
this.src = src;
this.dest = dest;
}
public FileMessage() {
}
public byte[] getFileBytes() {
return fileBytes;
}
public void setFileBytes(byte[] fileBytes) {
this.fileBytes = fileBytes;
}
public int getFileLen() {
return fileLen;
}
public void setFileLen(int fileLen) {
this.fileLen = fileLen;
}
public String getDest() {
return dest;
}
public void setDest(String dest) {
this.dest = dest;
}
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
}
4.消息类型接口
package com.wu.qqcommon;
public interface MessageType {
//不同的值代表不同的消息类型
String MESSAGE_LOGIN_SUCCED = "1";//登录成功
String MESSAGE_LOGIN_FAIL = "2";//登录失败
String MESSAGE_COMM_MES = "3";//常规消息(私聊)
String MESSAGE_GET_ONLINE_FRIEND = "4";//拉取在线用户列表(客户端)
String MESSAGE_RET_ONLINE_FRIEND = "5";//拉取在线用户列表(服务器端)
String MESSAGE_CLIENT_EXIT = "6";//退出系统
String MESSAGE_NOT_FOUND_USER = "7";//未找到用户
String MESSAGE_NOT_ONLINE_USER = "8";//未在线用户
String MESSAGE_GROUP_CHAT_MES = "9";//发送群聊消息
String MESSAGE_FILE_MES = "10";//发送文件
String MESSAGE_COMM_MES_TOALL = "11";
String MESSAGE_OFFLINE_MES = "12";//离线消息
}
工具类
1.Utility控制台输入类
package com.wu.qqclient.utils;
import java.util.*;
public class Utility {
//静态属性。。。
private static Scanner scanner = new Scanner(System.in);
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);//包含一个字符的字符串
c = str.charAt(0);//将字符串转换成字符char类型
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
public static char readChar() {
String str = readKeyBoard(1, false);//就是一个字符
return str.charAt(0);
}
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(2, false);//一个整数,长度<=2位
try {
n = Integer.parseInt(str);//将字符串转换成整数
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(10, true);
if (str.equals("")) {
return defaultValue;
}
//异常处理...
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("")? defaultValue : str;
}
public static char readConfirmSelection() {
System.out.println("请输入你的选择(Y/N)");
char c;
for (; ; ) {//无限循环
//在这里,将接受到字符,转成了大写字母
//y => Y n=>N
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
private static String readKeyBoard(int limit, boolean blankReturn) {
//定义了字符串
String line = "";
//scanner.hasNextLine() 判断有没有下一行
while (scanner.hasNextLine()) {
line = scanner.nextLine();//读取这一行
//如果line.length=0, 即用户没有输入任何内容,直接回车
if (line.length() == 0) {
if (blankReturn) return line;//如果blankReturn=true,可以返回空串
else continue; //如果blankReturn=false,不接受空串,必须输入内容
}
//如果用户输入的内容大于了 limit,就提示重写输入
//如果用户如的内容 >0 <= limit ,我就接受
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
2.StreamUtils流的读写类
package com.wu.qqclient.utils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class StreamUtils {
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];//字节数组
int len;
while((len=is.read(b))!=-1){//循环读取
bos.write(b, 0, len);//把读取到的数据,写入bos
}
byte[] array = bos.toByteArray();//然后将bos 转成字节数组
bos.close();
return array;
}
public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine())!=null){
builder.append(line+"rn");
}
return builder.toString();
}
}
总结评价
开发过程中出现的异常
1.Socket java.net.SocketException: Connection reset
查阅博客:发送数据不符合服务器接收格式,服务器会自动断开连接,socket就会被关闭,就不能使用IO流读取数据,这样就会报这个错误
异常原因:添加FileMessage类后,在服务器端项目代码中,忘记让该类实现Serializable,导致服务器端在接收消息时socket关闭,出现异常.
总结:对于自定义类,想要让其可以在网络上传输,需使其可以串行化.
2.java.io.StreamCorruptedException: invalid stream header: 7371007E
查阅博客:java 序列化追加文件及反序列化读取多个对象
异常原因: 序列化时 new ObjectOutputStream() 会调用 writeStreamHeader() 写入四个字节的表头, 反序列化时 new ObjectInputStream() 只会调用一次 readStreamHeader() 来读表头,导致将表头认为数据进行读取,发生该异常.
我在服务器端的 ServerConnectClientThread 类的线程中定义一个 ObjectOutputStream() ,其写入多次数据,结构为一个表头多份数据.在客户端的 ClientConnectService 的接收线程中,每次接收数据都 new 了一个新的 ObjectInputStream ,其结果就是将数据误认为表头进行处理,发生该异常.
总结:
- 保证序列化和反序列化时,写入和读取的表头数量一致.
- 在写入多个数据时,只保留第一个表头,删除添加其他数据时加入的表头,保证在读取时只检测一次表头不会出现异常.
- 该通信系统简易地实现了社交软件中的常见功能,运行稳定.但是无界面,功能简单,未实现数据库连接,使用相关功能不便捷,仍有许多可以改进的地方.
- 在代码方面,整体结构较为简单明朗,但是存在许多代码冗余的状况,可以进行精简,部分代码需要进行封装.
- 开发过程中,对于IO流,Socket的相关API调用不够熟练,项目的整体架构不清晰带来的无从下手也值得注意.
- 经过该项目加深了对Socket.IO流.面向对象.String字符串等相关知识的理解
- 使用git版本控制工具,将项目推送到GItee和Github上并进行更新,熟悉了git的操作流程
- java.io.StreamCorruptedException: invalid stream header
- java 序列化追加文件及反序列化读取多个对象
- 3种Java从文件路径中获取文件名的方法
- 韩顺平java学习笔记
- 【零基础 快速学Java】韩顺平 零基础30天学会Java
服务器端
客户端



