dsg_03通信

通信

    • TCP/IP
        • TCP/IP协议集的主要协议
    • OSI(开放系统互连参考模型)
    • TCP协议建立连接的三次握手
    • 通信的分类
    • UDP通信
        • send端
        • receive端
        • 设计报头
        • 案例(屏幕广播,红蜘蛛)
            • 需要掌握的其他技术
        • 代码实现
            • 教师端
            • 学生端
    • TCP通信
        • QQ案例
            • 设计报头
            • 需要掌握的其他技术
            • QQServer
            • QQClient

TCP/IP

  • TCP:传输控制协议
  • IP:网络协议

TCP/IP协议集的主要协议

协议 提供服务 相应OSI层
IP 数据报服务 3
ICMP 差错和控制 3
ARP 互联网地址 -> 物理地址 3
RARP 物理地址 -> 互联网地址 3
TCP 可靠流服务 4
FTP 文件传送 5~7
TELNET 终端仿真 5~7
DNS 域名 -> 互联网地址 5~7

OSI(开放系统互连参考模型)

OSI分为七层结构,分别是:

  • 物理层:规范有关传输介质的特性标准
  • 数据链路层:定义了在单个链路上如何传输数据:SPX
  • 网络层:对端到端的包传输进行定义,定义了能够标识所有结点的逻辑地址,定义了路由实现的方式和学习的方式,定义了如何将一个包分解成更小的包的分段方法:IP,IPX
  • 传输层:UDP(无连接,用于广播,无固定路由),TCP(面向连接,三次握手,身份识别)
  • 会话层:
  • 表示层:
  • 应用层:http,https,ftp,SMTP

TCP协议建立连接的三次握手

  1. A发送syn信号x给B
  2. B接收A的syn信号x,构造一个ack信号(x + 1),准备自己的syn信号y
  3. B向A发送x + 1和 y的信号
  4. A接收B的ack + syn信号,对B的身份进行认证,同时构造ack( y + 1)
  5. A发送 ack( y + 1)给B
  6. B认证A的身份

通信的分类

  • 单工:单项通信,只能收不能发
  • 双工:双向通信,既可以收也可以发
    • 半双工:同一时刻,只能收或者只能发
    • 全双工:同一时刻,既可以收也可以发

UDP通信

UPD通信一般用于屏幕广播,UDP通信的特点是发完就结束,不管接收端收不收到,每个发送的数据有64k上限,发送的包无序

send端

import java.io.ByteArrayInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

/**
 * Create Time: 2020.06.07 09:38
 * 发送端
 **/
public class Sender {
    public static void main(String[] args) throws Exception {
        //数据报套接字,指定发送端的发送端口9999
        DatagramSocket socket = new DatagramSocket(9999);
        String data = "6月6日0—24时,31个省(自治区、直辖市)和新疆生产建设兵团报告新增确诊病例6例,其中境外输入病例5例(陕西2例,天津1例,福建1例,广东1例),本土病例1例(在海南);无新增死亡病例;新增疑似病例2例,均为境外输入病例(均在上海)。当日新增治愈出院病例3例,解除医学观察的密切接触者633人,重症病例减少1例。境外输入现有确诊病例66例(无重症病例),现有疑似病例3例。累计确诊病例1776例,累计治愈出院病例1710例,无死亡病例。截至6月6日24时,据31个省(自治区、直辖市)和新疆生产建设兵团报告,现有确诊病例70例(无重症病例),累计治愈出院病例78332例,累计死亡病例4634例,累计报告确诊病例83036例,现有疑似病例3例。累计追踪到密切接触者746744人,尚在医学观察的密切接触者3389人。31个省(自治区、直辖市)和新疆生产建设兵团报告新增无症状感染者5例(境外输入4例);当日转为确诊病例1例;当日解除医学观察25例(境外输入2例);尚在医学观察无症状感染者236例(境外输入43例)。累计收到港澳台地区通报确诊病例1593例。其中,香港特别行政区1105例(出院1048例,死亡4例),澳门特别行政区45例(出院45例),台湾地区443例(出院429例,死亡7例)。";
        ByteArrayInputStream bis = new ByteArrayInputStream(data.getBytes());
        //构造数据缓冲区,形成数据报包
        byte[] buf = new byte[1024];
        int len = 0;
        //192.168.12.255    255指给192.168.12这个网段的所有ip进行广播发送
        InetSocketAddress address = new InetSocketAddress("localhost", 8888);
        while ((len = bis.read(buf)) != -1){
            //数据报包,参数:缓冲区,长度
            DatagramPacket packet = new DatagramPacket(buf,len);
            //数据报包中包含接收端的地址和端口
            packet.setSocketAddress(address);
            socket.send(packet);

        }
    }
}

