【代碼練習8】UDP協議實現局域網屏幕廣播功能

老師服務端

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();
        }

    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章