Java實(shí)現(xiàn)UDP通信

UDP(User Datagram Protocol),即用戶數(shù)據(jù)報(bào)協(xié)議,UDP只提供數(shù)據(jù)的不可靠傳遞,它一旦把應(yīng)用程序發(fā)給網(wǎng)絡(luò)層的數(shù)據(jù)發(fā)送出去,就不保留數(shù)據(jù)備份(所以UDP有時(shí)候也被認(rèn)為是不可靠的數(shù)據(jù)報(bào)協(xié)議)。UDP在IP數(shù)據(jù)報(bào)的頭部僅僅加入了復(fù)用和數(shù)據(jù)校驗(yàn)(字段)。

雖然UDP被認(rèn)為是不可靠的,但是可以通過應(yīng)用程序來負(fù)責(zé)傳輸?shù)目煽啃?,如FTP等都是用了UDP協(xié)議。另外一個(gè)特點(diǎn)是UDP是基于數(shù)據(jù)包,也就是說數(shù)據(jù)會(huì)被打成包發(fā)送。所以包的大小會(huì)有限制,一般認(rèn)為最大是64KB

Java中UDP主要涉及了DatagramPacket和 DatagramSocket兩個(gè)類。前者被認(rèn)為是信息的載體,后者被認(rèn)為是收發(fā)的實(shí)體。也就是,DatagramPacket攜帶數(shù)據(jù),并通過DatagramSocket收發(fā)。

下面就來實(shí)現(xiàn)一下服務(wù)端與客戶端。

服務(wù)端:

public class UDPService {
    public static final String SERVICE_IP = "127.0.0.1";

    public static final int SERVICE_PORT = 10101;

    public static final int MAX_BYTES = 2048;

    private DatagramSocket service;

    public static void main(String[] args) {
        UDPService udpService = new UDPService();
        udpService.startService(SERVICE_IP,SERVICE_PORT);//啟動(dòng)服務(wù)端
    }

