老師服務端
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();
}
}
}