Java UDP 编程简介.

来源:互联网 发布:淘宝怎么装修手机店铺 编辑:程序博客网 时间:2024/05/17 23:26


一.UDP 协议简介

UPD协议 是常见的 网络传输协议之一, 当然另1个是TCP协议.


UPD协议 是一种不靠的协议.

是因为发送方不会关心接受方的状态, 直接向接收方发送数据包, 也就是说这个数据包有可能因为对方不在线而丢失.




二. Java UDP编程的几个关键的类(或数组).

1. Byte[], 字节数组, 在UDP协议中, 任何数据(图片, 字符..) 都必须转化成ByteArray.

   字节数组相当与要发送或接受的货物.


2. DatagramPacket, 字节数组不能网络在传输,  就如货物不能直接在海上运输一样.

   DatagramPacket 就相当于1个容器或集装箱, 我们可以把字节数组放在这个容器, 然后在容器上贴上关键的信息.(目标地址和端口号)

   就如我们call顺丰必须要事先把物品打包, 写上地点和收件人一样, 道理很简单.

   DatagramPacket 就是所谓的数据包, 把字节数组放入DatagramPacket的行为就叫数据打包.


3. DatagramSocket,  这个类相当与1个码头, 码头与发送和接受集装箱(DatagramPacket), 但是码头不会直接发送和接受货物(ByteArray).

  

4. InetAddress, 这个是java中表示ip地址的1个类.

   它没有提供公开的构造函数, 但是它提供静态方法getByName(String ip)来返回1个静态对象(singleTone).

   一般我们会将1个InetAddress对象贴到DatagramPacket对象上.


5. ByteAarryInputStream, ByteArrayOutputStream, DataInputStream, DataOutputStream...

   这个些流是把各种类型的数据转换为字节数组的工具.





也就是说.

a. 所有的要发送的数据都必须转换成字节数组.

b. 所有要发送的字节数组都必须打包(DatagramPacket), 并贴上目标ip, 和端口.

c. 所有的DatagramPacket都必须经过DatagramSocket来发送或接受.


三. DatagramSocket 简介

上面说过了,  这个类相当于1个发送和接受集装箱(DatagramPacket)的码头.


下面介绍若干个它的关键方法.


3.1 DatagramSocket()

首先介绍构造方法, 这个构造方法是不带任何参数的(ip, 端口).

这个构造方法一般用于构造1个发送端的码头,  它可以向各个目标ip和端口发送数据包, 前提是目标ip和端口都附加在数据包(DatagramPacket).


3.2 DatagramSocket(int port)

构造1个DatagramSocket对象同时绑定端口.

这个方法一般用于构造接收数据包(DatagramPacket)的码头. 表示只接受指定端口的数据包. 


原理很简单, 例如你发邮件, 可以向不同邮箱发送地址, 但是你只会接受发给你自己的邮件.



3.3 send(DatagramPacket dp)

将1个数据包发送出去, 发送哪里? 取决于数据包上面的ip和端口信息.

注意, 参数dp必须具有目标ip, 端口信息, 否则回throw Exception.


3.4 receive(DatagramPacket dp)

用1个数据包(集装箱)来接受发送过来的数据,  也就是dp这个参数的值会被修改.

这个方法一旦被执行, 程序会等待(hold 住), 知道收到1个数据包为止.


3.5 close()

关闭这个码头, DatagramSocket使用完后可以执行close()来释放资源, 注意一旦close(), 就不能重新打开, 就如stream一样.



四. DatagramPacket 简介

上面提到过了, DatagramPacket 实际上是1个存放字节数组的容器.  并可以贴上ip和端口信息用于发送.


下面也介绍若干个关键方法.



4.1 DatagramPacket(byte[] buf, int length)

首先也是构造方法, DatagramPacket并没有提供参数为空的构造方法, 而且各个构造方法必须有1个字节数组参数.

这个字节数组就是DatagramPacket的内核数组.


也就是说, 我们不能构造1个为"空"的DatagramPacket.



