1. 程式人生 > >【網路程式設計2】Java資料報套接字

【網路程式設計2】Java資料報套接字

這篇博文是本文學習《Java網路程式設計》書中第5章資料報套接字的學習總結。初學者網友學習這篇Java資料報套接字文章,如果難於理解文章前面理論部分,可以先執行後面的程式,邊看執行後面的程式邊理解前面的原理,這對初學者是最好的方法。所有原始碼都在文章後面我的github連結程式碼中。
——惠州學院13網路工程 吳成兵 20160609

目錄 1

一 資料報套接字概述

  流套接字的每個連線均要花費一定的時間,為了減少這種開銷,網路API提供了第二種套接字——資料報套接字(DatagramSocket),又稱自定址套接字
資料報套接字通訊流程
  資料報套接字基於的協議是UDP協議,採用的是一種盡力而為

(Best-Effort)的傳送資料的方式,它只是把資料的目的地記錄在資料報包(DatagramPacket)中,然後就直接放在網路上,系統不保證資料是否能安全送到,或者什麼時候可以送到,也就是說它並不保證傳送的質量。UDP在每一個自定址包中包含了錯誤檢測資訊,在每個自定址包到達目的地之後UDP進行簡單的錯誤檢查,如果檢查失敗,UDP將拋棄這個自定址包,也不會從傳送者那裡重新請求替代者。這與通過郵局傳送信件相似,發信人在發信這前不需要與收信人建立連線,同樣也不能保證信件能到達發信人那裡。

  可見,UDP的優點是效率高,並且比較靈活,一般用於質量和實時性要求不是很高的情況,比如實時音訊和視訊應用中。

  DatagramSocket本身只是碼頭,不維護狀態,不能產生I/O流,它的唯一作用就是接收和傳送資料報包,這個資料報包在Java中是使用DatagramPacket物件實現的。

  資料報套接字程式設計中主要使用下面三個類:DatagramPacket 、DatagramSocket和MulticastSocket。

  • DatagramPacket物件描繪了自定址包的地址資訊;
  • DatagramSocket表示客戶端程式與伺服器程式自定址套接字;
  • MulticastSocket 描繪了能進行多點傳送的自定址套接字。

二 DatagramPacket

2.1 建立DatagramPacket物件

2.1.1 建立的DatagramPacket物件用於接收資料

  以一個空陣列來建立DatagramPacket物件,該物件的作用是接收DatagramSocket中的資料。

  • public DatagramPacket(byte buf[], int length) :接收到的資料從buf[0]開始存放,直到整個資料包接收完畢或者將length的位元組寫入buf為止。
  • public DatagramPacket(byte buf[], int offset, int length):接收到的資料從buf[offset]開始存放,如果資料包長度超出了length,則會觸發IllegalArgument-Exception。

不過這是RuntimeException,不需要使用者程式碼捕獲。
示範程式碼如下:

byte[] buffer = new byte[8912];
DatagramPacket datap = new DatagramPacket(buffer, buffer.length);

2.1.2 建立的DatagramPacket物件用於傳送資料

  以一個包含資料的陣列 buf[]來建立DatagramPacket物件,該物件作為DatagramSocket傳送資料的載體,並指定該DatagramPacket目的IP地址和埠號。

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

示範程式碼是要傳送一串字串:

String string = new String("java networking");
byte[] data=string.getBytes();
int port=1024;
InetAddress inetA=null;
try {
    inetA = InetAddress.getByName("127.0.0.1");
    DatagramPacket datap = new DatagramPacket(data,data.length,inetA,port);
} catch (UnknownHostException e) {}

2.2 DatagramPacket常用方法

獲取DatagramPacket物件屬性的方法如下:

  • public synchronized InetAddress getAddress()
  • public synchronized int getPort()
  • public synchronized byte[] getData()
  • public synchronized int getLength()
  • public synchronized int getOffset()

設定DatagramPacket物件屬性的方法如下:

  • public synchronized void setAddress(InetAddress iaddr)
  • public synchronized void setPort(int iport)
  • public synchronized void setData(byte[] buf, int offset, int length)
  • public synchronized void setData(byte[] buf)
  • public synchronized void setLength(int length)
  • public synchronized void setSocketAddress(SocketAddress address)

