简介
近日,我通过人工智能,网络爬虫,语义分析,非对称加密…编不下去了,我在B站看视频学Java感觉自己行了.强制输出一下学到的东西
这个程序实现了拉取在线用户,私聊,和收发文件,并没有GUI界面.
一.程序设计
如图,整体思路是,先将消息封装到类,以类的形式在客户端的对应线程和服务端的对应线程之间进行通信
二.代码实现
Message类,MessageType接口用来设置Message类中的消息类型mesType
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;
private String dest;
private String src;
}
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_FILE_MES = "7"; //文件消息
}
客户端中的通信线程类
当客户端登陆验证通过后就会与服务端产生连接并创建一个线程,这个线程持有一个socket,并且这个线程一经启动便会进入死循环不断的接收来自服务器的数据,如果接受到来自服务器的数据那么会通过发回的Message里的消息类型来做出相应的反应,如果没有接受到数据便会进入阻塞状态,如果连接中断就会不停的循环抛出异常,所以必须在退出的时候告知服务器才能无异常退出
public class CSConnectThread extends Thread{
//该线程需要持有socket
Socket socket;
//接受一个Socket对象
public CSConnectThread (Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return 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)) {
//如果读取到了服务端返回的MESSAGE_RET_ONLINE_FRIEND
String[] onlineFriendList = message.getContent().split(" ");
System.out.println("==============在线用户=============");
for (int i = 0; i < onlineFriendList.length; i++) {
System.out.println("用户 : " + onlineFriendList[i]);
}
} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
System.out.println("\n" + message.getSender() + " 对 " + message.getGetter() + " 说: " + message.getContent());
} else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
System.out.println("正在保存" + message.getSender() + "发来的文件" + "保存路径为:" + message.getDest());
//取出message对象中的文件数组
FileOutputStream fileOutputStream = new FileOutputStream(message.getDest());
fileOutputStream.write(message.getFileBytes());
fileOutputStream.close();
System.out.println("保存文件成功");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这是服务器端的通信线程类中重写的run方法,和客户端中的同理,一经启动就会不停的接收消息,根据消息类型来做出相应的服务,直到收到来自客户端的退出请求
@Override
public void run() {
while (true) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Message message = (Message) ois.readObject();
if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {
System.out.println(message.getSender() + ": 正在请求在线用户列表");
String onlineUser = ManageThread.getOnlineUser();
//构建一个message对象
Message message1 = new Message();
message1.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
message1.setContent(onlineUser);
message1.setGetter(message.getSender());
//写入到数据通道,返回给客户端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message1);
} else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//收到客户端退出的请求
System.out.println(message.getSender() + "--退出系统");
ManageThread.remove(message.getSender());
break;
} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
CSConnectThread thread = ManageThread.getThread(message.getGetter());
//得到对应的Socket的对象输出流,将message转发给指定的客户端
ObjectOutputStream oos = new ObjectOutputStream(thread.getSocket().getOutputStream());
//转发
oos.writeObject(message);
} else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
CSConnectThread thread = ManageThread.getThread(message.getGetter());
ObjectOutputStream oos = new ObjectOutputStream(thread.getSocket().getOutputStream());
oos.writeObject(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
以下为服务端中的线程管理类,在用户登录成功后会根据用户名来把线程放到一个集合中来管理线程
在服务端中如果接收到一个客户端要与另一个客户端通信的请求时,会发生以下:
客户端1与服务器连接,并与服务器中的线程1保持通信
客户端2与服务器连接,并与服务器中的线程2保持通信
客户端1发出了一个Message,其中接收者为客户端2,服务器接收到了Message
服务器根据Message中的类型来做出判断,普通消息或发送文件,再根据Message中的getterId为客户端2,并在线程池中找到服务器与客户端2保持通讯的线程,并取得该线程中的socket的ObjectOutputStream并通过writeObject()将Message发给客户端2
public class ManageThread {
//把线程放到一个集合中,key就是用户id,value就是线程
private static HashMap<String, CSConnectThread> hm = new HashMap<>();
//添加
public static void addThread(String userId, CSConnectThread csConnectThread) {
hm.put(userId, csConnectThread);
}
//取出
public static CSConnectThread getThread (String userId) {
return hm.get(userId);
}
public static String getOnlineUser () {
Iterator<String> iterator = hm.keySet().iterator();
String onlineUsersList = "";
while (iterator.hasNext()) {
onlineUsersList += iterator.next().toString() + " ";
}
return onlineUsersList;
}
public static void remove(String userId) {
hm.remove(userId);
}
}
总结
很简单的一个小项目,但是客户端和服务端在写的时候要不停的来回切换,很容易出错,也很容易逻辑混乱,源码放到网盘里有需要的自己拿,交个大作业应该还是能行的,希望我学习可以坚持下去,少年有梦不应止于心动
链接: https://pan.baidu.com/s/1vF5sRfEjKBf_bHEes-NtIQ 密码: hk2b