【網路程式設計2】Java資料報套接字
這篇博文是本文學習《Java網路程式設計》書中第5章資料報套接字的學習總結。初學者網友學習這篇Java資料報套接字文章,如果難於理解文章前面理論部分,可以先執行後面的程式,邊看執行後面的程式邊理解前面的原理,這對初學者是最好的方法。所有原始碼都在文章後面我的github連結程式碼中。
——惠州學院13網路工程 吳成兵 20160609
目錄 1
一 資料報套接字概述
流套接字的每個連線均要花費一定的時間,為了減少這種開銷,網路API提供了第二種套接字——資料報套接字(DatagramSocket),又稱自定址套接字。
資料報套接字基於的協議是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;
}
}
如果你看完這篇博文,覺得對你有幫助,並且願意付贊助費,那麼我會更有動力寫下去。
- 返回到目錄 ↩