4.2 setData(byte[] buf, int offset, int length),

但是我们可以用别的字节数组来替换掉原来的内核字节数组, 就是用这个setData方法了, 这个方法一般用于发送端, 在编程中, 我们可以利用同1个DatagramPacket对象来不断发送不同的数据.


int offset, int length这个参数是指定 byteArray中的有效数据起始位置和长度.


4.3 setAddress(InetAddress iaddr)

为包裹贴上目标ip地址, 一般用于发送端.


注意参数是1个对象, 也就是我们必须实现构造1个InetAddress对象, 上面有提过.


4.4 setPort(int port)

为包裹贴上端口信息, 例如发送包裹到太古汇1座12F 汇丰公司(ip), 但是里面很多人啊, 谁来收呢. 所以还有加上收件人信息啊(port)


4.5 byte[] getData()

返回内核数组, 也就是把货物从包裹中提取出来啦, 一般用于接收端.



这得注意的是, DatagramPacket这个容器一般可以重复利用.

对于发送端来讲很简单, 不断利用setData(byte[] bArr)来替换内核数组(货物)就ok了.


但是对于接受来讲, DatagramPacket .的内核数组会用于接受数据.

那么这个内核数组的高位位置可能存在上次接受的数据.


DatagramPacket并没有提供重置内核数组的方法.

所以我们需要用Arrays.fill()方法来手动重置它的内核数组(当然你也可以在接受前利用setData()来给它1个新的内核数组).


五. 一个UPD编程例子.

这个例子很简单, 无非1个发送端放在本机, 1个接收端放在另1台机器.

发送端会可以发送字符串(消息)给接受端, 接收端回在终端上显示这个信息.


这个程序有四个类.

分别是

UDP_Server1.java     ->   接收端

UPD_SocketReceive    ->   接收端调用这个类来接受数据


UPD_Client1.Java      ->  发送端

UDP_SocketSend      ->   发送端调用这个类来发送数据.



5.1 UDP_SocketSend 

这个类的提供下面关键方法:


start()      执行这个方法后, 可以发送数据.

send(byte[] bArr,int sLength, String ip, int port)    发送字节数组到指定ip 和端口

close()     关闭这个socket.


代码如下:

