这个仿QQ项目是参考韩顺平老师的多线程课程做的,因为个人觉得非常有意义特别是让我对多线程通信又了一个新的理解因此我准备写一篇总结(如果觉得视频太长可以参考下):

具体视频地址:大家给韩老师一键三连【韩顺平讲Java】Java网络多线程专题 - TCP UDP Socket编程 多线程 并发处理 文件传输 新闻推送 Java_哔哩哔哩_bilibili那我们直接开始:

1.QQ项目的实现思路:

(1)创建一一个服务端(QQClient)和一个客户端(QQServer)和一个公共类(QQcomman)

  1. 服务端包含了:线程管理集合(ManageServerThread)、服务端启动主函数(ServerFrame)、链接客户端线程(ServerConnectClientThread)、服务端服务(ServerService)
  2. 客户端包含了:登陆界面(QQview)、工具类(Utility)链接服务端(ClientConnectServerThread)客户端服务(ClientService)管理客户端线程类(ManageClientThread)
  3. 公共类(QQcommon):Message(消息类)、MessageType(消息类型)、User(用户类)
  • message类
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)进行通信链接

  1. 用线程管理类的愿意是需要我们对所有的通信线程去管理
  2. 通信线程的run方法能够让我们和服务端或者客户端不断的进行通信

2.开始实现QQ登陆(Hashmap代替数据裤)

  1. 实现客户端的登陆说明(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;}//获得Socketpublic Socket getSocket(){return socket;}public void setSocket(Socket socket) {this.socket = socket;}@Overridepublic 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();//此时读取的信息是userObjectOutputStream 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.实现拉取在线用户的列表功能

  • 实现思路:
  1. 我们从客户端发送一个message消息给客户端
  2. 服务端接收到这个message消息判断这个message的类型返回列表内容
  3. 客户端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;}@Overridepublic 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)//构建新的messageString 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();}}}
}

思路:

  1. 是通过server端管理的ManageServerThread类中的hm表遍历得到链接服务端的所对应的线程用户名
  2. 把用户名转换成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;}@Overridepublic 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)//构建新的messageString 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;}//获得Socketpublic Socket getSocket() {return socket;}public void setSocket(Socket socket) {this.socket = socket;}@Overridepublic 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还没有关闭,而且在客户端只关闭了该对象的线程,这样的情况下服务端肯定会报错:

  1. 错误:线程退出了但是进程没有退出

  1. 错误:大量的IO错误(服务端)

解决思路:

  1. 在客户端发一条消息Message对服务端说我要退出了
  2. 服务端关闭该线程对应的服务端socket
  3. 此时客户端再进行退出

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.私聊功能的实现:

实现思路:

  1. 在客户端发送一个私聊类型的Message 给服务端
  2. 服务端接收到私聊类型的Message 把message转发给相应的getterID
  3. 客户端接收服务端发送过来的消息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);//对应的messageif (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.群发消息功能实现

实现思路:

  1. 在客户端发送一个群聊类型的Message 给服务端
  2. 服务端接收到群聊类型的Message 把message转发除了发送者之外的getterID
  3. 客户端接收服务端发送过来的消息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();//遍历hmwhile (iterator.hasNext()){//取出在线用户idString onlineUser= iterator.next().toString();if (!onlineUser.equals(message.getSender())) {//通过hm得到和message的发送者不一样的userID得到socketObjectOutputStream 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.实现发文件功能

