首先我們先來簡單了解一下UDP協(xié)議。它和TCP協(xié)議都是網絡通信中非常重要的傳輸協(xié)議。不同于TCP協(xié)議的端到端服務,它是面向非連接的,屬不可靠協(xié)議,實際上,UDP實現(xiàn)了兩個功能,并有它自己的優(yōu)勢:
- 在IP協(xié)議的基礎上添加了端口
- 可以檢測傳輸過程中可能產生的錯誤數(shù)據(jù),拋棄那些已經損壞的數(shù)據(jù)
- 無需建立連接,傳輸速度快
- 在多播和廣播時只能用UDP協(xié)議
當然UDP也有一定的不足之處:
- 無連接,傳輸不可靠
- 無連接,一旦一方數(shù)據(jù)報丟失,另一方將陷入無限等待(這時可以設置一個超時來解決)
接下來切入正題,在Java中UDP的實現(xiàn)分為兩個類:DatagramPacket和DatagramSocket。DatagramPacket類將數(shù)據(jù)字節(jié)填充到UDP包中,這就是數(shù)據(jù)報;DatagramSocket來發(fā)送這個包,要接收數(shù)據(jù)。到底java如何一步步實現(xiàn)UDP通信呢?
- 首先創(chuàng)建一個DatagramSocket實例,指定本地端口號,并可以有選擇地指定本地地址,此時,服務器已經準備好從任何客戶端接收數(shù)據(jù)報文;
- 使用DatagramSocket實例的receive()方法接收一個DatagramPacket實例,當receive()方法返回時,數(shù)據(jù)報文就包含了客戶端的地址,這樣就知道了回復信息應該發(fā)送到什么地方;
- 使用DatagramSocket實例的send()方法向服務器端返回DatagramPacket實例。
了解了UDP通信的基本原理之后,下面分別從Android端作為發(fā)送方,PC端作為接收方和Android端作為接收方,PC端作為發(fā)送方來具體說明實現(xiàn)思路和過程。
無論是發(fā)送方還是接收方,消息的發(fā)送和接收都要放在單獨的線程中執(zhí)行,以免出現(xiàn)消息阻塞。需要特別注意的是,Android端作為接收方要考慮Android的UI主線程和子線程,也就是說,UI主線程里不能有任何延時的操作。所以Android端接收到的消息要放進消息隊列中,用handle消息處理機制,在子線程中接收消息并處理。
Android端:
public class MainActivity extends AppCompatActivity {
private EditText etmessage;
private TextView showmessage;
private String message;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnsend = this.findViewById(R.id.btnSend);
etmessage = this.findViewById(R.id.etMessage);
showmessage = this.findViewById(R.id.showMessage);
Log.v("shoudao", "msg");
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
receiveMsg();
} catch (IOException e) {
}
}
}
}).start();
btnsend.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
String s = etmessage.getText().toString();
sendMsg(s);
Log.v("client send", s);
} catch (IOException e) {
}
}
}).start();
}
});
}
public void sendMsg(String msg) throws IOException {
Log.v("client send", msg);
DatagramSocket socket = new DatagramSocket(0);
InetAddress host = InetAddress.getByName("192.168.43.171");
byte[] data = msg.getBytes();
DatagramPacket request = new DatagramPacket(data, data.length, host, 9988);
socket.send(request);
}
public void receiveMsg() throws IOException {
Log.v("Android recieve", "start..");
DatagramSocket socket = new DatagramSocket(6666);
Log.v("Android port", "start..");
byte[] container = new byte[10];
DatagramPacket request = new DatagramPacket(container, 10);
Log.v("Android port", "start..");
socket.receive(request);
message = new String(container);
Message msg = handle.obtainMessage();
msg.obj = message;
handle.sendMessage(msg);
Log.v("shou dao xiaoxi", message);
}
private Handler handle=new Handler(){
public void handleMessage(Message msg){
String s=(String)msg.obj;
showmessage.setText(s);
}
};
}
PC端:
public class UDPServer {
public static void main(String[] args) throws IOException {
System.out.println("UDP recive server start...");
DatagramSocket socket=new DatagramSocket(9988);
byte[]container=new byte[10];
DatagramPacket request=new DatagramPacket(container,10);
System.out.println("go");
SendMsg send=new SendMsg();
send.start();
System.out.println("go1");
while(true) {
socket.receive(request);
String address=request.getAddress().toString();
String s = new String(container);
System.out.println("shou dao xiaoxi" + s+"from"+address);
}
}
public class SendMsg extends Thread {
@Override
public void run(){
String message= null;
System.out.println("go2");
try {
message = sendMsg();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("fasong:"+message);
}
public String sendMsg() throws IOException {
String s="run";
DatagramSocket socket = new DatagramSocket(0);
InetAddress host = InetAddress.getByName("192.168.43.1");
byte[] data = s.getBytes();
DatagramPacket request = new DatagramPacket(data, data.length, host, 6666);
socket.send(request);
return s;
}
}
UDP程序在receive()方法處阻塞,直到收到一個數(shù)據(jù)報文或等待超時,由于UDP協(xié)議是不可靠協(xié)議,如果沒有收到DatagramPacket,那么程序將會一直阻塞在receive()方法處,這樣客戶端將永遠都接收不到服務器端發(fā)送回來的數(shù)據(jù),但是又沒有任何提示。為了避免這個問題,除了放在線程中解決之外,我們也可以在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,并指定重發(fā)數(shù)據(jù)報的次數(shù),如果每次阻塞都超時,并且重發(fā)次數(shù)達到了設置的上限,則關閉客戶端。
這樣就基本實現(xiàn)了基于UDP的Android端與PC端的簡單通信,與君共勉。