package UDP_kng;import java.net.*;import java.io.*;public class UDP_SocketSend{    private boolean startedFlag;    private InetAddress ipSend;    private DatagramSocket dataSocket; //it just like a wharf or port    private DatagramPacket dataPacket = new DatagramPacket(new byte[1],0); //it just like a container    public UDP_SocketSend(){        this.startedFlag = false;    }        //build a wharf    public int start(){        if (true == this.startedFlag){            System.out.println("Err: This UDP Client is started..");            return -2;        }        try{            dataSocket = new DatagramSocket();            System.out.println("This socket started successfully!!");        }        catch(Exception e){            e.printStackTrace();            System.out.println("Err: Failed to instantiate a UDP Socket..!!");            return -1;        }                        this.startedFlag = true;        return 0;    }    public int close(){        if (false == this.startedFlag){            System.err.println("Err: The socket is not started yet!");            return -2;        }        try{            dataSocket.close();  //once the socket is closed, it cannot be reopen, we hava to new another socket if we want to sendout data.            System.out.println("this socket closed successfully!!");        }catch(Exception e){            e.printStackTrace();            System.err.println("Err: Fail to close the Socket!!");            return -1;        }        this.startedFlag = false;        return 0;    }    public int send(byte[] bArr,int sLength, String ip, int port){        if (false == this.startedFlag){            System.err.println("Err: The socket is not started yet!");            return -2;        }        if (0 >= port){            System.err.println("Err: The port provided is not vaild!");            return -1;        }                try{            ipSend = InetAddress.getByName(ip);         }catch(Exception e){            System.err.println("Err: Failed to setup the ip address!");        }        dataPacket.setPort(port); //attach a label        dataPacket.setData(bArr,0,sLength); //put the data into this container        dataPacket.setAddress(ipSend);        try{            dataSocket.send(dataPacket);  //send out the container to ipSend        }catch(Exception e){            e.printStackTrace();            System.err.println("Err: Failed to send out the data package!");            return -3;        }        return 0;    }}        

5.2 UDP_Client1


这个类调用上面的UDP_SocketSend,可以不断地让用户发送消息到制定的ip和端口.

代码:

package UDP_kng;import java.net.*;import java.io.*;import UDP_kng.*;public class UDP_Client1{    private static int port = 9001;    //private static String ip = "127.0.0.1";    private static String ip = "192.168.1.106";    public static void f() throws Exception{        ByteArrayOutputStream bas = new ByteArrayOutputStream();            DataOutputStream dos = new DataOutputStream(bas);        DataInputStream dis = new DataInputStream(System.in);        UDP_SocketSend uSender = new UDP_SocketSend();        uSender.start();        System.out.println("Please input your message, type EOF to exit.");        String msg = dis.readLine();        while(!msg.equals("EOF")){            bas.reset();            dos.writeBytes(msg);            dos.flush();            uSender.send(bas.toByteArray(), bas.size(),ip, port);            System.out.println("Please input your message, type EOF to exit.");            msg = dis.readLine();        }        uSender.close();        dos.close();        dis.close();        //uSender.send(dstream.toByteArray(),"127.0.0.1", port);            } }



5.3 UDP_SocketReceive

这个类提供receive()方法, 返回1个DatagramPacket数据包.

package UDP_kng;import java.net.*;import java.util.Arrays;public class UDP_SocketReceive{    private DatagramSocket dataSocket; //just like a wharf    private byte[] bArr = new byte[1024];    private DatagramPacket dataPacket = new DatagramPacket(bArr,bArr.length); //just like a container    private int port;    private boolean startedFlag = false;    public UDP_SocketReceive(int port){        this.port = port;        this.start();     }     public int start(){        if (true == startedFlag){            System.err.println("This socket is stated already!");            return -1;        }        try{            dataSocket = new DatagramSocket(this.port);        }catch(Exception e){            e.printStackTrace();            System.err.println("fail to new a DatagramSocket, please try to start once again!");            this.startedFlag = false;            return -2;        }        this.startedFlag = true;        return 0;    }     public DatagramPacket receive(){        if (false == startedFlag){            System.err.println("This socket is not stated yet!");            return null;        }        byte b = 0;        Arrays.fill(bArr,b); //clean the dataPacket, avoid to show previous data.            try{            dataSocket.receive(this.dataPacket);//after this step, the container have got the data from sender.        }catch(Exception e){            e.printStackTrace();            System.err.println("Failed receive a datapackage!");            return null;        }        return dataPacket;    }    }


5.4 UDP_SocketServer1

这个类不断循环调用上面那个UDP_SocketReceive 来接受数据包, 实际上就是在监听制定的端口, 一旦接受到数据包, 就把数据包里的数据转化为字符串输出到终端.


package UDP_kng;import UDP_kng.*;import java.io.*;import java.net.*;public class UDP_Server1{    public static int port = 9001;    public static void f() throws Exception{        DatagramPacket dataPacket;        UDP_SocketReceive uReceiver = new UDP_SocketReceive(port);        while(true){            System.out.println("Listening port....");            dataPacket =  uReceiver.receive();            printMsg(dataPacket);                }    }    public static void printMsg(DatagramPacket dataPacket) throws Exception{        ByteArrayInputStream bis = new ByteArrayInputStream(dataPacket.getData());        DataInputStream dis = new DataInputStream(bis);        String msg = dis.readLine();        System.out.println("msg: " + msg);        dis.close();    }        }




5.5 执行结果

将编译后的UDP_Server1 放在虚拟机的xp里执行(java跨平台),  UDP_Client1放在本机执行, 如下:





5.6 小结


上面例子的功能十分简单, 一端只能发送, 一端只能接受, 但是如果我们利用多线程,  在两端都打开发送和接受线程, 就相当于1个简单的UDP聊天工具了..








0 0