这个仿QQ项目是参考韩顺平老师的多线程课程做的,因为个人觉得非常有意义特别是让我对多线程通信又了一个新的理解因此我准备写一篇总结(如果觉得视频太长可以参考下):
具体视频地址:大家给韩老师一键三连【韩顺平讲Java】Java网络多线程专题 - TCP UDP Socket编程 多线程 并发处理 文件传输 新闻推送 Java_哔哩哔哩_bilibili那我们直接开始:
1.QQ项目的实现思路:
(1)创建一一个服务端(QQClient)和一个客户端(QQServer)和一个公共类(QQcomman)
- 服务端包含了:线程管理集合(ManageServerThread)、服务端启动主函数(ServerFrame)、链接客户端线程(ServerConnectClientThread)、服务端服务(ServerService)
- 客户端包含了:登陆界面(QQview)、工具类(Utility)链接服务端(ClientConnectServerThread)客户端服务(ClientService)管理客户端线程类(ManageClientThread)
- 公共类(QQcommon):Message(消息类)、MessageType(消息类型)、User(用户类)
package QQcommon;
import java.io.Serializable;
/**
* message类
* 如果一个对象想通过对象流的方式去传输 需要对这个对象进行序列化Serializable
*/
public class Message implements Serializable {
private static final long SeriaVersionUID = 1L;
private String sender;//发送者
private String getter;//接收者
private String content;//内容
private String senTime;//发送时间
private String mesType;//消息类型 (可以定义窗口的类型)
private byte[] fileBytes;
private int fileLen = 0;
private String dest;//将文件传输到哪里
private String src;//源文件路径
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;}
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 byte[] getFileBytes() {return fileBytes;}
public void setFileBytes(byte[] fileBytes) {this.fileBytes = fileBytes;}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSenTime() {
return senTime;
}
public void setSenTime(String senTime) {
this.senTime = senTime;
}
public String getMesType() {
return mesType;
}
public void setMesType(String mesType) {
this.mesType = mesType;
}
}
MessageType(消息类型)
/**
* @author MXS
* @version 1.0
* 表示消息类型
*/
public interface MessageType {
String MESSAGE_LOGIN_SUCCEED="1";
String MESSAGE_LOGIN_FALSE="2";
String MESSAGE_COMMMON_MESSAGE="3";//普通信息包 私聊
String MESSAGE_GET_ONLINE_FRIEND="4";//要求返回在线列表
String MESSAGE_RET_ONLINE_FRIEND="5";//返回在线用户列表
String MESSAGE_CLIENT_EXIT="6";//客户端请求退出
String MESSAGE_SEND_ALL="7";//群发消息
String MESSAGE_SEND_WENJIAN="8";//发文件
}
User(用户类)
/**
* user类 如果一个对象想通过对象流的方式去传输 需要对这个对象进行序列化Serializable
*/
public class User implements Serializable {
private static final long SeriaVersionUID = 1L;
private String userID;//账号
private String userpwd;//密码
public User(){
}
public User(String userID, String userpwd) {
this.userID = userID;
this.userpwd = userpwd;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
public String getUserpwd() {
return userpwd;
}
public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}
}
(2)客户端通过线程管理类里面的线程(持有的socket),和服务端端的线程管理类(持有socket)进行通信链接
- 用线程管理类的愿意是需要我们对所有的通信线程去管理
- 通信线程的run方法能够让我们和服务端或者客户端不断的进行通信
2.开始实现QQ登陆(Hashmap代替数据裤)
-
实现客户端的登陆说明(QQview)
public class QQview {
public static void main(String[] args) {
new QQview().LoginUI();
}
private boolean loop=true;//登陆条件
private String key="";//输入登陆系统的钥匙
private String userID;//用户的账户
private String userPwd;//用户密码
private ClientService clientService=new ClientService();
public void LoginUI() {
while (loop){
System.out.println("======欢迎登陆网络通信系统======");
System.out.println("\t\t 登陆系统选择1");
System.out.println("\t\t 退出系统选择9");
System.out.println("请输入你的选择");
key= Utility.readString(1);
switch (key){
case "1":
System.out.println("请输入账号");
userID=Utility.readString(50);
System.out.println("请输入密码");
userPwd=Utility.readString(50);
//如果正确 则登陆成功 登陆成功的条件 ---->登陆成功
if (loop) {
while (loop){
System.out.println("\n=======网络通信系统二级菜单(用户"+userID+")=======");
System.out.println("\t\t 1 显示在线用户列表");// /t/t 表示换行
System.out.println("\t\t 2 群发消息消息");
System.out.println("\t\t 3 私聊消息");
System.out.println("\t\t 4 发送文件");
System.out.println("\t\t 9 退出系统");
System.out.println("请输入你的选择");
//读取输入的消息
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;
}
}
}
}
?已完成一级菜单和二级菜单(其中Util是一个工具类我在最后会给出)
- 创建管理线程类(ManageServerThread):用来管理客户端链接到服务端的线程
- 用ConcurrentHashMap的原因是:HashMap是非线程安全的。而HashMap的线程不安全主要体现在resize时的死循环及使用迭代器时的fast-fail上。
/**
* 管理客户端链接到服务端的线程类
*/
import java.util.concurrent.ConcurrentHashMap;
public class ManageClientThread {
//ConcurrentHashMap用来创建用户ID和链接服务端ID的 map表
public static ConcurrentHashMap<String,ClientConnectServerThread> hm=new ConcurrentHashMap<>();
//添加线程方法
public static void addThread(String userid, ClientConnectServerThread Thread){
hm.put(userid, Thread);
}
//移除线程方法
public static void removeThread(String userID){
hm.remove(userID);
}
//获得线程方法
public static ClientConnectServerThread getUserIDThread(String userID){
return hm.get(userID);
}
}
- 实现ClientConnectServerThread(链接服务端)
public class ClientConnectServerThread extends Thread{
private Socket socket;
private Message message;
public ClientConnectServerThread(Socket socket) {
this.socket=socket;
}
//获得Socket
public Socket getSocket(){
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
while (true){
}
}
}
2.实现ClientService(客户端服务类)用来完成和服务端通信:所有和服务端通信的方法都在这里面(只需要在qqviewl调用相关的方法即可)
public class ClientService {
private User user = new User();//用户对象
private boolean isSucceed;//是否登陆成功
private Message message;//消息对象
private ClientConnectServerThread clientConnectServerThread;//链接服务端线程的对象
//验证用户的账号密码是否正确方法
public boolean checkUser(String userID, String userPwd) {
user.setUserID(userID);
user.setUserpwd(userPwd);
try {
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9898);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);
//从服务端读取回来的message对象
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
message = (Message) ois.readObject();
//验证成功
if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {
//用该socket开一个线程和服务端保持通信
clientConnectServerThread = new ClientConnectServerThread(socket);
clientConnectServerThread.start();
//加入管理线程
ManageClientThread.addThread(userID, clientConnectServerThread);
isSucceed = true;
} else {//验证失败
System.out.println("用户账号" + userID + "和密码不匹配");
isSucceed = false;
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return isSucceed;
}
// 获得在线好友列表
}
我们用检测用户登陆的方法代替QQview里面的loop
?3.服务端管理线程类(ManageServerThread)和服务端服务类(ServerService)
/**
* 服务端管理线程类
*/
public class ManageServerThread {
public static ServerConnectClientThread serverConnectClientThread;//服务端链接客户端线程
public static HashMap<String,ServerConnectClientThread> hm=new HashMap<>();//map表
/**
* 添加线程
*/
public static void addThread(String userid,ServerConnectClientThread Thread){
hm.put(userid, Thread);
}
//移除线程
public static void removeThread(String userID){
hm.remove(userID);
}
//获取线程
public static ServerConnectClientThread getThread(String userID){
serverConnectClientThread=hm.get(userID);
return serverConnectClientThread;
}
//通过managThread 获取在线列表
public static String getOnlineList(){
//遍历hm的方法
Iterator<String> iterator = hm.keySet().iterator();
String onlineUserList="";
while (iterator.hasNext()){
onlineUserList+=iterator.next().toString()+" ";//用空格隔开这个时候就能更加容易的遍历
}
return onlineUserList ;
}
//获取hm表
public static HashMap<String,ServerConnectClientThread> getHm(){
return hm;
}
}
服务端服务类(ServerService):此时已经可以监听客户端的9898端口
/**
* 服务端服务类:所有的功能方法以及hm都在这里
*/
public class ServerService {
private User user;//用户
private Message message=new Message();//消息
public ServerConnectClientThread serverConnectClientThread;//链接线程方法类
private ServerSocket serverSocket;
private static ConcurrentHashMap<String,User> valueHm=new ConcurrentHashMap<>();
static {//添加用户信息
valueHm.put("100",new User("100","1234"));
valueHm.put("200",new User("200","1234"));
valueHm.put("300",new User("300","1234"));
valueHm.put("400",new User("400","1234"));
valueHm.put("500",new User("500","1234"));
valueHm.put("600",new User("600","1234"));
}
//验证uesr是否正确
public boolean checkUser(String userID,String userPwd){
User user=valueHm.get(userID);//取出相对应的user对象用来验证账号密码
if (user==null){//没有这个用户
return false;
}
if (!user.getUserpwd().equals(userPwd)) {
System.out.println("账号存在密码不正确");
return false;
}
return true;//账号密码都正确
}
public ServerService() throws IOException {
System.out.println("服务端端口正在监听 9898");
try {
serverSocket = new ServerSocket(9898);
//验证登陆信息
while (true){//需要不断的监听 因为客户端会不断的发现对象过来
//读取信息
Socket socket=serverSocket.accept();
ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());
user = (User) ois.readObject();//此时读取的信息是user
ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
if (checkUser(user.getUserID(), user.getUserpwd())) {//如果正确
//把正确的消息回复给客户端
message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
oos.writeObject(message);
serverConnectClientThread=new ServerConnectClientThread(socket, user.getUserID());
new Thread(serverConnectClientThread).start();
//把线程放管理线程里
ManageServerThread.addThread(user.getUserID(), serverConnectClientThread);
}else {
System.out.println("用户 ID="+user.getUserID()+"用户 pwd="+user.getUserpwd()+"登陆失败");
message.setMesType(MessageType.MESSAGE_LOGIN_FALSE);
oos.writeObject(message);
socket.close();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//退出while循环
serverSocket.close();
}
}
?(ServerFrame)启动服务端类
public class ServerFrame {
public static void main(String[] args) throws IOException {
new ServerService();
}
}
到此我们完成了用户登陆验证的功能(成功则会进入二级菜单;账号密码不对则登陆不上)
?第一阶段的总结:
1.我们通过QQview类中的key选择自己登陆还是退出,
2.在登陆的过程中我们需要通过ClientService类中的checkuser方法去验证用户账号密码是否正确。
我们用到对象流的方式,在此中我们必须注意:(用对象流的原因也是因为我可以在该对象中赋予他多种属性,然后解析方便有效)
如果一个对象想通过对象流的方式去传输 需要对这个对象进行序列化Serializable
3.我们在服务端的ServerService类中用hm表代替数据库
?4.在服务端women直接用checkUser方法作为条件需要我们注意我们是需要把message对象传回给客户端,由客户端接收到的message类型作为是否验证用户登陆成功的信息:
?到此我们服务端和客户端的通信已经打通接下来我们完善功能即可!
3.实现拉取在线用户的列表功能
- 我们从客户端发送一个message消息给客户端
- 服务端接收到这个message消息判断这个message的类型返回列表内容
- 客户端Client根据服务端返回的message类型进行解析
(1)我们从客户端发送一个message消息给客户端
在ClientService类中定义一个获取在线列表的方法:
注意:我们需要通过管理线程ManageClientThread去拿到该用户UserID的线程的socket的OutputStream.才能传输要不然传输的socket不对的话是传错的;
// 获得在线好友列表
public void getOnlineFriendList() {
//发送Message给服务端
Message message = new Message();
message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
message.setSender(user.getUserID());
message.setGetter(user.getUserID());
try {
ObjectOutputStream oos =
new ObjectOutputStream(ManageClientThread.getUserIDThread(user.getUserID()).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
在QQview直接调用 :
?(2)服务端接收到这个message消息判断这个message的类型返回列表内容
public class ServerConnectClientThread implements Runnable{
private Socket socket;
private String userID;
private Message message;//消息
private ArrayList<Message> messageArr=new ArrayList<>();
private ConcurrentHashMap<String,ArrayList> messageHm=new ConcurrentHashMap<>();
public ServerConnectClientThread(Socket socket, String userID){
this.userID=userID;
this.socket=socket;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
while (true){
System.out.println("服务器和客户端"+userID+"保持通信,读取数据");
try {
//得到客户端传过来的message类型
ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());
message = (Message) ois.readObject();
//根据类型进行操作
if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {
System.out.println(message.getSender()+" 要用户在线列表");
//获取在线用户 (通过messageThread)
//构建新的message
String onlineList= ManageServerThread.getOnlineList();
message.setContent(onlineList); //用户列表
message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND); //消息类型
message.setGetter(message.getSender());
ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
}
else {
System.out.println("Server其他类型暂时不显示");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
思路:
- 是通过server端管理的ManageServerThread类中的hm表遍历得到链接服务端的所对应的线程用户名
- 把用户名转换成String 字符串发送到客户端进行解析成String[] 数组打印出来
ManageServerThread.getOnlineList();这个方法是放在管理线程类中:
//通过managThread 获取在线列表
public static String getOnlineList(){
//遍历hm的方法
Iterator<String> iterator = hm.keySet().iterator();
String onlineUserList="";
while (iterator.hasNext()){
onlineUserList+=iterator.next().toString()+" ";//用空格隔开这个时候就能更加容易的遍历
}
return onlineUserList ;
}
ServerConnectClientThread(服务端链接客户端线程类):
package Test;
import QQClient.src.serviceThread.ManageClientThread;
import QQServer.src.qqserver.ManageServerThread;
import QQcommon.Message;
import QQcommon.MessageType;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class ServerConnectClientThread implements Runnable {
private Socket socket;
private String userID;
private Message message;//消息
private ArrayList<Message> messageArr = new ArrayList<>();
private ConcurrentHashMap<String, ArrayList> messageHm = new ConcurrentHashMap<>();
public ServerConnectClientThread(Socket socket, String userID) {
this.userID = userID;
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
@Override
public void run() {
while (true) {
System.out.println("服务器和客户端" + userID + "保持通信,读取数据");
try {
//得到客户端传过来的message类型
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
message = (Message) ois.readObject();
//根据类型进行操作
if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {
System.out.println(message.getSender() + " 要用户在线列表");
//获取在线用户 (通过messageThread)
//构建新的message
String onlineList = ManageServerThread.getOnlineList();
message.setContent(onlineList); //用户列表
message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND); //消息类型
message.setGetter(message.getSender());
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(message);
} else {
System.out.println("Server其他类型暂时不显示");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(3)客户端Client根据服务端返回的message类型进行解析
public class ClientConnectServerThread extends Thread {
private Socket socket;
private Message message;
public ClientConnectServerThread(Socket socket) {
this.socket = socket;
}
//获得Socket
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
while (true) {
try {
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
message = (Message) ois.readObject();
//判断Message类型
System.out.println("Client====" + message.getMesType());
if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
//转换成数组
String[] onLinelist = message.getContent().split(" ");
System.out.println("\n===========用户在线列表==========");
for (int i = 0; i < onLinelist.length; i++) {
System.out.println("用户" + onLinelist[i]);
}
} else {
System.out.println("Client其他类型暂时不显示");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
到此我们实现拉取线上qq用户的功能:
登陆系统(100,200) 可以看到这个用户列表已经成功显示
4.无异常退出系统
QQview的时候服务端会大量的报错:原因是当退出客户端Cilent的时候,服务端的socket还没有关闭,而且在客户端只关闭了该对象的线程,这样的情况下服务端肯定会报错:
- 错误:线程退出了但是进程没有退出
- 错误:大量的IO错误(服务端)
解决思路:
- 在客户端发一条消息Message对服务端说我要退出了
- 服务端关闭该线程对应的服务端socket
- 此时客户端再进行退出
1.在客户端发一条消息Message对服务端说我要退出了
在客户端Client端ClientService写退出方法
//关闭进程解决退出异常问题
public void longinOut() {
Message message = new Message();
message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
message.setSender(user.getUserID());
try {
ObjectOutputStream oos
=new ObjectOutputStream(ManageClientThread.getUserIDThread(user.getUserID()).getSocket().getOutputStream());
oos.writeObject(message);
System.out.println(user.getUserID()+"退出了系统");
ManageClientThread.removeThread(user.getUserID());
System.exit(0);//退出进程
} catch (IOException e) {
e.printStackTrace();
}
}
在QQview写上对应方法
2.服务端关闭该线程对应的服务端socket
else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
System.out.println(message.getSender()+"退出服务端");
ManageServerThread.removeThread(message.getSender());
socket.close();
break;//break狠重要 如果不退出这个循环会出现大量的IO错误
}
3. 此时服务端再进行退出serversocket
这样就可以解决了退出异常问题! 直接看效果
客户端已经退出系统
服务端也显示客户端100退出了系统:
5.私聊功能的实现:
实现思路:
- 在客户端发送一个私聊类型的Message 给服务端
- 服务端接收到私聊类型的Message 把message转发给相应的getterID
- 客户端接收服务端发送过来的消息Message 解析打印
1.在客户端发送一个私聊类型的Message 给服务端
QQview:
case "3":
System.out.println("请输入私聊的聊天号");
String getterID=Utility.readString(50);
System.out.println("请输入你想说的话");
String con=Utility.readString(100);
clientService.sendMessageToOne(con,userID,getterID);
break;
ClientService私聊方法的实现:
//私聊消息
public void sendMessageToOne(String content,String senderID,String getterID){
Message message=new Message();
message.setMesType(MessageType.MESSAGE_COMMMON_MESSAGE);
message.setSender(senderID);
message.setGetter(getterID);
message.setContent(content);
System.out.println("sendMessageToOne"+message.getContent());
message.setSenTime(new Date().toString());
System.out.println(senderID+"对"+getterID+"说了"+content);
try {
ObjectOutputStream oos
=new ObjectOutputStream(ManageClientThread.getUserIDThread(senderID).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
2服务端接收到私聊类型的Message 把message转发给相应的getterID
else if (message.getMesType().equals(MessageType.MESSAGE_COMMMON_MESSAGE)) {
//实现私聊保存离线消息的功能
messageArr.add(message);
System.out.println("messageArrayList.size"+messageArr.size());
for (int i = 0; i < messageArr.size(); i++) {
message=messageArr.get(i);//对应的message
if (checkOnline(message.getGetter(),ManageServerThread.getOnlineList()) ) {
System.out.println("server.content"+message.getContent());
ObjectOutputStream oos=
new ObjectOutputStream(ManageServerThread.getThread(message.getGetter()).getSocket().getOutputStream());
oos.writeObject(message);
messageHm.remove(message.getGetter());
}else {
//存进hm 里面
messageHm.put(message.getGetter(), messageArr);
}
}
实现效果:
3. 客户端(ClientConnectServerThread)接收服务端发送过来的消息Message 解析打印
else if (message .getMesType().equals(MessageType.MESSAGE_COMMMON_MESSAGE)) {
System.out.println("\n"+message.getSender()+"对"+message.getGetter()+"说"+message.getContent());
}
6.群发消息功能实现
实现思路:
- 在客户端发送一个群聊类型的Message 给服务端
- 服务端接收到群聊类型的Message 把message转发除了发送者之外的getterID
- 客户端接收服务端发送过来的消息Message 解析打印
1.在客户端发送一个群聊类型的Message 给服务端
QQview:
case "2":
System.out.println("请输入对大家说的话");
String content=Utility.readString(100);
clientService.sendMessageToAll(content,userID);
break;
ClientService:
//群发消息
public void sendMessageToAll(String content,String userID){
Message message=new Message();
message.setContent(content);
message.setMesType(MessageType.MESSAGE_SEND_ALL);
message.setSender(userID);
message.setSenTime(new Date().toString());
System.out.println(userID+"对"+"大家说"+content);
try {
ObjectOutputStream oos
= new ObjectOutputStream(ManageClientThread.getUserIDThread(userID).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
2.服务端接收到群聊类型的Message 把message转发除了发送者之外的getterID
ServerConnectClientThread:
else if (message.getMesType().equals(MessageType.MESSAGE_SEND_ALL)) {
//取出HashMap表然后遍历
HashMap<String, ServerConnectClientThread> hm = ManageServerThread.getHm();
Iterator<String> iterator = hm.keySet().iterator();
//遍历hm
while (iterator.hasNext()){
//取出在线用户id
String onlineUser= iterator.next().toString();
if (!onlineUser.equals(message.getSender())) {//通过hm得到和message的发送者不一样的userID得到socket
ObjectOutputStream oos=new ObjectOutputStream(hm.get(onlineUser).getSocket().getOutputStream());
oos.writeObject(message);
System.out.println("服务端的消息发送成功");
}
}
3.客户端接收服务端发送过来的消息Message 解析打印
ClientConnectServerThread:
else if (message.getMesType().equals(MessageType.MESSAGE_SEND_ALL)) {
System.out.println(message.getSender()+"对大家说说"+message.getContent());
}
实现效果:
?7.实现发文件功能
实现思路:
- 先把文件用文件流的方式读取(FileInputStream),再把文件流读取的数据存储到message对象流里面
- 服务端接收对应的文件message消息转发到客户端
1.先把文件用文件流的方式读取(FileInputStream),再把文件流读取的数据存储到message对象流里面
QQview
case "4":
System.out.println("你想发送文件给那个用户");
getterID=Utility.readString(50);
System.out.print("请输入发送文件的路径(形式 d:\\xx.jpg)");
String src=Utility.readString(100);
System.out.print("请输入把文件发送到对应的路径(形式 d:\\yy.jpg)");
String dest=Utility.readString(100);
clientService.sendFileToOne(src,dest,userID,getterID);
break;
ClientService:
//发送文件
public void sendFileToOne(String src, String dest, String userID, String getterID) {
Message message=new Message();
message.setSender(userID);
message.setGetter(getterID);
message.setMesType(MessageType.MESSAGE_SEND_WENJIAN);
message.setSrc(src);
message.setDest(dest);
//读取文件
File file=new File(src);
int length= (int) file.length();
byte[] fileBytes=new byte[length];
//获得文件长度
message.setFileBytes(fileBytes);
FileInputStream fileInputStream=null;
try {
fileInputStream = new FileInputStream(src);
fileInputStream.read(fileBytes);
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭流
if (fileInputStream!=null){
try {
fileInputStream.close();
System.out.println("FileClientService读完关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
//提示信息
System.out.println("\n" + userID +" 给"+ getterID +"发送文件:"+ src+"到对方的电脑的目录" + dest);
try {
ObjectOutputStream oos=
new ObjectOutputStream(ManageClientThread.getUserIDThread(userID).getSocket().getOutputStream());
oos.writeObject(message);
} catch (IOException e) {
e.printStackTrace();
}
}
2.服务端接收对应的文件message消息转发到客户端
else if (message.getMesType().equals(MessageType.MESSAGE_SEND_WENJIAN)) {
//根据message的getterid得到相对应的线程 将message转发
ObjectOutputStream oos
=new ObjectOutputStream(ManageServerThread.getThread(message.getGetter()).getSocket().getOutputStream());
oos.writeObject(message);
3.客户端显示并写入磁盘
else if (message.getMesType().equals(MessageType.MESSAGE_SEND_WENJIAN)) {
System.out.println("\n"+message.getSender()+
"给"+message.getGetter()+"发文件"+message.getSrc()+"到我的电脑目录"+message.getDest());
//取出message的文件数组,通过文件输出流 写出磁盘
FileOutputStream fileOutputStream=new FileOutputStream(message.getDest());
byte[] b=message.getFileBytes();
fileOutputStream.write(b);
fileOutputStream.close();
System.out.println("\n 保存文件成功");
}
实现效果
?8.实现服务端推送功能
实现思路:
1.在服务端开一个推送服务消息的线程(SendNewsToAll)客户端接收普通消息message
SendNewsToAll:
package QQServer.src.qqserver;
import QQClient.src.QQUtils.Utility;
import QQcommon.Message;
import QQcommon.MessageType;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
/**
* 推送消息给客户端
*/
public class SendNewsToAll implements Runnable {
@Override
public void run() {
//多次推送
while (true) {
System.out.println("请输入服务器推送的新闻按End取消推送");
String news = Utility.readString(100);
//构建一个消息
if("End".equals(news)){
//退出推送
System.out.println("退出服务器的推送");
break;
}
Message message = new Message();
message.setSender("服务器");
message.setContent(news);
message.setSendTime(new Date().toString());
message.setmesType(MessageType.MESSAGE_SEND_ALL);
//遍历所有的通信线程,得到socket 发送message
HashMap<String, ServerConnetClientThread> hm = ManageThread.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();
}
}
}
}
}
实现效果:
5.5号凌晨00:41分:明天起来写总结!(大家有什么问题一起交流)
源码放这里了:链接: https://pan.baidu.com/s/1hhli-zElHbahH4McQm009g?pwd=ip3j 提取码: ip3j
MXS很垃圾
|