实现思路:

  1. 先把文件用文件流的方式读取(FileInputStream),再把文件流读取的数据存储到message对象流里面
  2. 服务端接收对应的文件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 {@Overridepublic 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 发送messageHashMap<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很垃圾

JAVA实现QQ:实现文字聊天、QQ用户登录、拉取在线用户列表、无异常退出、私聊、发文件、下载文件、离线留言、服务端推送新闻等功能(后端无界面,Utilty源码在后面、)相关推荐

  1. springboot实现SSE服务端主动向客户端推送数据,java服务端向客户端推送数据,kotlin模拟客户端向服务端推送数据

    SSE服务端推送 服务器向浏览器推送信息,除了 WebSocket,还有一种方法:Server-Sent Events(以下简称 SSE).本文介绍它的用法. 在很多业务场景中,会涉及到服务端向客户端 ...

  2. Java 服务端推送消息有那么难吗?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 转自公众号:码农小胖哥 今天项目经理交给我一个开发任务.如果有人在前台下了订单就给后台仓库管 ...

  3. 麻将服务端架设linux,湖南房卡麻将客户端/服务器端完整源码及编译教程

    客户端源码是基于cocos2d-x 3.10版本开发的.代码完整可编译.本人在WINDOWS平台下成功编译了android包并正常运行.源码里面也有服务器的源码(C++),经过再次鉴定应该是配套的,服 ...

  4. netty tcp服务端主动断开客户端_「Netty核心技术」6-ChannelPipeline源码

    ChannelPipeline是Channelhandler的容器,它负责ChannelHandler的管理和事件拦截与调度. 土话: ChannelPipeline就是用来管理Channelhand ...

  5. 开源的微信商城,含小程序端,后台管理系统,服务器后端,附完整源码

    微信小程序商城 项目概述 一个完整的微信小程序商城,包含微信小程序,管理系统,服务端后台,项目预览如下: 微信小程序 -微信小程序包含主页.商品分类.商品详情.加购物车,微信授权登录,微信支付,个人中 ...

  6. Java服务端推送--SSE

    1.java代码: @GetMapping(value="/message", produces = "text/event-stream;charset=utf-8&q ...

  7. 短视频去水印多功能工具箱微信小程序源码下载支持多种流量主

    没错这是一款以去水印为主的一款多功能微信小程序源码 该小程序源码除了拥有去水印功能以外还拥有N款其它实用的功能 比如喝酒神器,短网址生成,历史上的今天等等如下: 短视频去水印(自带接口,速度非常快) ...

  8. 猜歌小游戏多功能组合微信小程序源码下载

    这是一款多功能游戏组合的一款小程序 比如猜歌,摇骰子,真心话大冒险等等 php7.2 mysql5.6 1.上微擎框架 2.将后台两个压缩包上传到addons目录下解压 创建小程序应用 3.后台设置一 ...

  9. jquery 背景特效实现_html5实现的仿网页版微信聊天界面效果源码

    码农那点事儿 关注我们,一起学习进步 这是一款基于html5实现的仿网页版微信聊天界面效果源码,可实现微信网页版聊天界面效果,在编辑框编辑文字之后按Ctrl+Enter键即可提交文字到聊天对话框上.整 ...

最新文章

  1. 逐!帧!揭!秘!终于能看清波士顿动力机器人的细节了
  2. Android 仿PhotoShop调色板应用(三) 主体界面绘制
  3. IOS 实现滚动文字
  4. C语言经典例97-输入字符写入文件
  5. [云炬创业基础笔记]第十章企业的利润计划测试3
  6. jsp页面格式化时间或数字
  7. 诗歌,一路走来...
  8. @JsonSerialize的使用
  9. 美国国家安全局硬盘固件入侵技术揭秘
  10. LightOJ 1336. Sigma Function
  11. SonarLint: Replace the type specification in this constructor call with the diamond operator (“<>“).
  12. 计算机用户名怎么注册,微软账号怎么注册 Microsoft帐户注册使用教程
  13. iPhone4S、iPod5代、iPad2、iPad3、mini1代完美绕激活工具Sliver发布全新6.0版,支持工厂激活FMI OFF
  14. PM notifier
  15. 有限公司如何设立董事会
  16. Android手机录制屏幕并转gif图
  17. 【Java设计模式】组合模式
  18. 如何“看懂”图片?谈出海企业的视觉识别体系搭建
  19. Android 实现一个计时器
  20. 扫地僧是怎样练成的,前辈学51单片机的感悟是怎么样的

热门文章

  1. 整车nvh培训入门 仿真 ,基于Hyperworks 掌握思路 细节
  2. 2019 年 Google 编程之夏活动报告
  3. ST_Intersects
  4. 夏季干燥口腔溃疡频发怎么办
  5. 最没灵魂的爬虫——Selenium 游戏信息的爬取与分析
  6. 现代密码学(五) 数论和密码学困难性假设
  7. 细谈Type-C、PD原理(二)
  8. uni-app项目实现用户注册密码前端页面加密
  9. Mangopi MQ-R:T113-s3编译Tina Linux系统(二)SDK目录
  10. 微信语音转换成mp3文件保存的简单详细步骤