receive端

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * Create Time: 2020.06.07 09:50
 * 接收方
 **/
public class Receiver {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(8888);
        //数据接收缓冲区
        byte[] bytes = new byte[1024];
        String str = "";
        while(true){
        	//接收方需要先创建一个空包去接收数据
            DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
            socket.receive(packet);
            int dataLen = packet.getLength();
            str = new String(bytes, 0, dataLen);
            System.out.println("收到了:"+str);
        }
    }
}

设计报头

报头需要的元素:

  • 帧的id,8个字节,可以用时间戳表示,一帧代表一组数据
  • 帧单元个数,1个字节
  • 帧单元编号,1个字节,编号是连续的,帧单元是一组数据被切割成若干个包,每个包就是一个帧单元,用编号来确定包的顺序
  • 帧单元数据长度,4个字节
  • 帧数据,最多60K

案例(屏幕广播,红蜘蛛)

需要掌握的其他技术

截屏:

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
	Robot robot = new Robot();
    //获取屏幕的分辨率
    int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    int height = Toolkit.getDefaultToolkit().getScreenSize().height;
    //截屏,参数:坐标+分辨率
    BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, width, height));
    //保存图片,参数:BufferedImage,图片格式,图片路径输出流
    ImageIO.write(image,"jpg",new FileOutputStream("d:/1.jpg"));

字节数组与长整形的转换:

	/**
     * 将长整形转换成字节数组
     * @param i
     */
    public static byte[] long2Bytes(long i){
        byte[] arr = new byte[8];
        arr[0] = (byte)i;
        arr[1] = (byte)(i >> 8);
        arr[2] = (byte)(i >> 16);
        arr[3] = (byte)(i >> 24);
        arr[4] = (byte)(i >> 32);
        arr[5] = (byte)(i >> 40);
        arr[6] = (byte)(i >> 48);
        arr[7] = (byte)(i >> 56);
        return arr;
    }

    /**
     * 将字节数组转换成长整形
     */
    public static long bytes2long(byte[] arr){
        long i0 = (long)(arr[0] & 0xFF);
        //这个long必须要放在里面,要先将其转为long再移动字节,不然是无法满足那么多位让你去移动的
        long i1 = ((long)(arr[1] & 0xFF) << 8);
        long i2 = ((long)(arr[2] & 0xFF) << 16);
        long i3 = ((long)(arr[3] & 0xFF) << 24);
        long i4 = ((long)(arr[4] & 0xFF) << 32);
        long i5 = ((long)(arr[5] & 0xFF) << 40);
        long i6 = ((long)(arr[6] & 0xFF) << 48);
        long i7 = ((long)(arr[7] & 0xFF) << 56);
        return i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7;
    }

