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的透明效果。