數據報(Datagram)使網絡層數據單元在介質上傳輸信息的一種邏輯分組格式,它是一種在網絡中傳播的、獨立的、自身包含地址信息的消息,它能否到達目的地,到達的時間,到達時內容是否會變化不能准確知道的。它的通訊雙方是不需要建立連接的,對於一些不需要很高質量的應用程序來說,數據報通訊是一個非常好的選擇。在Java的java.net包中有兩個類DatagramSocket和DatagramPacket,為應用程序中采用數據報通訊方式進行網絡通訊。
下面,我想詳細解釋在Java中實現客戶端與服務器之間數據報通訊的方法,請看:
一、客戶端應用程序的工作流程
1) 首先要建立數據報通訊的Socket,我們可以通過創建一個DatagramSocket對象來實現它,在Java中DatagramSocket類有如下兩種構造方法:
a)public DatagramSocket() 構造一個數據報socket,並使其與本地主機任一可用的端口連接。若打不開socket則拋出SocketException異常。
b) public DatagramSocket(int port) 構造一個數據報socket,並使其與本地主機指定的端口連接。若打不開socket或socket無法與指定的端口連接則拋出SocketException異常。
2) 創建一個數據報文包,用來實現無連接的包傳送服務。每個數據報文包是用DatagramPacket類來創建,DatagramPacket對象封裝了數據報包數據,包長度,目標地址,目標端口。作為客戶端要發送數據報文包,要調用DatagramPacket類以如下形式的構造函數創建DatagramPacket對象,將要發送的數據和包文目的地址信息放入對象之中。
DatagramPacket(byte bufferedarray[],int length,InetAddress address,int port)即構造一個包長度為length的包傳送到指定主機指定端口號上的數據報文包,參數length必須小於等於bufferedarry.length
DatagramPacket類提供了四個類來獲取信息
a) public byte[] getData() 返回一個字節數組,包含收到或要發送的數據報中的數據
b) public int getLength() 返回發送或接收到的數據的長度
c) public InetAddress getAddress() 返回一個發送或接收此數據報包文的機器的IP地址
d) public int getPort() 返回發送或接收數據報的遠程主機的端口號。
3)創建完DatagramSocket和DatagramPacket對象,就可以發送數據報文包了。發送是通過調用DatagramSocket對象的send方法實現,它需要以DatagramPacket對象為參數,將剛才封裝進DatagramPacket對象中的數據組成數據報發出。
4)當然,我們也可以接收數據報文包,為了接收從服務器返回的結果數據報文包,我們需要創建一個新的DatagramPacket對象,這就需要用到DatagramPacket的另一種構造方式DatagramPacket(byte bufferedarray[],int length),即只需指明存放接收的數據報的緩沖區和長度。調用DatagramSocket對象的receive()方法來完成接收數據報的工作,此時需要將上面創建的DatagramPacket對象作為參數,該方法會一直阻塞知道收到一個數據報文包,此時DatagramPacket的緩沖區中包含的就是接收到的數據,數據報文包中也包含發送者的IP地址,發送者機器上的端口號等信息。
5)處理接收緩沖區內的數據,獲取服務結果。
6)當通訊完成後,可以使用DatagramSocket對象的close()方法來關閉數據報通訊Socket。當然,Java自己會自動關閉Socket,釋放DatagramSocket和DatagramPacket所占用的資源。但是作為一種良好的編程習慣,還是要顯示的予以關閉。
下面我給出一個簡單的利用數據報通訊的客戶端程序,它能夠完成與服務器簡單的通訊。為了直觀,我把它寫成了Applet程序,由於本文不是介紹Applet,所以我只寫了簡要的注釋,對Applet感興趣的朋友親參閱有關書籍。
import java.applet.*;
import java.awt.*;
import java.net.*;
import java.io.*;
public final class javaCommunicationClient extends Applet
{
private Label label1,label2 ;
private Panel panel1,panel2;
private TextField textfield;
private TextArea textarea;
private DatagramSocket sendSocket,receiveSocket;//聲明發送數據報Socket和接收數據報Socket
private DatagramPacket sendPacket,receivePacket;//聲明發送數據報文包和接收數據報文包
public void init()
{
setBackground(Color.gray);
setLayout(new BorderLayout());//設置一個布局管理器
panel1=new Panel();
panel1.setLayout(new BorderLayout());//在容器中放置布局管理器
label1=new Label("通話紀錄");
textarea=new TextArea(10,20);//文本顯示區域
textarea.setText("歡迎您!");
panel1.add("North",label1);//將標簽添加到布局管理器中
panel1.add("Center",textarea);
add("North",panel1);
label2=new Label("發言:");//創建另一個容器
panel2.add("Center",label2);
textfield=new TextField(20);
textfield.setText("");
panel2.add("South",textfield);
add("Center",panel2);
show();
}
public void start()
{
waitForPackets();
}
public void waitForPackets()
/*方法waitForPacket用來監聽來自於服務器的數據報,當獲得數據報後,在文本顯示區域顯示出來
*/
{
try
{
sendSocket=new DatagramSocket();//實例化一個發送數據報Socket對象
receiveSocket= new DatagramSocket(5001);//實例化一個接收數據報Socket對象,以5001為端口
} catch (SocketException e)//捕獲異常
{
textarea.appendText("不能打開數據報Socket,或數據報Socket無法與指定端口連接!");
}
while (true)
{
try
{
byte buf[]=new byte[100];
receivePacket=new DatagramPacket(buf,buf.length);//實例化一個接收數據報文包對象
receiveSocket.receive(receivePacket);//以receivePacket為參數,接受文包
textarea.appendText("\n服務器:\t");
byte[] data=receivePacket.getData();
String receivedString=new String(data);
textarea.appendText(receivedString);//將接收到的數據報文報中的數據顯示出來
} catch(IOException e)
{
textarea.appendText("網絡通訊出現錯誤,問題在"+e.toString());
}
}
}
public boolean action(Event e,Object o)
{
try
{textarea.appendText("\n客戶端:");
String string=o.toString();
textarea.appendText(string);
byte[] databyte=new byte[100];
string.getBytes(0,string.length(),databyte,0);
sendPacket=new DatagramPacket(databyte,string.length(),InetAddress.getByName("202.38.64.4"),5000);//發送數據報,其中你可以用你自己的主機IP替換器中的IP地址
sendSocket.send(sendPacket);
}catch(IOException ioe)
{
textarea.appendText("網絡通訊出現錯誤,問題在"+ioe.toString());
}
return true;
}
}
二、服務器端應用程序的工作流程
不同於基於數據流通訊方式,在數據報通訊中,通訊雙方之間並不要建立連接,所以,服務器應用程序通訊過程與客戶端應用程序的通訊過程使非常相似的,也要建立數據報通訊DatagramSocket,構建數據報文包DatagramPacket,接收數據報和發送數據報,處理接收緩沖區內的數據,通訊完畢後,關閉數據報通訊Socket。不同的是,服務器應用程序要面向網絡中的所有計算機,所以服務器應用程序收到一個包文後要分析它,得到數據報的源地址信息,這樣才能創建正確的返回結果報文給客戶機。
下面我給出了一個數據報通訊的服務器段程序,由於服務器端的相應應用程序和客戶端程序比較相似,所以我不想詳細的注釋,僅列出程序供大家參考:
1、javaCommunicationServer.java
import java.net.*;
import java.io.*;
import java.awt.*;
import java.applet.Applet;
public final class javaCommunicationServer extends Frame
{
private Label label1,label2 ;
private Panel panel1,panel2;
private TextField textfield;
private String name,name1;
private TextArea textarea;
private DatagramSocket sendSocket,receiveSocket;
private DatagramPacket sendPacket,receivePacket;
public javaChatServer()
{
super("通訊控制台:");//使用超類構造方法,構造一個Frame
panel1=new Panel();
panel1.setLayout(new BorderLayout());
label1=new Label("通話紀錄");
textarea=new TextArea(10,20);
textarea.setText("歡迎您!");
panel1.add("North",label1);
panel1.add("Center",textarea);
add("North",panel1);
panel2=new Panel();
panel2.setLayout(new BorderLayout());
label2=new Label("發言:");
panel2.add("Center",label2);
textfield=new TextField(20);
panel2.add("South",textfield);
add("Center",panel2);
show();
try
{
sendSocket=new DatagramSocket();
receiveSocket= new DatagramSocket(5000);
}
catch (SocketException e)
{
e.printStackTrace();
System.exit(1);
}
}
public void waitForPacket()
{
while (true)
{
try
{
byte buf[]=new byte[100];
receivePacket=new DatagramPacket(buf,buf.length);
receiveSocket.receive(receivePacket);
name=receivePacket.getAddress().toString();
if(name1!=name)
{
textarea.appendText(" 來自主機:"+name+" 端口:"+receivePacket.getPort());
}
textarea.appendText(" 客戶: ");
byte[] data=receivePacket.getData();
String receivedString=new String(data,0);
textarea.appendText(receivedString);
name1=name;
}
catch(IOException e)
{
textarea.appendText("網絡通訊出現錯誤,問題在"+e.toString());
}
}
}
public boolean handleEvent(Event e)
{
if(e.id==Event.WINDOWS_DESTROY)
{
hide();
dispose();
System.exit(0);
}
return super.handleEvent(e);
}
public boolean action(Event e,Object o)
{
try
{textarea.appendText(" 服務器:");
String string=o.toString();
textarea.appendText(string);
byte[] databyte=new byte[100];
string.getBytes(0,string.length(),databyte,0);
sendPacket=new DatagramPacket(databyte,string.length(),InetAddress.getByName(name),5001);
sendSocket.send(sendPacket);
}catch(IOException ioe)
{
textarea.appendText("網絡通訊出現錯誤,問題在"+ioe.toString());
}
return true;
}
}
2、CommunicationServerRun.java,建立javaCommunicationServer的實例,然後運行。
import javaCommunicationServer
class CommunicationServerRun extends javaCommunicationServer
{
public static void main(String args[])
{
javaCommunicationServer cs=new javaCommunicationServer();
cs.waitForPacket();
}
}