概述
多用户通信系统
1、用户登录
我们人为规定 用户名 id = 100, 密码123456就可以登录,其他用户不能登录。
后面使用HashMap模拟数据库,可以多个用户登录。
共有类
//表示一个用户信息
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;//用户ID//用户名
private String password;//用户密码
//省略构造器和setXxx和getXxx
}
//表示客户端和服务端通信时的消息对象
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//消息内容
private String sendTime;//发送时间
private String mesType;//消息类型[可以在接口中定义消息类型]
//和文件相关的成员
private byte[] fileBytes;
private int fileLen = 0;
private String dest;//目的路径
private String src;//源文件路径
//省略setXxx和getXxx
}
//表示消息类型
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED = "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_TO_ALL_MES = "7";//群发消息
String MESSAGE_FILE_MES = "8";//文件消息(发送文件)
}
客户端
//客户端菜单界面
public class QQView {
private boolean loop = true;//控制是否显示菜单
private String key = "";//接收用户的键盘输入
private UserClientService userClientService = new UserClientService();//用于登录和注册
public static void main(String[] args) {
new QQView().mainMenu();
System.out.println("客户端退出系统......");
}
//显示主菜单
private void mainMenu() {
while (loop) {
System.out.println("============欢迎登录网络通信系统============");
System.out.println("tt 1 登录系统");
System.out.println("tt 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(50);
//需要到服务器去验证用户是否合法
//这里编写一个类 UserClientService[用户登录/注册]
if (userClientService.checkUser(userId, pwd)) {
System.out.println("============欢迎 (用户 " + userId + ") ============");
//进入到二级菜单
while (loop) {
System.out.println("n============网络通信系统二级菜单(用户 " + userId + " )============");
System.out.println("tt 1 登录系统");
System.out.println("tt 2 群发信息");
System.out.println("tt 3 私聊信息");
System.out.println("tt 4 发送文件");
System.out.println("tt 9 退出系统");
System.out.print("请输入你的选择:");
key = Utility.readString(1);
switch (key) {
case "1":
System.out.println("显示在线用户列表");
break;
case "2":
System.out.println("群发消息");
break;
case "3":
System.out.println("私聊消息");
break;
case "4":
System.out.println("发送文件");
break;
case "9":
loop = false;
break;
}
}
} else {//登录失败
System.out.println("============登录失败============");
}
break;
case "9":
loop = false;
break;
}
}
}
}
//该类完成用户登录验证和用户注册等功能
public class UserClientService {
//因为可能在其他地方使用User信息,因此作为成员属性
private User u = new User();
private Socket socket;
//根据用户名和密码验证用户是否合法
public boolean checkUser(String userId, String pwd) {
boolean b = false;
//创建User对象
u.setUserId(userId);
u.setPassword(pwd);
//连接到服务器
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(u);//发送User对象
//读取从服务器回复的Message对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message ms = (Message) ois.readObject();
if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//登录成功
//创建一个和服务器端保持通讯的线程->创建一个类ClientConnectServerThread
ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
//启动客户端的线程
clientConnectServerThread.start();
//将线程放在集合中,方便管理
ManageClinetConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
b = true;
} else {
//如果登录失败。。我们要关闭socket
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}
}
//客户端的线程,持有socket
public class ClientConnectServerThread extends Thread {
//该线程需要持有Socket
private Socket socket;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
while (true) {
try {
System.out.println("客户端线程,等待读取从服务端发送来的信息...");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程就会阻塞在这里
Message message = (Message) ois.readObject();
//后面需要使用message
} catch (Exception e) {
e.printStackTrace();
}
}
}
//为了更方便获得Socket
public Socket getSocket() {
return socket;
}
}
//管理客户端连接到服务器的线程的类
public class ManageClinetConnectServerThread {
//我们把多个线程放入到一个HashMap集合中,key就是用户id,vlaue就是线程
private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();
//将某个线程加入到集合
public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
hm.put(userId, clientConnectServerThread);
}
//通过userId,得到对应的线程
public static ClientConnectServerThread getClientConnectServerThread(String userId) {
return hm.get(userId);
}
}
服务端
//这是服务器,在监听9999,等待客户端的连接,并保持通讯
public class QQServer {
private ServerSocket ss = null;
//创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法的
//HashMap 没有处理线程安全,因此在多线程情况下是不安全的
//ConcurrentHashMap 处理了线程安全,在多线程情况下是安全的
private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
static {//在静态代码块,初始化validUsers
validUsers.put("100", new User("100", "123456"));
validUsers.put("200", new User("200", "123456"));
validUsers.put("300", new User("300", "123456"));
validUsers.put("至尊宝", new User("至尊宝", "123456"));
validUsers.put("紫霞仙子", new User("紫霞仙子", "123456"));
validUsers.put("菩提老祖", new User("菩提老祖", "123456"));
}
//验证用户是否有效的方法
private boolean checkUser(String userId, String password) {
User user = validUsers.get(userId);
if (user == null) {//说明userId没有存在在validUsers 的key中
return false;
}
if (!user.getPassword().equals(password)) {//userId正确,但是密码错误
return false;
}
return true;
}
public QQServer() {
//注意:端口可以写在配置文件里
try {
System.out.println("服务端在9999端口监听。。。");
ss = new ServerSocket(9999);
while (true) {//当和某个客户端连接后,会继续监听
Socket socket = ss.accept();
//得到socket的输入流
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
User u = (User) ois.readObject();//读取客户端发送的User对象
//创建一个Message对象,准备回复客户端
Message message = new Message();
//获得socket输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//验证
if (checkUser(u.getUserId(),u.getPassword())) {//登录成功
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
//将message对象回复给客户端
oos.writeObject(message);
//创建一个线程,和客户端保持通信,该线程需要持有socket
ServerConnectClientThread serverConnectClientThread =
new ServerConnectClientThread(socket, u.getUserId());
//启动该线程
serverConnectClientThread.start();
//把该线程对象,放到一个集合中
ManageClientThreads.addClientThread(u.getUserId(), serverConnectClientThread);
} else {//登录失败
message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
oos.writeObject(message);
//关闭socket
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//如果服务器退出了while,说明服务端不在监听,要关闭ServerSocket
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//和某个客户端保持通讯的类
public class ServerConnectClientThread extends Thread {
private Socket socket;
private String userId;//连接到服务端的用户id
public ServerConnectClientThread(Socket socket, String userId) {
this.socket = socket;
this.userId = userId;
}
@Override
public void run() {//可以发送/接收消息
while (true) {
try {
System.out.println("服务端和客户端" + userId + "保持通讯,读取数据...");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
//后面使用message
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//该类用于管理和客户端通信的线程
public class ManageClientThreads {
private static HashMap<String,ServerConnectClientThread> hm = new HashMap<>();
//添加线程对象到 hm 集合
public static void addClientThread(String userId,ServerConnectClientThread serverConnectClientThread){
hm.put(userId, serverConnectClientThread);
}
//根据userId 返回serverConnectClientThread
public static ServerConnectClientThread getServerConnectClientThread(String userId){
return hm.get(userId);
}
}
2、拉取在线用户列表
客户端
//UserClientService add!!!!!!
//向服务端请求在线用户列表
public void onlineFriendList(){
//发送一个Message,类型MESSAGE_GET_ONLINE_FRIEND
Message message = new Message();
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
message.setSender(u.getUserId());
//发送给服务器
//得到当前线程的socket,对应的输出流对象
try {
ObjectOutputStream oos = new ObjectOutputStream
(ManageClinetConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
//ClientConnectServerThread run() update!!!!!!
@Override
public void run() {
while (true) {
try {
System.out.println("客户端线程,等待读取从服务端发送来的信息...");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
//如果服务器没有发送Message对象,线程就会阻塞在这里
Message message = (Message) ois.readObject();
//判断message类型,然后做相应的业务处理
//如果读取的是服务端返回的在线用户列表
if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
//取出在线列表信息,并显示
String[] onlineUsers = message.getContent().split(" ");
System.out.println("n=============当前在线用户列表===========");
for (int i = 0; i < onlineUsers.length; i++) {
System.out.println("用户信息:" + onlineUsers[i]);
}
} else {
System.out.println("是其他类型,暂时不处理。。。");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端
//ManageClientThreads add!!!!!!!!!
//返回在线用户列表
public static String getonlineUser() {
//遍历集合,遍历 hashmap的key
Iterator<String> iterator = hm.keySet().iterator();
String onlineUserList = "";
while (iterator.hasNext()) {
onlineUserList += iterator.next().toString() + " ";
}
return onlineUserList;
}
//ServerConnectClientThread run() update!!!!
@Override
public void run() {//可以发送/接收消息
while (true) {
try {
System.out.println("服务端和客户端" + userId + "保持通讯,读取数据...");
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
//后面使用message
if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
//客户端要在线用户列表
System.out.println(message.getSender()+" 要在线用户列表");
String onlineUser = ManageClientThreads.getonlineUser();
//构建一个message对象,返回给客户端
Message message2 = new Message();
message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message2.setContent(onlineUser);
message2.setGetter(message.getSender());
//返回给客户端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message2);
}else{
System.out.println("其他类型的message,暂时不处理。。。");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、无异常退出
客户端
//UserClientService add!!!!!!!!!!!!!!!
//退出客户端,并给服务端发送一个退出系统的message对象
public void logout(){
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(u.getUserId());//一定要指定是哪个客户端
//发送
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
oos.writeObject(message);
System.out.println(u.getUserId()+"退出系统");
System.exit(0);//结束进程
} catch (IOException e) {
e.printStackTrace();
}
}
服务端
//ServerConnectClientThread update!!!!!!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
//客户端退出
System.out.println(message.getSender()+"要求退出");
//将这个客户端从线程集合中移除
ManageClientThreads.removeServerConnectClientThread(message.getSender());
socket.close();//关闭连接
//退出线程
break;
}
4、私聊
客户端
//QQView update!!!!!
case "3":
System.out.print("请输入想要聊天的用户号(在线):");
String getterId = Utility.readString(50);
System.out.print("请输入想说的话:");
String content = Utility.readString(50);
//将消息发送给服务端
messageClientService.sendMessageToOne(content, userId, getterId);
break;
//该类提供和消息相关的服务方法
public class MessageClientService {
/**
* @param content 内容
* @param senderId 发送用户id
* @param getterId 接收用户id
*/
public void sendMessageToOne(String content, String senderId, String getterId) {
//构建message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_COMM_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setContent(content);
message.setSendTime(new Date().toString());
System.out.println(senderId + " 对 " + getterId + " 说 " + content);
//发送给服务端
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//ClientConnectServerThread update!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
//接收到普通聊天消息
//把从服务端转发的消息,显示到控制台即可
System.out.println("n" + message.getSendTime() + "n" + message.getSender()
+ " 对 " + message.getGetter() + " 说 " + message.getContent());
}
服务端
//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
//客户端私聊
ServerConnectClientThread serverConnectClientThread = ManageClientThreads.getServerConnectClientThread(message.getGetter());
//得到对应的socket输出流,将message发送给指定的客户端
ObjectOutputStream oos =
new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
oos.writeObject(message);//如果客户不在线,可以保存到数据库,做成离线消息
}
5、群聊
无非就是服务端一次性遍历多个客户端socket。
客户端
//MessageClientService update!!!!!!!
/**
*
* @param content 内容
* @param senderId 发送者id
*/
public void sendMessageToAll(String content,String senderId){
//构建message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setSender(senderId);
message.setContent(content);
message.setSendTime(new Date().toString());
System.out.println(senderId + " 对大家说 " + content);
//发送给服务端
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
//ClientConnectServerThread update!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
//接收到群发消息
//把从服务端转发的消息,显示到控制台即可
System.out.println("n" + message.getSendTime() + "n" + message.getSender()
+ " 对大家说 " + message.getContent());
}
服务端
//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
//需要遍历,管理线程的集合,把所有线程的socket得到,然后把message进行转发
HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();
Iterator<String> iterator = hm.keySet().iterator();
while(iterator.hasNext()){
//取出在线用户id
String onlineUserId = iterator.next().toString();
if(!onlineUserId.equals(message.getSender())){//排除群发消息的发起人
//进行转发message
ObjectOutputStream oos =
new ObjectOutputStream(hm.get(onlineUserId).getSocket().getOutputStream());
oos.writeObject(message);
}
}
}
6、发文件
客户端
//该类完成文件的传输
public class FileClientService {
/**
* @param src 源文件
* @param dest 把文件传输到对方的哪个目录
* @param senderId 发送用户id
* @param getterId 接收用户id
*/
public void sendFileToOne(String src, String dest, String senderId, String getterId) {
//读取src文件 ,做成一个message
Message message = new Message();
message.setMesType(MessageType.MESSAGE_FILE_MES);
message.setSender(senderId);
message.setGetter(getterId);
message.setSrc(src);
message.setDest(dest);
//需要将文件读取
FileInputStream fileInputStream = null;
byte[] fileBytes = new byte[(int) new File(src).length()];
try {
fileInputStream = new FileInputStream(src);
fileInputStream.read(fileBytes);//将src文件读入到程序的字节数组中
message.setFileBytes(fileBytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//提示信息
System.out.println("n" + senderId + " 给 " + getterId + " 发送文件:" + src
+ " 到对方电脑的目录 " + dest);
//发送
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClinetConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//ClientConnectServerThread update!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
//文件消息
System.out.println("n" + message.getSender() + " 给 " + message.getGetter()
+ " 发送文件:" + message.getSrc()
+ " 到我的电脑的目录 " + message.getDest());
//取出message的文件字节数组,通过文件输出流,写入磁盘
FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
fileOutputStream.write(message.getFileBytes());
fileOutputStream.close();
System.out.println("n 保存文件成功~");
}
//QQView update!!!!!!
case "4":
System.out.println("请输入你想发送文件给的用户(在线用户):");
getterId = Utility.readString(50);
System.out.println("请输入发送文件的路径(d:\xx.jpg): ");
String src = Utility.readString(100);
System.out.println("请输入保存文件的最终路径(d:\xx.jpg)");
String dest = Utility.readString(100);
fileClientService.sendFileToOne(src,dest,userId,getterId);
break;
服务端
//ServerConnectClientThread update!!!!!!
else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
//客户端发送文件,服务端转发给getterId
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
oos.writeObject(message);
}
7、服务器推送新闻
服务端
//SendNewsToAllService add!!!!!!
public class SendNewsToAllService implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("请输入服务器要推送的新闻、消息[输入exit表示退出推送服务]");
String news = Utility.readString(100);
if("exit".equals(news)){
break;
}
//构建一个消息,群发消息
Message message = new Message();
message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
message.setContent(news);
message.setSendTime(new Date().toString());
message.setSender("服务器");
System.out.println("服务器推送消息给所有人 说:" + news);
//遍历当前所有的在线线程,得到socket,发送message
HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();
Iterator<String> iterator = hm.keySet().iterator();
while(iterator.hasNext()){
String onlineUserId = iterator.next().toString();
try {
ObjectOutputStream oos =
new ObjectOutputStream(hm.get(onlineUserId).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
8、离线消息和离线文件
最后
以上就是负责服饰为你收集整理的模拟QQ聊天功能通信系统多用户通信系统的全部内容,希望文章能够帮你解决模拟QQ聊天功能通信系统多用户通信系统所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复