基于UDP的網絡通信之屏幕共享
UDP是一種用途廣泛的網絡傳輸協議,發送方只管發送數據出去,而不管是否能夠送達。
應用范圍:有時候因為網絡問題,接收方可能會丟失部分數據,但是并不影響程序的功能。例如視頻直播的時候有一些數據丟失了,最多就是卡頓一下,并不會造成功能很大的影響。
對于發送者而言,需要有一個發送者的地址與端口,也需要知道要發到哪個地址的哪個端口。同時還需要一個socket傳送數據。
在這里,可以將他們形象的比喻成郵政系統。
發送者就是寄件人,接收者就是收件人,而傳遞著就是郵遞員。
- // 創建一個發送者(發件人)
- SocketAddress sender = new InetSocketAddress("127.0.0.1", 912);
- // 創建一個接收者(收件人)
- SocketAddress receiver = new InetSocketAddress("127.0.0.1", 913);
- // 創建一個傳遞者(郵遞員)
- DatagramSocket socket = new DatagramSocket(sender);
而對于寄件人而言,他需要將要寄的東西用一個包裝裝好,也就是包裹一樣。然后再交給郵遞員送出去。
- byte[] msg="Hello!".getBytes();
- DatagramPacket m = new DatagramPacket(msg, msg.length, receiver);
- socket.send(m);
對于接收者而言,他需要知道去哪里取數據,郵遞員是誰,收到了一個包裹。
- // 創建接收對象(收件人)
- SocketAddress receiver = new InetSocketAddress("127.0.0.1", 913);
- // 得到消息接收的socket(郵遞員)
- DatagramSocket socket = new DatagramSocket(receiver);
- // 定義好包裹
- DatagramPacket data = new DatagramPacket(buf, buf.length);
- // 用socket將數據包裹接收進來
- socket.receive(data);
這其中就需要定義一些協議。
UDP出了上述一對一共享,還可以以組播的方式共享數據,即一對多。
這里以簡單的屏幕分享為例
首先,要明確我們的目的是需要將某臺計算機的屏幕分享給其他人。
也就是將計算機屏幕截圖,再使用局域網組播。
由于每次發送的數組不能過大,所以截取屏幕得到的圖片需要分多次發送出去,等客戶端接收到了再拼成原圖。所以需要一個信息頭來保存圖片的基本信息以便于客戶端收到之后能順利拼回原圖。
關鍵在于如何定義這個信息頭,在接收方我們需要知道發送端傳給我們的圖片是分多少次發送過來的,也要知道總共有多少個字節,還要判斷是不是因為網絡原因有部分數據被丟棄了,那樣的話自然就無法還原數據了。
在這里,我采用的方法是:
信息頭定義如下:
第一個字節為類型,暫時用0表示圖片
第二個字節為數據組數,意思是這張圖片分成了多少次發出去,在客戶端需要收到多少才能pin回來
第三個字節為隨機的一個記號,用來告訴客戶端是否數據丟失了。如果有數據丟失,
則應該丟棄相關的所有數據,不能拼回原圖,則跳過這一幀。
第四個字節為實際要傳輸的數據長度的位數。比如實際上是1234byte,則這個值是4
接下來的n個為長度信息,比如:data[4] = 1;data[5] = 2;data[6] = 3;這就表示長度為1234
每一次都發10000個實際字節數據
加上10個左右的頭部信息。所以每個數組長度都是10010
客戶端接收到消息之后,就要判斷是不是有數據丟失。沒有的話就會拼回原圖并顯示
接收到了這次的數據之后,如果發現前一組丟了部分數據,那么就要將前一組數據全部清空,然后繼續接收#p#
部分代碼如下:
發送者:
- package V0913;
- import java.awt.Dimension;
- import java.awt.Rectangle;
- import java.awt.Robot;
- import java.awt.Toolkit;
- import java.awt.image.BufferedImage;
- import java.io.BufferedOutputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.InetAddress;
- import java.net.MulticastSocket;
- import java.net.UnknownHostException;
- import java.util.ArrayList;
- import javax.imageio.ImageIO;
- /**
- * 發送數據的線程
- *
- * @author 斌
- * @2014年9月13日
- */
- public class SendThread extends Thread {
- InetAddress inetAdd;
- MulticastSocket cast;
- byte biaoji = 0;
- public void run() {
- try {
- // 創建組播地址
- inetAdd = InetAddress.getByName("230.0.0.1");
- // 創建組播的Socket對象
- cast = new MulticastSocket();
- // 截屏
- Robot robot = new Robot();
- Dimension dis = Toolkit.getDefaultToolkit().getScreenSize();
- BufferedImage image;
- while (Login.connected) {
- // 得到屏幕截圖數據
- image = robot.createScreenCapture(new Rectangle(dis));
- // 將圖片轉換為byte數組
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ImageIO.write(image, "png", baos);
- byte[] data = baos.toByteArray();
- // new BufferedOutputStream(new FileOutputStream(new File(
- // "data.txt"))).write(data);
- send(data);
- // // 數據丟失的模擬
- // byte dt[] = { 0, 122, 2, 1, 4, 1, 2, 3, 4 };
- // DatagramPacket packet = new DatagramPacket(dt, dt.length,
- // inetAdd, 9876);
- //
- // // 將其發送
- // try {
- // cast.send(packet);
- // } catch (IOException e) {
- // e.printStackTrace();
- // }
- if (biaoji < 100) {
- biaoji++;
- } else {
- biaoji = 0;
- }
- Thread.sleep(30);
- }
- } catch (UnknownHostException e) {
- e.printStackTrace();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public void send(byte[] data) {
- // 將data數組拆分發送
- long length = data.length;// 數據總長度
- ArrayList<byte[]> list = new ArrayList<byte[]>();
- byte size = (byte) (length / 10000 + 1);// 這張圖片有多少組數據數據
- int j = 0;
- while (j < size) {
- byte[] dataTemp;
- int temp;
- if (j < size - 1) {
- temp = 10000;
- } else {
- temp = (int) (length % 10000);// 最后一次需要的大小
- }
- dataTemp = new byte[10010];
- dataTemp[0] = 0;// 類型
- dataTemp[1] = biaoji;// 記號,接收方用來判斷是不是丟了數據
- dataTemp[2] = size;// 總共有多少組數據需要接收
- dataTemp[3] = getLength(temp);// 數據大小占了數組幾位
- for (int i = 0; i < dataTemp[3]; i++) {
- // 將數據大小保存起來
- dataTemp[i + 4] = getElem(temp, i);
- }
- // 每次存10000個字節數據
- for (int i = 0; i < temp; i++) {
- dataTemp[i + 4 + dataTemp[3]] = data[j * 10000 + i];
- }
- list.add(dataTemp);
- j++;
- }
- // 循環發送數據
- for (int i = 0; i < list.size(); i++) {
- // 將其打包
- DatagramPacket packet = new DatagramPacket(list.get(i),
- list.get(i).length, inetAdd, 9876);
- // 將其發送
- try {
- cast.send(packet);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- System.out.println("發送了一張圖片");
- }
- /**
- * 獲得一個long的位數
- *
- * @param num
- * @return
- */
- private byte getLength(long num) {
- byte count = 1;
- while (num / 10 != 0) {
- num /= 10;
- count++;
- }
- return count;
- }
- /**
- * 獲得num中第index位的數字,以0開始計算起始位置
- *
- * @param num
- * @param index
- * @return
- */
- private byte getElem(long num, int index) {
- int length = getLength(num);
- // 最后一個
- if ((index + 1) == length) {
- return (byte) (num % 10);
- }
- long count = num;
- for (int i = 0; i < length - index - 1; i++) {
- countcount = count / 10;
- }
- countcount = count % 10;
- return (byte) count;
- }
- }
#p#接收者:
- package V0913;
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.InetAddress;
- import java.net.MulticastSocket;
- import java.util.ArrayList;
- import javax.swing.ImageIcon;
- /**
- * 接收數據的線程
- *
- * @author 斌
- * @2014年9月13日
- */
- public class ReceiveThread extends Thread {
- private MulticastSocket cast;
- public void run() {
- try {
- // 創建窗口
- MainUI mu = new MainUI();
- // 創建socket用來接收數據
- cast = new MulticastSocket(9876);
- // 定義組播地址
- InetAddress inetAdd = InetAddress.getByName("230.0.0.1");
- // 將socket加入該地址組
- cast.joinGroup(inetAdd);
- System.out.println("stratServer");
- while (mu.connect) {
- ImageIcon icon = receive();
- // 顯示在窗口上
- if (icon != null) {
- mu.label.setIcon(icon);
- mu.center.repaint();
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public ImageIcon receive() throws IOException {
- ArrayList<byte[]> list = new ArrayList<byte[]>();
- // 創建數據包對象
- byte dataTemp[] = new byte[10010];
- long alllength = 0;
- DatagramPacket packet = new DatagramPacket(dataTemp, dataTemp.length);
- // 接收數據包
- cast.receive(packet);
- // 提取頭部信息進行解析,第0個為類型,判斷是否為0,第1個為記號,第2個為多少個數據需要接受,第3個為長度的長度,之后接著長度信息,之后再是數據
- int biaoji = dataTemp[1];
- byte size = dataTemp[2];
- alllength += getLength(dataTemp);
- list.add(dealData(dataTemp));
- for (int i = 1; i < size; i++) {
- packet = new DatagramPacket(dataTemp, dataTemp.length);
- // 接收數據包
- cast.receive(packet);
- if (biaoji == dataTemp[1]) {
- list.add(dealData(dataTemp));
- alllength += getLength(dataTemp);
- } else {
- // ***************************************************************************************//
- System.out.println("有數據丟了");
- // 初始化數據
- list.clear();
- biaoji = dataTemp[1];
- size = dataTemp[2];
- i = 0;
- list.add(dealData(dataTemp));
- alllength = getLength(dataTemp);
- }
- }
- // 將list中的數組全部加到data中去
- byte data[] = new byte[(int) alllength];
- for (int i = 0; i < list.size(); i++) {
- byte t[] = list.get(i);
- for (int j = 0; j < t.length; j++) {
- data[i * 10000 + j] = t[j];
- }
- }
- // new BufferedOutputStream(new FileOutputStream(new File("data.txt")))
- // .write(data);
- // 將數據還原成圖像
- ImageIcon icon = new ImageIcon(data);
- return icon;
- }
- /**
- * 處理收到的數據,得到真正需要的數據
- *
- * @param dataTemp
- * @return
- */
- public byte[] dealData(byte dataTemp[]) {
- int length = getLength(dataTemp);// 一般為10000
- byte[] data = new byte[length];
- // 得到了數據長度,之后開始讀數據
- for (int i = 0; i < length; i++) {
- data[i] = dataTemp[i + dataTemp[3] + 4];
- }
- return data;
- }
- /**
- * 獲得實際需要數據的長度
- *
- * @param dataTemp
- * @return
- */
- public int getLength(byte dataTemp[]) {
- byte temp[] = new byte[dataTemp[3]];
- for (int i = 0; i < dataTemp[3]; i++) {
- temp[i] = dataTemp[i + 4];
- }
- return getNum(temp);
- }
- /**
- * 根據byte數組合成一個數字 如:{1,2,3,4}合成之后為1234
- *
- * @param data
- * @return
- */
- public int getNum(byte data[]) {
- int temp = 0;
- for (int i = 0; i < data.length; i++) {
- temp += data[i] * Math.pow(10, data.length - i - 1);
- }
- return temp;
- }
- }
#p#運行效果圖如下:

發送端點擊開始按鈕開始發送截圖

接收方點擊開始,開始接受數據

由于在本地上直接測試,所以會出現重疊。程序中使用了jna和platform的透明效果。