压缩字节与解压缩字节:

	/**
     * 压缩字节
     */
    public static byte[] zipData(byte[] zipData){
        try{
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ZipOutputStream zos = new ZipOutputStream(baos);
            zos.putNextEntry(new ZipEntry("one"));
            zos.write(zipData);
            zos.close();
            return baos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解压缩
     */
    public static byte[] unzipData(byte[] rawData){
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(rawData);
            ZipInputStream zis = new ZipInputStream(bais);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            zis.getNextEntry();
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = zis.read(buf)) != -1){
                baos.write(buf,0,len);
            }
            zis.close();
            bais.close();
            baos.close();
            return baos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

代码实现

教师端
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Create Time: 2020.06.07 14:57
 *
 * 屏幕广播
 * 发送方
 **/
public class SenderPG {

    //每个帧单元的最大值60K
    private static int frame_UNIT_MAX = 60 * 1024;
    //截屏需要的机器人类
    private static Robot robot = null;
    //屏幕的分辨率
    private static  int width = 0;
    private static int height = 0;
    //UDP广播需要的连接
    private static DatagramSocket socket = null;

    static {
        try {
            robot = new Robot();
            //获取屏幕分辨率
            width = Toolkit.getDefaultToolkit().getScreenSize().width;
            height = Toolkit.getDefaultToolkit().getScreenSize().height;
           	//设置发送端的端口为8888
            socket = new DatagramSocket(8888);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        while (true){
            sendOneScreenData();
        }
    }

    /**
     * 发送一屛数据
     * 1. 截屏
     * 2. 切屏(切屏之前先压缩截屏数据,减小网络传输的压力)
     * 3. 组装UdpPackage (8 + 1 + 1 + 4 + n)
     * 4. 发送
     */
    private static void sendOneScreenData() {

        byte[] bytes = null;
        List<Map<String,Object>> maps = null;
        try {
            //1. 截屏
            bytes = captureScreen();

            //2. 切屏
            if(bytes != null && bytes.length > 0){
                //切屏之前先压缩字节,将网络传输的压力转移到cpu上
                maps = splitFrame(Util.zipData(bytes));
            }
            //发送
            if(maps != null && maps.size() > 0){
                sendUnits(maps);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送数据
     * @param maps
     */
    private static void sendUnits(List<Map<String, Object>> maps) {
        try {
			//客户端的地址是本地地址,端口号9999
            InetSocketAddress address = new InetSocketAddress("localhost", 9999);
            for(Map<String,Object> map:maps){
            	//组装帧单元数据到字节数组
                byte[] bytes = propFrameUnit(map);
                DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
                packet.setSocketAddress(address);
                socket.send(packet);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 组装帧单元数据到字节数组
     * @param map
     * @return
     */
    private static byte[] propFrameUnit(Map<String, Object> map) {
        byte[] bytes = new byte[14 + Integer.parseInt(BlankNull(map.get("frameLength")))];
        //framId
        byte[] frameIds = Util.long2Bytes((long) map.get("frameId"));
        System.arraycopy(frameIds,0,bytes,0,frameIds.length);
        //frameCount
        byte frameCount = (byte) map.get("frameCount");
        bytes[frameIds.length] = frameCount;
        //frameNo
        byte frameNo = (byte) map.get("frameNo");
        bytes[frameIds.length + 1] = frameNo;
        //frameLength
        byte[] frameLengths = Util.int2Bytes((int) map.get("frameLength"));
        System.arraycopy(frameLengths,0,bytes,frameIds.length + 2,frameLengths.length);
        //frameData
        byte[] frameData = (byte[]) map.get("frameData");
        System.arraycopy(frameData,0,bytes,frameIds.length + 2 + frameLengths.length,frameData.length);
        return bytes;
    }

    /**
     * 非空判断
     * @param obj
     * @return
     */
    private static String BlankNull(Object obj) {
        if(obj != null){
            return obj.toString();
        }else{
            return "";
        }
    }

    /**
     * 切屏
     * @param bytes
     * @return
     */
    private static List<Map<String, Object>> splitFrame(byte[] bytes) {
        List<Map<String, Object>> resultList = new ArrayList<>();
        long frameId = System.currentTimeMillis();
        //判断数据是否大于60K
        if(bytes.length > frame_UNIT_MAX){
            //切
            //求需要切除的个数
            byte count = (byte)(bytes.length / frame_UNIT_MAX);
            int ys = bytes.length % frame_UNIT_MAX;
            if( ys != 0) {
                count ++;
            }
            for(byte i = 0; i < count;i++){
                //map中包含:帧id(每一屏的数据为一帧,取当前的时间戳,8个字节),帧单元的数量(1个字节),帧单元的编号(1个字节),帧单元数据的大小(除了最后一个,前面的帧单元都是最大值60K),帧单元的数据
                Map<String, Object> map = new HashMap<>();
                map.put("frameId",frameId);
                map.put("frameCount",count);
                map.put("frameNo",i);
                if(i == (count - 1) && ys != 0){
                    map.put("frameLength", ys);
                    byte[] data = new byte[ys];
                    System.arraycopy(bytes,i*frame_UNIT_MAX,data,0,ys);
                    map.put("frameData",data);
                }else{
                    map.put("frameLength",frame_UNIT_MAX);
                    byte[] data = new byte[frame_UNIT_MAX];
                    System.arraycopy(bytes,i*frame_UNIT_MAX,data,0,frame_UNIT_MAX);
                    map.put("frameData",data);
                }
                resultList.add(map);
            }
        }else{
            //不切
            Map<String, Object> map = new HashMap<>();
            map.put("frameId",frameId);
            map.put("frameCount",1);
            map.put("frameNo",0);
            map.put("frameLength",bytes.length);
            map.put("frameData",bytes);
            resultList.add(map);
        }
        return resultList;
    }

    /**
     * 截屏
     * @return
     */
    private static byte[] captureScreen() throws IOException {
        BufferedImage image = robot.createScreenCapture(new Rectangle(0, 0, width, height));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image,"jpg",baos);
        return baos.toByteArray();
    }
学生端
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.*;

/**
 * Create Time: 2020.06.07 14:58
 *  * 屏幕广播
 * 接收方
 **/
//使用线程是为了防止接收端占用主线程导致线程阻塞阻碍其他项目的运行
public class ReceiverPG extends Thread {

    private DatagramSocket socket = null;

    private DatagramPacket packet = null;
	//一个帧单元数据的最大长度
    private final static int frame_UNIT_MAX = 60 * 1024;
	//帧单元缓冲区,数据最大长度+报头长度
    private byte[] buf = new byte[frame_UNIT_MAX + 14];

    public ReceiverPG() {
        try {
        	//设置接收端的端口为9999
            this.socket = new DatagramSocket(9999);
            this.packet = new DatagramPacket(buf,buf.length);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        receiver();
    }

    public void receiver(){
        Map<Integer, Map<String, Object>> resultMap = new HashMap<>();
        //记录当前获取的帧单元的id
        long currentFrameId = 0L;
        while (true){
            try {
                socket.receive(packet);
                //整合帧单元
                Map<String, Object> map  = propFrameUnit();
                long frameId = Long.parseLong(Util.BlankNull(map.get("frameId")));
                int frameCount = Integer.parseInt(Util.BlankNull(map.get("frameCount")));
                int frameNo = Integer.parseInt(Util.BlankNull(map.get("frameNo")));
                //判断当前接收的对象是否属于同一帧
                if( frameId == currentFrameId){
                    //是就存储
                    resultMap.put(frameNo,map);
                }else if(frameId > currentFrameId){
                 	//如果不是,取最新的帧,放弃原来的帧单元
                    currentFrameId = frameId;
                    resultMap.clear();
                    resultMap.put(frameNo,map);
                }
                //判断同一帧数据是否收集完毕,完毕则组装数据,还原截图
                if(resultMap.keySet().size() == frameCount){
                    //组装截屏数据
                    propData(resultMap);
                    resultMap.clear();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 组装图片数据
     * @param maps
     * @return
     */
    private BufferedImage propData(Map<Integer, Map<String, Object>> maps) {
        BufferedImage bufferedImage = null;
        try {
            String pictureName = (new Date()).getTime() + ".jpg";
            System.out.println("保存一张图片"+pictureName);
            FileOutputStream fos = new FileOutputStream("D:\\aa\\pg\\" + pictureName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            for(int i = 0; i < maps.size();i++){
                byte[] bytes = (byte[]) maps.get(i).get("frameData");
                baos.write(bytes);
            }
            //拿到截屏字节,发送端压缩过,所以接收端要解压
            fos.write(Util.unzipData(baos.toByteArray()));
            baos.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bufferedImage;
    }

    /**
     * 整合帧单元
     * @return
     */
    private Map<String, Object> propFrameUnit() {
        Map<String, Object> map = new HashMap<>();
        byte[] frameIds = new byte[8];
        System.arraycopy(buf,0,frameIds,0,8);
        long frameId = Util.bytes2long(frameIds);
        byte frameCount = buf[8];
        byte frameNo = buf[9];
        byte[] frameLengths = new byte[4];
        System.arraycopy(buf,10,frameLengths,0,4);
        int frameLength = Util.Bytes2int(frameLengths);
        byte[] frameData = new byte[frameLength];
        System.arraycopy(buf,14,frameData,0,frameLength);
        map.put("frameId",frameId);
        map.put("frameCount",frameCount);
        map.put("frameNo",frameNo);
        map.put("frameLength",frameLength);
        map.put("frameData",frameData);
        return map;
    }

    public static void main(String[] args) {
        new ReceiverPG().start();
    }
}

TCP通信

使用TCP通信最常见的应用就是QQ

QQ案例

设计报头

报头需要的元素:

  • 消息类型,4个字节,(单发、群发、刷新好友列表)
  • 接收方信息长度
  • 接收方信息
  • 消息长度,4个字节
  • 消内容
  • 发送方信息长度
  • 发送方信息
需要掌握的其他技术

java串行化
java串行化是指将一个对象拆分为字节数组,然后转为流的形式,可以拆分为字节数组的对象必须实现 Serializable接口

	 /**
     * 串行化对象
     */
    public static byte[] serializeObject(Serializable src){
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(src);
            oos.close();
            baos.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 反串行化
     */
    public static Serializable deSerializeObject(byte[] bytes){
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            Serializable s = (Serializable) ois.readObject();
            ois.close();
            bais.close();
            return s;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
QQServer
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Create Time: 2020.06.14 09:26
 *
 * QQ服务器
 **/
public class QQServer {

    //维护所有的客户端集合
    //key是ip+端口号
    private static Map<String,Socket> socketMap = new HashMap<>();

    public static Map<String, Socket> getSocketMap() {
        return socketMap;
    }

    public static void main(String[] args) {
        try {
        	//设置QQ服务器端口为8888
            ServerSocket ss = new ServerSocket(8888);
            while (true){
                //阻塞
                Socket socket = ss.accept();
                InetSocketAddress isa = (InetSocketAddress)socket.getRemoteSocketAddress();
                String ip = isa.getAddress().getHostAddress();
                int port = isa.getPort();
                String key = ip + ":" + port;
                //将上线的客户端都维护到这个集合中去
                socketMap.put(key,socket);
                //处理客户端来的消息,这里必须要开分线程,因为客户端不可能只有一个
                //参数当前发送消息的客户端的socket和他对应的key
                (new CommThread(socket,key)).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //服务器向客户端发送消息
    public static void sendMessage(Socket socket, byte[] bytes){
        try {
            OutputStream os = socket.getOutputStream();
            os.write(bytes);
            //清理
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.InputStream;
import java.net.Socket;

/**
 * Create Time: 2020.06.14 09:52
 *
 **/
public class CommThread extends Thread {

    private Socket socket = null;

    private String sender = null;

    public CommThread(Socket socket,String sender){
        this.socket = socket;
        this.sender = sender;
    }

    @Override
    public void run() {
        try {
            while (true){
                InputStream is = socket.getInputStream();
                //解析客户端发送过来的消息并发送
                MessageFactory.serverParseMessage(is,sender);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

	/**
     * 服务器解析消息
     * @param is
     * is中包含消息类型、(接收方信息长度、接收方信息)、消息长度、消息内容、发送方信息长度、发送方信息
     */
    public static void serverParseMessage(InputStream is,String sender){
        try {
            if(is.available() > 0){
                byte[] xxBytes = new byte[is.available()];
                is.read(xxBytes);
                byte[] senderBytes = sender.getBytes();
                byte[] senderLengthBytes = Util.int2Bytes(senderBytes.length);
                //读取消息类型
                byte[] xxlxBytes = new byte[4];
                System.arraycopy(xxBytes,0,xxlxBytes,0,4);
                int xxlx = Util.Bytes2int(xxlxBytes);
                switch (xxlx){
                    /**
                     * 0:私聊 需要解析接收端信息,以便发给接收端,并同时附加发送端信息
                     * 1:群聊 ,附加发送方信息,遍历好友列表,并发送
                     * 2:刷新好友列表 //上线和下线
                     */
                    case 0: {
                        //读取接收端用户信息的长度
                        byte[] receiverYHXXCDBytes = new byte[4];
                        System.arraycopy(xxBytes,4,receiverYHXXCDBytes,0,4);
                        int receiverYHXXLength = Util.Bytes2int(receiverYHXXCDBytes);
                        //读取接收端用户信息
                        byte[] receiverBytes = new byte[receiverYHXXLength];
                        System.arraycopy(xxBytes,8,receiverBytes,0,receiverYHXXLength);
                        String receiver = new String(receiverBytes, 0, receiverYHXXLength);
                        Socket socket = QQServer.getSocketMap().get(receiver);
                        //附加发送方位发送方信息

                        byte[] bytes = new byte[xxBytes.length + 4 + senderBytes.length];
                        System.arraycopy(xxBytes,0,bytes,0,xxBytes.length);
                        System.arraycopy(senderLengthBytes,0,bytes,xxBytes.length,4);
                        System.arraycopy(senderBytes,0,bytes,xxBytes.length + 4,senderBytes.length);



                        if (socket != null) {
                            QQServer.sendMessage(socket, bytes);
                        }
                        break;
                    }
                    case 1: {
                        byte[] bytes = new byte[xxBytes.length + 4 + senderBytes.length];
                        System.arraycopy(xxBytes,0,bytes,0,xxBytes.length);
                        System.arraycopy(senderLengthBytes,0,bytes,xxBytes.length,4);
                        System.arraycopy(senderBytes,0,bytes,xxBytes.length + 4,senderBytes.length);

                        for (Socket client : QQServer.getSocketMap().values()) {
                            QQServer.sendMessage(client, bytes);
                        }
                        break;
                    }
                    case 2:{
                        List<String> clients = new ArrayList<>(QQServer.getSocketMap().keySet());
                        byte[] bytes = Util.serializeObject((Serializable) clients);
                        //将消息类型与好友列表组装起来
                        byte[] message = new byte[4 + bytes.length];
                        System.arraycopy(xxlxBytes,0,message,0,4);
                        System.arraycopy(bytes,0,message,4,bytes.length);
                        for(Socket client:QQServer.getSocketMap().values()){
                            QQServer.sendMessage(client,message);
                        }
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
QQClient
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * Create Time: 2020.06.14 09:26
 *
 * QQ客户端
 **/
public class QQClient {

    //好友列表
    private static List<String> friends = null;

    public static void setFriends(List<String> friends) {
        QQClient.friends = friends;
    }

    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 8888);
        //获取服务端发送过来的消息,这里也必须开启线程,因为客户端需要发送消息,不开线程这里就会堵塞,客户端无法发送消息
        new Thread(new CommThread(socket)).start();

        Map<String, String> map = new HashMap<>();
        //上线,刷新好友列表
        map.put("xxlx","2");
        byte[] bytes = MessageFactory.fzxx(map);
        //向服务器发送消息
        sendMessage(socket,bytes);

        //请选择发送方式
        System.out.println("0:单发;1:群发");
        Scanner scanner = new Scanner(System.in);
        String xxlx = scanner.nextLine();
        String fsdx = "";
        if(xxlx.equals("0")){
            //单发指定发送对象
            System.out.println("请选择发送对象");
            String fsdxIndex = scanner.nextLine();
            fsdx = friends.get(Integer.parseInt(fsdxIndex) - 1);
        }
        //输入消息
        System.out.println("请输入发送消息");
        String message = scanner.nextLine();
        //封装消息为字节数组
        map.put("xxlx",xxlx);
        map.put("fsdx",fsdx);
        map.put("message",message);
        bytes = MessageFactory.fzxx(map);
        //发送消息
        sendMessage(socket,bytes);

    }

    //客户端向服务器发送消息
    public static void sendMessage(Socket socket, byte[] bytes){
        try {
            OutputStream os = socket.getOutputStream();
            os.write(bytes);
            //清理
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.InputStream;
import java.net.Socket;

/**
 * Create Time: 2020.06.14 09:52
 *
 **/
public class CommThread extends Thread {

    private Socket socket = null;

    public CommThread(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            while (true){
                InputStream is = socket.getInputStream();
                //解析服务端发送过来的消息
                MessageFactory.clientParseMessage(is);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
	/**
     * 客户端解析消息
     * @param is
     * @return  map中包含消息类型、(接收方信息长度、接收方信息)、消息长度、消息内容、发送方信息长度、发送方信息
     */
    public static void clientParseMessage(InputStream is){
        try {
            if(is.available() > 0){
                byte[] xxBytes = new byte[is.available()];
                is.read(xxBytes);
                //读取消息类型
                byte[] xxlxBytes = new byte[4];
                System.arraycopy(xxBytes,0,xxlxBytes,0,4);
                int xxlx = Util.Bytes2int(xxlxBytes);
                switch (xxlx){
                    //0:私聊
                    case 0:{
                        //读取接收端用户信息的长度
                        byte[] receiverYHXXCDBytes = new byte[4];
                        System.arraycopy(xxBytes,4,receiverYHXXCDBytes,0,4);
                        int receiverLength = Util.Bytes2int(receiverYHXXCDBytes);
                        //读取消息
                        byte[] messageLengthBytes = new byte[4];
                        System.arraycopy(xxBytes,8 + receiverLength,messageLengthBytes,0,4);
                        int messageLength = Util.Bytes2int(messageLengthBytes);
                        byte[] messageBytes = new byte[messageLength];
                        System.arraycopy(xxBytes,12 + receiverLength,messageBytes,0,messageLength);
                        String message = new String(messageBytes, 0, messageLength);
                        System.arraycopy(xxBytes,12 + receiverLength,messageBytes,0,messageLength);
                        //读取发送方信息
                        byte[] senderLengthBytes = new byte[4];
                        System.arraycopy(xxBytes,12 + receiverLength + messageLength ,senderLengthBytes,0,4);
                        int senderLength = Util.Bytes2int(senderLengthBytes);
                        byte[] senderBytes = new byte[senderLength];
                        System.arraycopy(xxBytes,16 + receiverLength + messageLength,senderBytes,0,senderLength);
                        String sender = new String(senderBytes,0,senderLength);
                        System.out.println(sender + "发送消息:" + message);
                        break;
                    }
                    //1:群聊
                    case 1:{
                        //读取消息
                        byte[] messageLengthBytes = new byte[4];
                        System.arraycopy(xxBytes,4 ,messageLengthBytes,0,4);
                        int messageLength = Util.Bytes2int(messageLengthBytes);
                        byte[] messageBytes = new byte[messageLength];
                        System.arraycopy(xxBytes,8,messageBytes,0,messageLength);
                        String message = new String(messageBytes, 0, messageLength);
                        System.arraycopy(xxBytes,8 ,messageBytes,0,messageLength);
                        //读取发送方信息
                        byte[] senderLengthBytes = new byte[4];
                        System.arraycopy(xxBytes,8 + messageLength ,senderLengthBytes,0,4);
                        int senderLength = Util.Bytes2int(senderLengthBytes);
                        byte[] senderBytes = new byte[senderLength];
                        System.arraycopy(xxBytes,12 + messageLength ,senderBytes,0,senderLength);
                        String sender = new String(senderBytes,0,senderLength);
                        System.out.println(sender + "发送消息:" + message);
                        break;
                    }

                    //2:刷新好友列表
                    case 2:{
                        byte[] friendBytes = new byte[xxBytes.length - 4];
                        System.arraycopy(xxBytes,4,friendBytes,0,friendBytes.length);
                        List<String> hylbList = (List<String>)Util.deSerializeObject(friendBytes);
                        QQClient.setFriends(hylbList);
                        for (int i = 0; i < hylbList.size();i++) {
                            System.out.print((i + 1) + ". "+hylbList.get(i)+";");
                        }
                        System.out.println();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端封装消息
     */
    public static byte[] fzxx(Map<String,String> message){
        int xxlx = Integer.parseInt(message.get("xxlx"));
        byte[] xxlxBytes = Util.int2Bytes(xxlx);
        switch (xxlx){
            //0:私聊
            case 0:{
                byte[] fsdx = message.get("fsdx").getBytes();
                byte[] fsdxLengthBytes = Util.int2Bytes(fsdx.length);
                byte[] messages = message.get("message").getBytes();
                byte[] messageLengthBytes = Util.int2Bytes(messages.length);
                byte[] bytes = new byte[4 + 4 + fsdx.length + 4 + messages.length];
                //存入消息类型
                System.arraycopy(xxlxBytes,0,bytes,0,4);
                //存入接收方信息长度
                System.arraycopy(fsdxLengthBytes,0,bytes,4,4);
                //存入接收方信息
                System.arraycopy(fsdx,0,bytes,8,fsdx.length);
                //存入消息长度
                System.arraycopy(messageLengthBytes,0,bytes,8 + fsdx.length , messageLengthBytes.length);
                //存入消息
                System.arraycopy(messages,0,bytes,12 + fsdx.length,messages.length);
                return bytes;
            }
            //1:群聊
            case 1:{
                byte[] messages = message.get("message").getBytes();
                byte[] messageLengthBytes = Util.int2Bytes(messages.length);
                byte[] bytes = new byte[4 + 4 + messages.length];
                //存入消息类型
                System.arraycopy(xxlxBytes,0,bytes,0,4);
                //存入消息长度
                System.arraycopy(messageLengthBytes,0,bytes,4  , messageLengthBytes.length);
                //存入消息
                System.arraycopy(messages,0,bytes,8,messages.length);
                return bytes;
            }

            //2:刷新好友列表
            case 2:{
                return xxlxBytes;
            }
        }
        return null;
    }

你可能感兴趣的:(java-消息中间件)