【代码练习8】UDP协议实现局域网屏幕广播功能

来源:互联网 发布:故宫淘宝 月饼 编辑:程序博客网 时间:2024/05/18 00:33

老师服务端

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.*;import java.util.List;public class TeacherMain {    public static void main(String[] args) {        TeacherServer server = new TeacherServer();        server.start();    }}//创建老师服务端class TeacherServer{    //声明套接字变量    private DatagramSocket socket;    //声明测试自动化变量    private Robot robot;    /**     * 构造教师端服务器启动方法     */    public void start(){        try {            //根据ip和端口号指定套接字地址            InetSocketAddress addr = new InetSocketAddress("192.168.12.2",8888);            //创建数据包套接字,并绑定到本地套接字地址            socket = new DatagramSocket(addr);            //            robot = new Robot();            //死循环,连续广播一帧画面            for (;;){                //1.调用广播一帧画面的方法                broadcastOneScreen();            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     *1. 广播一帧画面的方法     */    private void broadcastOneScreen() {        //1.1抓图,调用抓取一帧画面的方法,获取返回的字节数组        byte[] frameData = captureOneScreen();        //1.2切图,调用切图的方法,并把切成的每个单元,放到一个集合中        List<FrameUnit> units = splitFrame(frameData);        //1.3发送帧单元集合        sendFrameUnits(units);    }    /**     * 1.1截屏,即抓取一帧画面的方法     *     */    private byte[] captureOneScreen() {        try {            //定义一个区域            Rectangle rect = new Rectangle(0,0,1366,768);            //获取从屏幕中读取的像素的图像            BufferedImage image = robot.createScreenCapture(rect);            //创建一个byte数组输出流            ByteArrayOutputStream baos = new ByteArrayOutputStream();            //调用ImageIO类的write方法,使用支持jpg格式的任意 ImageWriter 将图像image写入baos输出流中,返回值为boolean型。            ImageIO.write(image,"jpg",baos);            //返回字节数组输出流            return baos.toByteArray();        } catch (IOException e) {            e.printStackTrace();        }        return null;    }    /**     *1.2切图的方法,对一帧画面进行切割,生成FrameUnit集合     */    private List<FrameUnit> splitFrame(byte[] frameData) {        //创建一个集合        List<FrameUnit> units = new ArrayList<FrameUnit>();        //定义切割后每一块帧单元的长度        int unitLen = 63*1024;        //计算帧单元个数        int count = 0;        if (frameData.length % unitLen == 0){            count = frameData.length/unitLen;        }else{            count = frameData.length/unitLen + 1;        }        //先定义一个帧单元变量,并赋初始值为空        FrameUnit unit = null;        //定义一个记录当前时间的变量        long timestamp = System.currentTimeMillis();        //        for (int i = 0 ; i < count ;i++){            //创建一个帧单元            unit = new FrameUnit();            //设置该帧单元的时间标记。            unit.setTimestamp(timestamp);            //设置帧单元的个数。            unit.setCount(count);            //设置帧单元在一帧画面中的索引位置            unit.setIndex(i);            //定义帧单元字节数组缓冲区变量            byte[] unitData;            if(i !=(count-1)){                //当不是最后一块时,则字节数组的长度都等于60*1024                unitData = new byte[unitLen];            }            else{                //如果一帧画面的大小正好是帧单元的整数倍,则最后一块帧单元字节数组的长度也为60*1024,否则长度为余数                int remain = frameData.length % unitLen == 0 ? unitLen : frameData.length % unitLen;                unitData = new byte[remain];            }            //从一帧画面的字节数组frameData中,复制第i块帧单元的字节内容,到帧单元字节数组unitData中            System.arraycopy(frameData,i*unitLen,unitData,0,unitData.length);            //设置帧单元数据            unit.setUnitData(unitData);            //将帧单元放入帧单元集合中            units.add(unit);        }        return units;    }    /**     *1.3发送帧单元集合的方法     */    private void sendFrameUnits(List<FrameUnit> units) {        //循环发出帧单元集合中的每个帧单元        for (FrameUnit unit : units){            //1.3.1调用发送一个帧单元的方法            sendFrameUnit(unit);        }    }    /**     *1.3.1发送一个帧单元     */    private void sendFrameUnit(FrameUnit unit) {        try {            //1.3.1.1调用组包方法,组装成数据报包            DatagramPacket packet = popPacket(unit);            //从本地套接字地址发送数据报包            socket.send(packet);        } catch (IOException e) {            e.printStackTrace();        }    }    /**     *1.3.1.1组包     */    private DatagramPacket popPacket(FrameUnit unit) {        //初始化数据报包,报文格式为前8个字节存放时间标记,再1个字节存放这一帧图像被分割的帧单元个数,        //再1个字节存放该单元的位置索引,再4个字节存放帧单元内容的长度,最后存放帧单元的字节内容。        byte[] packData = new byte[8 + 1 +1 + 4 + unit.getUnitData().length];        //设置时间标记        byte[] timeStampBytes = DataUtil.longToByteArray(unit.getTimestamp());        //将时间标记添加到数据报包        System.arraycopy(timeStampBytes,0,packData,0,timeStampBytes.length);        //添加帧单元的个数        packData[8] = (byte)unit.getCount();        //添加帧单元的位置        packData[9] = (byte)unit.getIndex();        //帧单元的长度        byte[] dataLenBytes = DataUtil.intToByteArray(unit.getUnitData().length);        System.arraycopy(dataLenBytes,0,packData,10,dataLenBytes.length);        //帧单元内容        byte[] unitData = unit.getUnitData();        System.arraycopy(unitData,0,packData,14,unitData.length);        //构造套接字报包,加载报文字节数组        DatagramPacket pack = new DatagramPacket(packData,0,packData.length);        //设置要将此数据报发往的远程主机的套接字地址,因为是广播形式,所以设置接收端ip地址为255        pack.setSocketAddress(new InetSocketAddress("255.255.255.255",9999));        return pack;    }}

