案例GitHub地址
創(chuàng)建UDP服務(wù)端
-
new一個Module:
模塊名為:sample
-
創(chuàng)建一個package镰矿,名為udp:
InetAddress.InetAddressHolder源碼:
InetAddressHolder(String hostName, int address, int family) {
this.originalHostName = hostName;
this.hostName = hostName;
this.address = address;
this.family = family;
}
- 在包下創(chuàng)建UdpServer.class供汛,編寫:
package com.lwp.sample.udp;
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.util.Scanner;
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/27 17:08
* desc :
* </pre>
*/
public class UdpServer {
private InetAddress mInetAddress;
private int mPort = 7777;//盡可能用5000以后的
private DatagramSocket mSocket;
private Scanner mScanner;
//構(gòu)造方法中初始化
public UdpServer() {
try {
mInetAddress = InetAddress.getLocalHost();
//傳入权均,設(shè)置好本服務(wù)器ip 和 本服務(wù)程序指定的端口,虛擬“鏈接”的服務(wù)器一端
mSocket = new DatagramSocket(mPort, mInetAddress);
//用于控制面板的輸入
mScanner = new Scanner(System.in);
mScanner.useDelimiter("\n");//指定控制面板的輸入以換行來結(jié)束
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
}
}
public void start() {
//讓Server端持續(xù)運行
while (true) {
try {
//類似于緩存區(qū)的一個字節(jié)數(shù)組
//UDP每次通信的數(shù)據(jù)大小受限制
//限制就來自于服務(wù)端傳給DatagramPacket的字節(jié)數(shù)組
//因為UDP是通過DatagramPacket封裝數(shù)據(jù)的钞脂,
// 而DatagramPacket的創(chuàng)建必須傳入一個字節(jié)數(shù)組燥狰,這個數(shù)組便是通信數(shù)據(jù)包的大小限制
//
//這里指定的是1024棘脐,也就是客戶端發(fā)送過來的數(shù)據(jù)包,
// 每次不能超過1024個字節(jié)龙致,1byte = 8bit
byte[] buf = new byte[1024];
//接收客戶端數(shù)據(jù)
DatagramPacket receivedPacket = new DatagramPacket(buf, buf.length);
//如果沒有數(shù)據(jù)包到來的話蛀缝,程序會一直阻塞在receive()這里,receive()會阻塞目代,
// 如果有一個客戶端發(fā)送一個數(shù)據(jù)包到這個程序中屈梁,
// 程序就會去執(zhí)行receive()方法,將接收到的數(shù)據(jù)傳輸?shù)絩eceivedPacket中進而傳輸給receive()
mSocket.receive(receivedPacket);
//所以如果程序能往下走榛了,就證明接收到數(shù)據(jù)了
//拿到客戶端地址在讶、端口號、發(fā)送過來的數(shù)據(jù)
InetAddress address = receivedPacket.getAddress();
int port = receivedPacket.getPort();
byte[] data = receivedPacket.getData();
String clientMsg = new String(data, 0, data.length);//把接收到的字節(jié)數(shù)據(jù)轉(zhuǎn)換成String
//打印客戶端信息和發(fā)送過來的數(shù)據(jù)
System.out.println("address = " + address +
", port = " + port + ",(Client's) msg = " + clientMsg);
/*
讀取Terminal的輸入
next()也是阻塞的霜大,監(jiān)聽Terminal輸入(消息+回車)
給客戶端返回數(shù)據(jù),
返回的數(shù)據(jù)我們希望可以在控制面板Terminal上寫构哺,
寫完按Enter鍵完成
*/
String returnedMsg = mScanner.next();
byte[] returnedMsgBytes = returnedMsg.getBytes();//將String轉(zhuǎn)換成byte數(shù)組
//getSocketAddress中包含getAddress(), getPort(),即包含地址跟數(shù)組
//下面把需要返回給客戶端的數(shù)據(jù)封裝成一個DatagramPacket
DatagramPacket sendPacket = new DatagramPacket(returnedMsgBytes,
returnedMsgBytes.length, receivedPacket.getSocketAddress());
mSocket.send(sendPacket);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new UdpServer().start();
}
}
如此便完成了UDP Server的代碼的編寫战坤,
相對比較簡單曙强,
涉及到的API就是以上所說的DatagramSocket
以及DatagramPacket
,
接收途茫、發(fā)送數(shù)據(jù)時候碟嘴,
都要提前封裝一個DatagramPacket
對象检访,
接收時的封裝傳入的參數(shù):緩存字節(jié)數(shù)組引用
及其長度
掘托;
發(fā)送時的封裝傳入的參數(shù):緩存字節(jié)數(shù)組引用
及其長度
、封裝了客戶端(發(fā)送目的地)ip季俩、port
的InetAddress對象
边败;
然后通過receive()
和send()
操作即可袱衷;
創(chuàng)建UDP客戶端
先創(chuàng)建java文件,調(diào)試完畢之后笑窜,再移植到Android上來致燥;
-
udp包下,創(chuàng)建一個
UdpClient
:
package com.lwp.sample.udp;
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.util.Scanner;
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/28 15:20
* desc :
* </pre>
*/
public class UdpClient {
/**
* 指定Server的 ip 和 port
*/
private String mServerIp = "***";
private int mServerPort = 7777;
private InetAddress mServerAddress;
/**
* 通信用的Socket
*/
private DatagramSocket mSocket;
private Scanner mScanner;
//構(gòu)造方法中初始化
public UdpClient() {
try {
/*
直接實例化一個默認的Socket對象即可排截,
因為我們不需要像服務(wù)端那樣把別的Client接入過來嫌蚤,
不必特別明確指定 自己的ip和port(服務(wù)程序),6习痢M阎ā!H险帧O潋稹!!;掳帷Q榔啊!
因為這里是Client间校,是數(shù)據(jù)請求獲取方矾克,不是數(shù)據(jù)提供方,c咀恪P哺健!滓彰!
所以只需要一個默認的Socket對象
來進行send 和 receive 即可
*/
mSocket = new DatagramSocket();
mServerAddress = InetAddress.getByName(mServerIp);
mScanner = new Scanner(System.in);
mScanner.useDelimiter("\n");
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public void start() {
while (true) {
try {
/*
完成向服務(wù)端發(fā)送數(shù)據(jù)
*/
String clientMsg = mScanner.next();
byte[] clientMsgBytes = clientMsg.getBytes();
/*
封裝數(shù)據(jù)包控妻,傳入數(shù)據(jù)數(shù)組以及服務(wù)端地址、端口號
*/
DatagramPacket clientPacket = new DatagramPacket(clientMsgBytes,
clientMsgBytes.length, mServerAddress, mServerPort);
mSocket.send(clientPacket);
/*
接收服務(wù)端數(shù)據(jù)
*/
byte[] buf = new byte[1024];
DatagramPacket serverMsgPacket = new DatagramPacket(buf, buf.length);
mSocket.receive(serverMsgPacket);
//拿到服務(wù)端地址揭绑、端口號饼暑、發(fā)送過來的數(shù)據(jù)
InetAddress address = serverMsgPacket.getAddress();
int port = serverMsgPacket.getPort();
byte[] data = serverMsgPacket.getData();
String serverMsg = new String(data, 0, data.length);//把接收到的字節(jié)數(shù)據(jù)轉(zhuǎn)換成String
//打印服務(wù)端信息和發(fā)送過來的數(shù)據(jù)
System.out.println("(Server's) msg = " + serverMsg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new UdpClient().start();
}
}
private String mServerIp = "***";
這里,我把代碼的內(nèi)容略去了洗做,
這部分使用的是本機的網(wǎng)絡(luò)IPV4的ip,
查看本機ip方法傳送門彰居,
為何用的是本機ip
呢诚纸,因為UdpServer
中:
mInetAddress = InetAddress.getLocalHost();
處,
設(shè)置的服務(wù)端ip
正是本機ip
- 開始測試:
進入UdpServer.class宿崭,右鍵啟動main程序:注意陈惰,
程序運行第二次的時候畦徘,
如果第一次運行沒有對鏈接進行關(guān)閉,
則第一次運行的端口號
會被占用
抬闯,
導(dǎo)致第二次相關(guān)程序運行時Socket對象
無法實例化
井辆,
以致于Socket對象
為空(NULL)
,
程序報空指針
的錯誤溶握!為了避免這種情況杯缺,
可以在不需要Server
的時候,將Server
程序暫停睡榆;
也可以在更改程序之后萍肆,使PC睡眠再重新打開,亦可刷新port
占用胀屿;
或者直接為更改后的程序指定新的port
塘揣,當然這種方法不推薦;
同理啟動UdpClient.class的main程序亲铡,啟動完畢彈出底部界面如下:
Client端敲一個"nihao I am Client",然后回車:
切換到UdpServer終端,可以看到接收到信息:
反復(fù)測試:
移植客戶端
- 將UDP客戶端程序移植到Android中奖蔓;
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/id_btn_send"
android:text="Send"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/id_et_msg"
android:layout_toLeftOf="@+id/id_btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_below="@+id/id_et_msg"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/id_tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</RelativeLayout>
- 建一個Package名為biz赞草,用于打包業(yè)務(wù)代碼:
-
直接copy那UdpClient.class,粘貼在biz包下锭硼,
改名為UdpClientBiz:
-
編寫之:
public class UdpClientBiz {
/**
* 指定Server的 ip 和 port
*/
private String mServerIp = "172.18.1.59";
private int mServerPort = 7778;
private InetAddress mServerAddress;
private Handler mUIHandler = new Handler(Looper.getMainLooper());
/**
* 通信用的Socket
*/
private DatagramSocket mSocket;
//構(gòu)造方法中初始化
public UdpClientBiz() {
try {
/*
直接實例化一個默認的Socket對象即可房资,
因為我們不需要像服務(wù)端那樣把別的Client接入過來,
不必特別明確指定 自己的ip和port(服務(wù)程序)檀头,:湟臁!J钍肌4疃馈!@染怠Q栏巍!`推印配椭!
因為這里是Client,是數(shù)據(jù)請求獲取方雹姊,不是數(shù)據(jù)提供方股缸,!Vǔ6匾觥!
所以只需要一個默認的Socket對象
來進行send 和 receive 即可
*/
mSocket = new DatagramSocket();
mServerAddress = InetAddress.getByName(mServerIp);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
/*
需求:客戶端接收Server端返回的數(shù)據(jù)歧杏,并展示在控件上
實現(xiàn):send 方法綁定一個接口
ps:這里的回調(diào)機制實現(xiàn)其實還有一種寫法镰惦,
就是另外單獨再起一個setListener方法來綁定Listener ,
但是這樣做不太符合這里具體的場景——每個 服務(wù)端 return 回來的數(shù)據(jù)
都是跟每個 客戶端 send 出去的數(shù)據(jù)相關(guān)聯(lián)對應(yīng)的犬绒;
單獨使用setListener 的方式旺入,看不到這個關(guān)聯(lián)的邏輯,
所以這里直接把Listener 作為sendMsg 的必要形參懂更,形成關(guān)聯(lián)邏輯
以及綁定關(guān)系——必須先 sendMsg 之后才能 returnMsg(receiveMsg)
*/
public interface onMsgReturnedListener{
void onMsgReturned(String msg);
/*
Handle Exception
如果是異步的方法調(diào)用:可以把Exception 通過 Listener 給回調(diào)出去
如果是同步的方法調(diào)用:盡可能不要在方法中進行try catch眨业,
最好是將其throw 出去,
或者catch 之后 封裝下錯誤類型再將其throw 出去沮协,
即一定要讓調(diào)用者能知道這個異常龄捡;
這里是異步調(diào)用
*/
void onError(Exception ex);
}
public void sendMsg(final String msg, final onMsgReturnedListener listener) {
new Thread() {
@Override
public void run() {
try {
//信息轉(zhuǎn)型
byte[] clientMsgBytes = msg.getBytes();
/*
封裝數(shù)據(jù)包,傳入數(shù)據(jù)數(shù)組以及服務(wù)端地址慷暂、端口號
*/
DatagramPacket clientPacket = new DatagramPacket(clientMsgBytes,
clientMsgBytes.length, mServerAddress, mServerPort);
mSocket.send(clientPacket);
/*
接收服務(wù)端數(shù)據(jù)
*/
byte[] buf = new byte[1024];
DatagramPacket serverMsgPacket = new DatagramPacket(buf, buf.length);
mSocket.receive(serverMsgPacket);
//拿到服務(wù)端地址聘殖、端口號晨雳、發(fā)送過來的數(shù)據(jù)
InetAddress address = serverMsgPacket.getAddress();
int port = serverMsgPacket.getPort();
byte[] data = serverMsgPacket.getData();
final String serverMsg = new String(data, 0, data.length);//把接收到的字節(jié)數(shù)據(jù)轉(zhuǎn)換成String
/*
以上是信息的發(fā)送和接收,寫在sendMsg 方法體中奸腺,名副其實
以下是對接收數(shù)據(jù)的處理餐禁,通過回調(diào)處理
*/
//這里是子線程,
// 但是 Handler 已同 MainLooper 進行綁定突照,
// 則利用這個handle 去更新UI帮非,等同于切回主線程更新UI
mUIHandler.post(new Runnable() {
@Override
public void run() {
//數(shù)據(jù)借助回調(diào)外傳
//“切回了”主線程,在調(diào)用的時候讹蘑,接收數(shù)據(jù)之后才能更新UI
listener.onMsgReturned(serverMsg);
}
});
} catch (final Exception e) {
mUIHandler.post(new Runnable() {
@Override
public void run() {
//異衬┛回調(diào)
listener.onError(e);
}
});
}
}
}.start();
}
public void onDestroy() {
if(mSocket != null){
mSocket.close();
}
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private EditText mEtMsg;
private Button mBtnSend;
private TextView mTvContent;
private UdpClientBiz mUdpClientBiz = new UdpClientBiz();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
mEtMsg = findViewById(R.id.id_et_msg);
mBtnSend = findViewById(R.id.id_btn_send);
mTvContent = findViewById(R.id.id_tv_content);
mBtnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg = mEtMsg.getText().toString();
if (TextUtils.isEmpty(msg)) {
return;
}
appendMsgToContent("client:" + msg);
//發(fā)送后清除編輯框文本
mEtMsg.setText("");
mUdpClientBiz.sendMsg(msg, new UdpClientBiz.onMsgReturnedListener() {
@Override
public void onMsgReturned(String msg) {
//更新UI
appendMsgToContent("server:" + msg);
}
@Override
public void onError(Exception ex) {
ex.printStackTrace();
}
});
}
});
}
private void appendMsgToContent(String msg) {
mTvContent.append(msg + "\n");
}
/*
回收資源
*/
@Override
protected void onDestroy() {
super.onDestroy();
mUdpClientBiz.onDestroy();
}
}
然后記得添加網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET"/>
-
測試:
啟動UdpServer:
啟動sample模塊;
反復(fù)測試:
本例用的服務(wù)器ip是真實的ipv4的網(wǎng)絡(luò)本機ip座慰,
所以將客戶端安裝在可以聯(lián)網(wǎng)的真機
上
也是可以跑
的陨舱;
只是這里為了錄圖方便就跑在虛擬機調(diào)試而已。
參考自版仔,慕課網(wǎng)游盲。就業(yè)班