系列文章:
Android Socket通信(一) – 初識與相遇
Android Socket通信(二) --UDP,單播,廣播和多播(組播)
Android Socket通信(三) – TCP 配置和傳遞基礎數據
Android Socket通信(四) – UDP與TCP結合傳輸數據
在這篇文章中,你將學習到:
- 學習通過 UDP 獲取不同設備的ip和端口
- 通過 TCP 進行相互通信
- 實踐一個案例
今天要實現的效果:
一、案例分析
在前面幾章中,我們已經學習了 tcp 和 udp 基礎的方法;這一章中,我們對它進行一個總結,設想一個案例,即我想與B設備相互通信;
但是並不知道B設備的ip和端口,但我們可以通過 udp 發送廣播,找到設備 ip 和端口,再進行 tcp 通信;大概流程如下:
客戶端端通過 udp 廣播 搜索設備,如果A或者B設備對廣播的協議有響應,則發送自己的ip和端口給客戶端,客戶端拿到之後,再與之進行雙向通信。
二、進行UDP通過獲取IP
在第二章已經學習了 UDP 廣播的用法,代碼改動不大,發送廣播那裏,我們進行一個數據包裝,採用 ByteBuffer,畢竟我們不想讓每個人都與之通信。
客戶端中發送廣播:
/**
* 發送廣播
* @throws IOException
*/
public static void sendBroadcast() throws IOException {
DatagramSocket socket = new DatagramSocket();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
//發送特定數據
byteBuffer.putInt(Constans.CMD_BROAD);
byteBuffer.putInt(Constans.BROADCAST_PORT);
DatagramPacket packet = new DatagramPacket(byteBuffer.array(),
byteBuffer.position(),
InetAddress.getByName(Constans.BROADCAST_IP),
Constans.PORT);
socket.send(packet);
socket.close();
}
通過廣播,發送 cmd 和 要回送的 port ;接着,在服務端中,解析相應數據:
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes,0,length);
int cmd = byteBuffer.getInt();
int responsePort = byteBuffer.getInt();
System.out.println("客戶端: " + ip + "\tport: " + port +"\t回送接口: "+responsePort);
/**
* 給客戶端發送消息 cmd 必須匹配
*/
if (Constans.CMD_BROAD == cmd) {
ByteBuffer buffer = ByteBuffer.allocate(256);
buffer.putInt(Constans.CMD_BRO_RESPONSE);
buffer.putInt(Constans.TCP_PORT);
buffer.put(("我是設備: "+ UUID.randomUUID().toString()).getBytes());
System.out.println(buffer.position());
DatagramPacket receivePacket = new DatagramPacket(buffer.array(),
buffer.position(),
packet.getAddress(), //目標地址
responsePort); //廣播端口
socket.send(receivePacket);
}
當 cmd 是 CMD_BROAD 纔回送數據,然後把 tcp 的端口也回送回去;然後回到 client 中的 listener 去監聽:
ByteBuffer buffer = ByteBuffer.wrap(bytes,0,length);
int cmd = buffer.getInt();
if (Constans.CMD_BRO_RESPONSE == cmd) {
int tcpPort = buffer.getInt();
int pos = buffer.position();
String msg = new String(bytes,pos,length - pos);
System.out.println("監聽到: " + ip + "\ttcpPort: " + tcpPort + "\t信息: " + msg+" "+length+" "+pos);
if (msg.length() > 0) {
DeviceInfo device = new DeviceInfo(ip, tcpPort, msg);
devices.add(device);
}
//成功收取到一份
searchLatch.countDown();
}
打印如下:
三、進行 TCP 雙向通信
拿到 TCP 的設備之後,就可以進行通信了,代碼基於第一章。
首先,我們先改動 客戶端,讓發送和接收分開:
發送
/**
* 發送數據
* @param socket
*/
static void sendData(Socket socket){
try {
//終端輸入流
BufferedReader osReader = new BufferedReader(new InputStreamReader(System.in));
PrintStream ps = new PrintStream(socket.getOutputStream());
boolean isFinish = false;
do {
String msg = osReader.readLine();
ps.println(msg);
if ("bye".equalsIgnoreCase(msg)){
isFinish = true;
}
}while (!isFinish);
osReader.close();
ps.close();
} catch (IOException e) {
e.printStackTrace();
}
}
接收
/**
* 監聽數據
*/
static class ReaderListener extends Thread{
Socket socket;
boolean isFinish = false;
public ReaderListener(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
try {
BufferedReader responseReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
do {
String response = responseReader.readLine();
//當服務器關閉連接了,則 IO 不再阻塞,會返回null
if (response == null){
System.out.println("連接斷開");
break;
}else {
System.out.println(response);
}
}while (!isFinish);
responseReader.close();
} catch (IOException e) {
// e.printStackTrace();
}finally {
exit();
}
}
public void exit(){
isFinish =true;
if (socket != null){
try {
socket.close();
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這樣,我們就能監聽服務端的信息了,接着,我們需要改變一下服務端的;當終端也輸入時,發送給其他客戶端。
這裏,我們也把發送和接收分開:
爲了能併發,我們這裏都是採用線程去處理:
發送
/**
* 發送數據
*/
class writerHandle {
private PrintStream ps ;
private ExecutorService executorService ;
private boolean isFinish = false;
public writerHandle(OutputStream os) {
ps = new PrintStream(os);
executorService = Executors.newSingleThreadExecutor();
}
public void exit(){
isFinish = true;
ps.close();
executorService.shutdown();
}
public void sendMsg(String msg){
executorService.execute(new sendSync(msg));
}
class sendSync implements Runnable{
String str;
public sendSync(String str) {
this.str = str;
}
@Override
public void run() {
if (writerHandle.this.isFinish){
return;
}
ps.println(str);
}
}
}
接收:
/**
* 數據讀取監聽類
*/
class ReaderListener extends Thread{
InputStream inputStream;
boolean isFinish = false;
public ReaderListener(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public void run() {
super.run();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
do {
String msg = br.readLine();
if (msg != null){
System.out.println("client: "+msg);
}else{
System.out.println("連接已斷開");
break;
}
} while (!isFinish);
} catch (IOException e) {
// e.printStackTrace();
}finally {
exit();
}
}
public void exit(){
isFinish = true;
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這樣就完成了雙向通信了。