部分方法說明:

  • getAddress()是返回該DatagramPacket的IP地址。如果DatagramPacket是傳送出來的資料報包,這個方法則返回目的主機的IP地址。如果DatagramPacket是接收到的資料報包,這個方法則返回遠端主機的IP地址。
  • getSocketAddress()是返回要將此包傳送到的或此資料報包的遠端主機的SocketAddress(通常是IP地址+埠號)。
  • setAddress()是設定該DatagramPacket要傳送往的目的主機的IP地址。
  • setSocketAddress()是設定要將此包傳送到的或此資料報包的遠端主機的SocketAddress(通常是IP地址+埠號)。

三 DatagramSocket

3.1 建立DatagramSocket物件

  • public DatagramSocket() throws SocketException :系統隨機產生一個埠,建立一個數據報套接字。這種構造方法沒有指定埠號,可以用在傳送端,如果構造不成功則觸發SocketException異常。
  • public DatagramSocket(int port) throws SocketException :用一個指定埠號port建立一個數據報套接字。如果指定埠已被佔用或者是試圖連線低於1024的埠但又沒具備許可權。
  • public DatagramSocket(int port, InetAddress laddr) throws SocketException :建立一個數據報套接字,並將該物件繫結到指定的IP地址和埠。

通常用指定埠的方式建立傳送端資料報套接字。一旦得到了DatagramSocket物件之後,就可以通過如下兩個方法來接收和傳送資料:

  • public void send(DatagramPacket p) throws IOException :從該DatagramSocket中接收資料報包。
  • public synchronized void receive(DatagramPacket p) throws IOException :使用該DatagramSocket物件向外傳送資料報包。

從上面兩個方法可以看出,使用DatagramSocket傳送資料報包時,DatagramSocket並不知道將該資料報包傳送到哪裡,而是由DatagramPacket自身決定資料報包的目的地,所有的埠、目的地址和資料,需要由DatagramPacket來指定。就像碼頭並不知道每個集裝箱傳送到哪裡,碼頭只是將這些集裝箱傳送出去,而集裝箱本身包含了該集裝箱的目的地。

  DatagramSocket在接收資料之前,應該採用public DatagramPacket(byte buf[], int length)或public DatagramPacket(byte buf[], int offset, int length)構造方法建立一個DatagramPakcet物件,給出接收資料的位元組及其長度。然後呼叫DatagramSocket的方法receive()等待資料報包的到來,receive()將一直等待(也就是說會阻塞呼叫該方法的執行緒),直到收到一個數據報包為止,如下程式碼所示:

//建立要接收資料的DatagramPacket物件
DatagramPacket packet =new DatagramPacket(buf,buf.length);
//接收資料
receiveSocket.receive(packet);

  傳送資料之前,呼叫public DatagramPacket(byte buf[], int length, InetAddress address, int port)或public DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port)構造方法建立DatagramPacket物件,此時的位元組陣列存放了要傳送的資料。除此之外,還要給出完整的目的地址,包括IP地址和埠號。傳送資料是通過DatagramSocket的方法send()實現的,send()根據資料報包的目的地址來尋徑以傳遞資料報包,如下程式碼所示:

//建立一個要傳送資料的DatagramPacket物件
DatagramPacket packet = new DatagramPacket(buf,length,address, port);
//傳送資料
sendSocket.send(packet);

3.2 DatagramSocket常用方法

  • public InetAddress getInetAddress()
  • public int getPort()
  • public InetAddress getLocalAddress()
  • public int getLocalPort()
  • public SocketAddress getRemoteSocketAddress()
  • public SocketAddress getLocalSocketAddress()
  • public void send(DatagramPacket p) throws IOException
  • public synchronized void receive(DatagramPacket p) throws IOException

四 DatagramSocket程式設計示例

4.1 利用DatagramSocket查詢端口占用情況

這裡寫圖片描述

package _5_2DatagramSocket程式設計示例._5_2_1利用DatagramSocket查詢端口占用情況;


import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * 
 * Copyright ? 2016 Authors. All rights reserved.
 *
 * FileName: .java
 * @author : Wu_Being <[email protected]>
 * Date/Time: 2016-6-10/上午12:36:45
 * Description: 查詢埠的佔用情 ?
 */
public class UDPScan {

    /**
     * @param args
     */
    public static void main(String[] args) {

        //2014~65535
        for (int port = 1024; port < 65536; port++) {
            try {
                DatagramSocket server = new DatagramSocket(port);
                server.close();
            } catch (SocketException e) {
                System.out.println("there is a server in port:" + port + ".");
            }
        }
    }
}