    private void startService(String ip, int port) {
        try {
            //包裝IP地址
            InetAddress address = InetAddress.getByName(ip);
            //創(chuàng)建服務(wù)端的DatagramSocket對(duì)象,需要傳入地址和端口號(hào)
            service = new DatagramSocket(port,address);

            byte[] receiveBytes = new byte[MAX_BYTES];
            //創(chuàng)建接受信息的包對(duì)象
            DatagramPacket receivePacket = new DatagramPacket(receiveBytes,receiveBytes.length);

            //開啟一個(gè)死循環(huán),不斷接受數(shù)據(jù)
            while(true){
                try{
                    //接收數(shù)據(jù),程序會(huì)阻塞到這一步,直到收到一個(gè)數(shù)據(jù)包為止
                    service.receive(receivePacket);
                }catch (Exception e){
                    e.printStackTrace();
                }

                //解析收到的數(shù)據(jù)
                String receiveMsg = new String(receivePacket.getData(),0,receivePacket.getLength());
                //解析客戶端地址
                InetAddress clientAddress = receivePacket.getAddress();
                //解析客戶端端口
                int clientPort = receivePacket.getPort();

                //組建響應(yīng)信息
                String response = "Hello world " + System.currentTimeMillis() + " " + receiveMsg;
                byte[] responseBuf = response.getBytes();
                //創(chuàng)建響應(yīng)信息的包對(duì)象,由于要發(fā)送到目的地址,所以要加上目的主機(jī)的地址和端口號(hào)
                DatagramPacket responsePacket = new DatagramPacket(responseBuf,responseBuf.length,clientAddress,clientPort);

                try{
                    //發(fā)送數(shù)據(jù)
                    service.send(responsePacket);
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //關(guān)閉DatagramSocket對(duì)象
            if(service!=null){
                service.close();
                service = null;
            }
        }
    }

}

客戶端:

public class UDPClient {
    private DatagramSocket client;

    public static void main(String[] args){
        UDPClient client = new UDPClient();
        Scanner scanner = new Scanner(System.in);
        //建立死循環(huán),不斷發(fā)送數(shù)據(jù)
        while(true){
            String msg = scanner.nextLine();
            if("##".equals(msg))
                break;
            //打印響應(yīng)的數(shù)據(jù)
            System.out.println(client.sendAndReceive(UDPService.SERVICE_IP,UDPService.SERVICE_PORT,msg));
        }
    }

    private String sendAndReceive(String ip, int port, String msg) {
        String responseMsg = "";

        try {
            //創(chuàng)建客戶端的DatagramSocket對(duì)象,不必傳入地址和對(duì)象
            client = new DatagramSocket();
            byte[] sendBytes = msg.getBytes();
            //封裝要發(fā)送目標(biāo)的地址
            InetAddress address = InetAddress.getByName(ip);
            //封裝要發(fā)送的DatagramPacket的對(duì)象,由于要發(fā)送到目的主機(jī),所以要加上地址和端口號(hào)
            DatagramPacket sendPacket = new DatagramPacket(sendBytes,sendBytes.length,address,port);

            try {
                //發(fā)送數(shù)據(jù)
                client.send(sendPacket);
            }catch (Exception e){
                e.printStackTrace();
            }

            byte[] responseBytes = new byte[UDPService.MAX_BYTES];
            //創(chuàng)建響應(yīng)信息的DatagramPacket對(duì)象
            DatagramPacket responsePacket = new DatagramPacket(responseBytes,responseBytes.length);
            try {
                //等待響應(yīng)信息,同服務(wù)端一樣,客戶端也會(huì)在這一步阻塞,直到收到一個(gè)數(shù)據(jù)包
                client.receive(responsePacket);
            }catch (Exception e){
                e.printStackTrace();
            }
            
            //解析數(shù)據(jù)包內(nèi)容
            responseMsg = new String(responsePacket.getData(),0,responsePacket.getLength());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //關(guān)閉客戶端
            if(client != null){
                client.close();
                client = null;
            }
        }

        return responseMsg;
    }
}

最后介紹一下涉及的幾個(gè)類。

1. InetAddress
InetAddress類用來代表IP地址,InetAddress下還有2個(gè)子類:Inet4Address、Inet6Address,它們分別代表IPv4地址和IPv6地址。

這個(gè)類沒有提供公開的構(gòu)造方法,而是提供了如下兩個(gè)靜態(tài)方法來獲取InetAddress實(shí)例:

InetAddress getByAddress(byte[] addr)//根據(jù)原始IP地址來獲取對(duì)應(yīng)的InetAddress對(duì)象
InetAddress getByName(String host)//根據(jù)主機(jī)獲取對(duì)應(yīng)的InetAddress對(duì)象

其他一些方法

String getCanonicalHostName()//獲取此 IP 地址的全限定域名。
String getHostAddress()//返回該InetAddress實(shí)例對(duì)應(yīng)的IP地址字符串(以字符串形式)。
String getHostName()//獲取此 IP 地址的主機(jī)名。
InetAddress getLocalHost() //獲取本機(jī)IP地址對(duì)應(yīng)的InetAddress實(shí)例.
boolean isReachable(int timeout) //測(cè)試是否可以到達(dá)該地址

2. DatagramPacket
有以下幾種構(gòu)造

DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length)
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
DatagramPacket(byte[] buf, int length, SocketAddress address)

方法:

InetAddress getAddress() //獲取地址,可能是發(fā)送方的也可能是接收方的
byte[] getData()  //獲取數(shù)據(jù)
int getLength()  //獲取長度
int getOffset() //獲取偏移量
int getPort()  //獲取端口
SocketAddress getSocketAddress()  //獲取SocketAddress 
void setAddress(InetAddress iaddr)  //設(shè)置地址
void setData(byte[] buf)  //設(shè)置數(shù)據(jù)
void setData(byte[] buf, int offset, int length)  //設(shè)置數(shù)據(jù),指定長度與偏移
void setLength(int length) //設(shè)置長度
void setPort(int iport) //設(shè)置端口
void setSocketAddress(SocketAddress address)  //設(shè)置SocketAddress 

3. DatagramSocket
該類常用的構(gòu)造方法如下:

DatagramSocket()  //默認(rèn)本機(jī)ip地址,隨機(jī)選一個(gè)可用端口
DatagramSocket(int port)  //默認(rèn)本機(jī)IP地址,指定端口
DatagramSocket(int port, InetAddress laddr)  //指定ip和端口

通常第一個(gè)可作為客戶端,作為服務(wù)端一般需要指定端口,確保其他客戶端可以發(fā)送到該服務(wù)器.

常用方法:

void send(DatagramPacket p)  //發(fā)送一個(gè)數(shù)據(jù)包,數(shù)據(jù)包要包含目的主機(jī)的地址和端口
void receive(DatagramPacket p)  //接受一個(gè)數(shù)據(jù)包,數(shù)據(jù)包只要包含一個(gè)byte數(shù)組和長度即可

另外還有一個(gè)connect(InetAddress address,int port),我們都知道UDP是面向無連接的,這個(gè)connect方法其實(shí)只是指定一個(gè)主機(jī),相當(dāng)于綁定,但沒有任何實(shí)質(zhì)連接,而接下來都只能從這個(gè)主機(jī)收發(fā)數(shù)據(jù),而且在發(fā)數(shù)據(jù)時(shí),DatagramPacket 不必?cái)y帶目的主機(jī)的地址和端口號(hào)。并且如果DatagramPacket攜帶的主機(jī)地址與端口號(hào)和connect綁定的不一致,會(huì)拋出IllegalArgumentException。

另外getInnetAddress和getPort都是返回connect后綁定的主機(jī)信息,若要取到本機(jī)的需要調(diào)用getLocalPort和getLocalAddress。若沒有connect,則getInnetAddress和getPort返回null與-1.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容