帧单元类

public class FrameUnit {    //帧单元的时间标记    private long timestamp;    //一帧画面被分割的帧单元个数    private int count;    //帧单元在一帧画面中的索引位置    private int index;    //帧单元的数据内容    private byte[] unitData;    public long getTimestamp() {        return timestamp;    }    public void setTimestamp(long timestamp) {        this.timestamp = timestamp;    }    public int getCount() {        return count;    }    public void setCount(int count) {        this.count = count;    }    public int getIndex() {        return index;    }    public void setIndex(int index) {        this.index = index;    }    public byte[] getUnitData() {        return unitData;    }    public void setUnitData(byte[] unitData) {        this.unitData = unitData;    }}

数据工具类

/** * 数据转换工具类 */public class DataUtil {    /**     * 整数转成字节数组,大位靠前     */    public static byte[] intToByteArray(int a) {        byte[] ba = new byte[4];        ba[0] = (byte) ((a >> 24));        ba[1] = (byte) ((a >> 16));        ba[2] = (byte) ((a >> 8));        ba[3] = (byte) ((a >> 0));        return ba;    }    /**     * 字节数组转成整数     */    public static int byteArrayToInt(byte[] ba) {        int i = (int) (((ba[0] & 0xFF) << 24) | ((ba[1] & 0xFF) << 16) | ((ba[2] & 0xFF) << 8) | (ba[3] & 0xFF));        return i;    }    public static long byteArrayToLong(byte[] bys) {        long l = 0;        for (int i = 0; i < 8; i++) {            long lon = (long) (bys[i] & 0xff) << (8 * i);            l = l | lon;        }        return l;    }    public static byte[] longToByteArray(long l) {        //long 8个字节,创建一个长度为8的字节数组        byte[] bys = new byte[8];        for (int i = 0; i < 8; i++) {            bys[i] = (byte) (l >> (i * 8));        }        return bys;    }}

学生客户端

import java.io.ByteArrayOutputStream;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetSocketAddress;import java.net.SocketException;import java.util.HashMap;import java.util.Map;public class StudentMain {    public static void main(String[] args) {        //构造一个学生端UI界面        StudentUI ui = new StudentUI();        //开启接收端线程        ReceiverThread r = new ReceiverThread(ui);        r.start();    }}/** * 创建学生接收端线程 */class ReceiverThread extends Thread{    //私有化数据报套接字变量    private DatagramSocket socket;    //私有化帧单元集合    private Map<Integer,FrameUnit> frameUnitMap;    //私有化学生端界面    private StudentUI ui;    //接收端方法    public ReceiverThread(StudentUI ui) {        try {            //创建接收端套接字ip地址和端口            InetSocketAddress addr =new InetSocketAddress("192.168.12.2",9999);            //创建数据报套接字,将其绑定到本地套接字地址。            socket = new DatagramSocket(addr);            //            frameUnitMap = new HashMap<Integer, FrameUnit>();            this.ui = ui;        } catch (SocketException e) {            e.printStackTrace();        }    }    public void run(){        try {            //创建一个缓冲区字节数组            byte[] buf = new byte[64 * 1024];            //创建数据报包,接收缓冲区字节数组            DatagramPacket pack = new DatagramPacket(buf,0,buf.length);            //死循环,接收数据报包            for (;;){                socket.receive(pack);                //1.从字节数组中解析出一个帧单元                FrameUnit unit = parseFrameUnit(buf);                //2.拼接帧单元                processFrameUnit(unit);            }        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 1.将字节数组解析成帧单元     */    private FrameUnit parseFrameUnit(byte[] buf) {        //定义一个帧单元        FrameUnit unit = new FrameUnit();        //时间标记        long timestamp = DataUtil.byteArrayToLong(buf);        unit.setTimestamp(timestamp);        //帧被分成的帧单元个数        unit.setCount(buf[8]);        //此帧单元所在的索引位置        unit.setIndex(buf[9]);        //帧单元的内容长度        byte[] unitLen = {buf[10],buf[11],buf[12],buf[13]};        int len = DataUtil.byteArrayToInt(unitLen);        //帧单元内容        byte[] unitData = new byte[len];        System.arraycopy(buf,14,unitData,0,len);        unit.setUnitData(unitData);        return unit;    }    /**     *2.拼接帧单元     */    private void processFrameUnit(FrameUnit unit) {        if (frameUnitMap.isEmpty()){            //往集合添加帧单元元素,位置索引作为键            frameUnitMap.put(unit.getIndex(),unit);        }else{            //获取集合里存在的帧单元的时间标识            long oldTime = frameUnitMap.values().iterator().next().getTimestamp();            //新获取的帧单元时间标识            long nowTime = unit.getTimestamp();            if (nowTime < oldTime){            }else if (nowTime == oldTime){                //如果时间标识一致,则把新获取的帧单元添加进帧单元集合                frameUnitMap.put(unit.getIndex(),unit);            }else {                //如果新获取帧单元的时间大于集合里的帧单元时间标识,则把集合清空,放入新获取的帧单元                frameUnitMap.clear();                frameUnitMap.put(unit.getIndex(),unit);            }        }        //判断一帧画面有没有收集完成        int count = frameUnitMap.values().iterator().next().getCount();        if (frameUnitMap.size() == count){            //2.1重组帧单元集合,返回一帧的字节数组            byte[] frameData = popOneScreen();            //2.2更新ui画面            ui.updateScreen(frameData);            //2.3清空集合            frameUnitMap.clear();        }    }    /**     *2.1重组帧单元集合,返回一帧的字节数组     */    private byte[] popOneScreen(){            try {                //帧单元集合里帧单元的个数                int count = frameUnitMap.size();                //字节数组输出流                ByteArrayOutputStream baos = new ByteArrayOutputStream();                for (int i = 0; i < count; i++) {                    //从集合按帧单元索引顺序获取帧单元                    FrameUnit unit = frameUnitMap.get(i);                    //将帧单元内容写入输出流                    baos.write(unit.getUnitData());                }                return baos.toByteArray();            } catch (IOException e) {                e.printStackTrace();            }            return null;    }}

学生端界面窗口

import javax.imageio.ImageIO;import javax.swing.*;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import java.awt.image.BufferedImage;import java.io.ByteArrayInputStream;/** * 学生端UI界面窗口 */public class StudentUI extends JFrame{    //私有化显示图像的标签    private JLabel lblIcon;    public StudentUI(){        init();    }    private void init(){        //设置窗体的标题        this.setTitle("学生窗口");        //设置窗体大小        this.setBounds(0,0,1366,768);        //设置布局管理器        this.setLayout(null);        //创建一个无图像并且其标题为空字符串的标签        lblIcon = new JLabel();        //设置标签的大小和相对位置,为了将接收到的画面全屏显示,设置标签大小和窗口大小相等        lblIcon.setBounds(0,0,1366,768);        //将标签组件添加到窗口        this.add(lblIcon);        //添加窗口状态侦听器,并创建一个接收窗口事件的适配器的内部类        this.addWindowListener(new WindowAdapter() {            //内部类里重写窗口关闭时的方法            public void windowClosing(WindowEvent e){                //java虚拟机异常终止                System.exit(-1);            }        });        //设置窗口为可见状态        this.setVisible(true);    }    /**     * 更新画面     */    public void updateScreen(byte[] frameData){        try{            //创建一个输入流            ByteArrayInputStream bais = new ByteArrayInputStream(frameData);            //从输入流中读取数据返回给图像数据缓冲区            BufferedImage image = ImageIO.read(bais);            lblIcon.setIcon(new ImageIcon(image));        }catch (Exception e){            e.printStackTrace();        }    }}