4.2 利用資料報通訊的C/S程式

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

4.2.1 資料接收端

package _5_2DatagramSocket程式設計示例._5_2_2利用資料報通訊的CS程式;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * 
 * Copyright ? 2016 Authors. All rights reserved.
 *
 * FileName: .java
 * @author : Wu_Being <[email protected]>
 * Date/Time: 2016-6-10/下午10:36:26
 * Description:
 */
public class UDPReceiver {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        try {
            DatagramSocket receiveSocket =new DatagramSocket(5000);
            byte buf[]=new byte[1000];
            DatagramPacket receivepPacket=new DatagramPacket(buf,buf.length);
            System.out.println("starting to receive packet...");
            while(true){
                receiveSocket.receive(receivepPacket);
                String receiveData=new String(receivepPacket.getData(),0,receivepPacket.getLength());
                String name=receivepPacket.getAddress().getHostName();
                int port=receivepPacket.getPort();
                System.out.print("來自主機:"+name);
                System.out.print(",的埠:"+port);
                System.out.println("的資料:"+receiveData);
            }
        } catch (SocketException e) {
            System.out.println("不能開啟資料報Socket,或資料報Socket無法與指定埠連線");
            System.exit(1);
        } catch (IOException e) {
            System.out.println("網路通訊出現問題,問題在於:"+e.toString());
        }
        }

}

4.2.2 資料傳送端

package _5_2DatagramSocket程式設計示例._5_2_2利用資料報通訊的CS程式;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

/**
 * 
 * Copyright ? 2016 Authors. All rights reserved.
 * 
 * FileName: .java
 * 
 * @author : Wu_Being <[email protected]> Date/Time: 2016-6-10/下午10:36:20
 *         Description:
 */
public class UDPSender {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        while (true) {
            Scanner scanner = new Scanner(System.in);
            try {
                System.out.print("請輸入要傳送的資料:");
                String string = scanner.nextLine();
                DatagramSocket sendSocket = new DatagramSocket();// 埠號隨機
                // byte[] databyte=new byte[100]
                byte[] databyte = string.getBytes();
                DatagramPacket sentPacket = new DatagramPacket(databyte,
                        databyte.length, InetAddress.getByName("127.0.0.1"),
                        5000);
                sendSocket.send(sentPacket);
                System.out.println("send the data:" + string);
            } catch (SocketException e) {
                System.out.println("不能開啟資料報Socket,或資料報Socket無法與指定埠連線");
            } catch (IOException e) {
                System.out.println("網路通訊出現問題,問題在於:" + e.toString());
            }

        }
    }
}

4.3 用UDP實現的聊天程式

這裡寫圖片描述

