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

發(fā)送端點(diǎn)擊開(kāi)始按鈕開(kāi)始發(fā)送截圖

接收方點(diǎn)擊開(kāi)始,開(kāi)始接受數(shù)據(jù)

由于在本地上直接測(cè)試,所以會(huì)出現(xiàn)重疊。程序中使用了jna和platform的透明效果。