package _5_2DatagramSocket程式設計示例._5_2_3用UDP實現的聊天程式;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class UDPChat implements ActionListener, Runnable {

    JTextArea showArea;
    JLabel lbl1, lbl2, lbl3;
    JTextField msgTextField, sendPortTextField, receivePorttTextField, IPAddressTextField;
    JFrame mainFrame;
    JButton sendButton, startButton;//,resetbButton;
    JScrollPane JSPane;
    JPanel panel1, panel2;
    Container container;

    Thread thread = null;
    DatagramSocket sendSocket, receiveSocket;
    DatagramPacket sendPacket, receivePacket;

    private InetAddress sendIP;
    private int sendPort, receivePort;// 儲存傳送埠和接收埠
    private byte inbuf[], outbuf[];

    public static final int BUFSIZE = 1024;

    public UDPChat() {
        mainFrame = new JFrame("聊天——UDP協議");
        container = mainFrame.getContentPane();

        showArea = new JTextArea();
        showArea.setEditable(false);
        showArea.setLineWrap(true);

        lbl1 = new JLabel("接收埠號:");
        lbl2 = new JLabel("傳送埠號:");
        lbl3 = new JLabel("對方的地址:");

        sendPortTextField = new JTextField();
        sendPortTextField.setColumns(4);
        receivePorttTextField = new JTextField();
        receivePorttTextField.setColumns(4);
        IPAddressTextField = new JTextField();
        IPAddressTextField.setColumns(8);

        startButton = new JButton("開始");
        startButton.addActionListener(this);/**/

//      resetbButton=new JButton("重置");
//      resetbButton.addActionListener(this);/**/

        panel1 = new JPanel();
        panel1.setLayout(new FlowLayout());
        panel1.add(lbl1);
        panel1.add(receivePorttTextField);
        panel1.add(lbl2);
        panel1.add(sendPortTextField);
        panel1.add(lbl3);
        panel1.add(IPAddressTextField);
        panel1.add(startButton);
//      panel1.add(resetbButton);

        JSPane = new JScrollPane(showArea);

        msgTextField = new JTextField();
        msgTextField.setColumns(40);
        msgTextField.setEditable(false);
        msgTextField.addActionListener(this);/**/

        sendButton = new JButton("傳送");
        sendButton.setEnabled(false);
        sendButton.addActionListener(this);/**/

        panel2 = new JPanel();
        panel2.setLayout(new FlowLayout());
        panel2.add(msgTextField);
        panel2.add(sendButton);

        container.add(panel1, BorderLayout.NORTH);
        container.add(JSPane, BorderLayout.CENTER);
        container.add(panel2, BorderLayout.SOUTH);

        mainFrame.setSize(600, 400);
        mainFrame.setVisible(true);
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        try {
            if (e.getSource() == startButton) {/* 按下了開始按鍵 */
                inbuf = new byte[BUFSIZE];

                receivePort = Integer.parseInt(receivePorttTextField.getText());
                sendPort = Integer.parseInt(sendPortTextField.getText());
                sendIP = InetAddress.getByName(IPAddressTextField.getText());

                //建立傳送和接收DatagramSocket和DatagramPacket
                sendSocket = new DatagramSocket();
                receiveSocket = new DatagramSocket(receivePort);
                receivePacket = new DatagramPacket(inbuf, BUFSIZE);

                thread = new Thread(this);
                thread.setPriority(Thread.MIN_PRIORITY);
                thread.start();

                startButton.setEnabled(false);
                sendButton.setEnabled(true);
                msgTextField.setEditable(true);

//          } else if (e.getSource() == resetbButton) {
//              
//              sendPortTextField.setText(null);
//              receivePorttTextField.setText(null);
//              IPAddressTextField.setText(null);
//              msgTextField.setText(null);
//              showArea.setText(null);
//              
//              startButton.setEnabled(true);
//              sendButton.setEnabled(false);
//              msgTextField.setEditable(false);
//              
//              thread.interrupt();
//              if(receivePacket!=null) {
//                  receiveSocket.close();
//              }

            } else {/* 按下了傳送按鍵或回車鍵 */

                outbuf = msgTextField.getText().getBytes();
                // 組裝要傳送的的資料包
                sendPacket = new DatagramPacket(outbuf, outbuf.length, sendIP, sendPort);
                // 傳送資料
                sendSocket.send(sendPacket);
                showArea.append("我說(" + msgTextField.getText() + ")"    + getDateTime() + "\n");
                msgTextField.setText(null);
            }
        } catch (UnknownHostException e1) {
            showArea.append("getByName無法連線到指定地址!!!" + getDateTime() + "\n");
        } catch (SocketException e1) {
            showArea.append("DatagramSocket無法開啟指定埠!!!" + getDateTime() + "\n");
        } catch (IOException e1) {
            showArea.append("send資料資料失敗!!!" + getDateTime() + "\n");
        } catch (NumberFormatException e1) {
            showArea.append("設定埠資料格式不對!!!" + getDateTime() + "\n");
        }

    }

    @Override
    public void run() {
        String msgstr;
        while (true) {
            try {// 注意try的位置
                receiveSocket.receive(receivePacket);
                msgstr = new String(receivePacket.getData(), 0, receivePacket.getLength());
                showArea.append("對方說(" + msgstr + ")" + getDateTime() + "\n");
            } catch (Exception e) {
                showArea.append("receive接收資料出錯!!!" + getDateTime() + "\n");
            }
        }
    }

    public static void main(String[] args) {
        new UDPChat();
    }

    /**
     * Java程式碼中獲得當前時間
     * 
     * @return 當前時期時間
     */
    private String getDateTime() {
        Date date = new Date();
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = format.format(date);
        return time;
    }
}

Wu_Being 吳兵部落格接受贊助費二維碼

如果你看完這篇博文,覺得對你有幫助,並且願意付贊助費,那麼我會更有動力寫下去。

  1. 返回到目錄