2018.08.02
最近做的项目要求为:一端为电视端,一端为手机端,实现电视端与手机端通过UDP进行通信。
解释:就是两个机器在同一个局域网内(即两端连接的同一个WiFi),可以通过UDP,一端将信息通过局域网散播出去后,在同一个局域网内的所有设备都能接收到信息,只不过,另一端对接收到的信息进行处理,这样就完成了UDP通信。发送信息方只负责发送信息,另一方只负责接收信息,两方还可以互相发送和接收信息,反正互不干扰,谁都不会管对方是否接收到信息。
写的比较好的文章:https://blog.csdn.net/ITermeng/article/details/73482669?locationNum=9&fps=1。
想要了解更多关于UDP与TCP的基础知识,请看https://www.jianshu.com/p/cc62e070a6d2。
UDP通信步骤:
1、建立连接的IP地址和端口号,IP即是你想要发送的地址,端口号则要选用闲置端口就是向8000~9000这样的端口号。
2、建立DatagramSocket sendSocket/receiveSocket ,用于发送和接受数据报文包。
3、创建报文包DatagramPacket sendPackage/receivePackage,将发送或接收的内容打包。
4、通过sendSocket.send(sendPackage)或者receiveSocket.receive(receivePackage),则完成了基于Socket的UDP通信。
注意事项:
1.同一端如果既要发送消息又想接收消息,一定要将发送的socket和接收的socket分开。不能用一个socket既接收信息,又发送信息,这样一定会造成阻塞。
2.接收线程中,一定要有 while(true){ if(标志何时结束) 代码块 } 这样的方式,不然,在接收信息时,一定会造成阻塞,就是只能接收到一次信息。
3.本来想开一个线程进行接收数据和发送数据,真的是太混乱了,所以大家还是开两个线程吧。。。。
具体代码:
服务端:
package com.example.admin.learnUdpCommunicateServer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhou.jn
*/
public class UdpSocketServerActivity extends Activity {
private static final String TAG = "SocketAutoConnectServer";
private static String IP;
private static int BROADCAST_PORT = 9999;
private static String BROADCAST_IP = "255.255.255.255";
private InetAddress inetAddress = null;
private BroadcastThread broadcastThread;
private DatagramSocket sendSocket = null;
private DatagramSocket receiveSocket = null;
private Button sendUDPBrocast;
private volatile boolean isRuning = true;
private TextView ipInfo;
private Button btn_send;
private EditText et_sendInfo;
private String sendContent;
private TextView tv_receive;
private List ipList = new ArrayList<>();
private Button btnClear;
private ReceiveThread receiveThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
initIp();
initThread();
try {
inetAddress = InetAddress.getByName(BROADCAST_IP);
} catch (Exception e) {
e.printStackTrace();
}
}
private void initThread() {
broadcastThread = new BroadcastThread();
broadcastThread.start();
receiveThread = new ReceiveThread();
receiveThread.start();
}
private void initIp() {
//Wifi状态判断
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager.isWifiEnabled()) {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
IP = getIpString(wifiInfo.getIpAddress());
ipInfo.append(IP);
System.out.println("IP IP:" + IP);
}
}
private void initView() {
ipInfo = (TextView) findViewById(R.id.ip_info);
sendUDPBrocast = (Button) findViewById(R.id.sendUDPBrocast);
tv_receive = findViewById(R.id.tv_receive);
et_sendInfo = findViewById(R.id.et_sendContent);
btn_send = findViewById(R.id.btn_sendInfo);
try {
receiveSocket = new DatagramSocket(BROADCAST_PORT);
} catch (SocketException e) {
e.printStackTrace();
}
btn_send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
sendMessageToThread(broadcastThread.mhandler);
}
});
btnClear = findViewById(R.id.btnClear);
btnClear.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
tv_receive.setText("");
}
});
sendUDPBrocast.setOnClickListener(new SendUDPBrocastListener());
}
private void sendMessageToThread(Handler mhandler) {
Message msg = Message.obtain();
sendContent = et_sendInfo.getText().toString();
msg.obj = sendContent;
msg.what = 1;
mhandler.sendMessage(msg);
}
/**
* 将获取到的int型ip转成string类型
*/
private String getIpString(int i) {
return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "."
+ ((i >> 16) & 0xFF) + "." + (i >> 24 & 0xFF);
}
@SuppressLint("HandlerLeak")
Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1: {
if (!msg.obj.equals(IP)) {
if (!isExistIp(msg.obj.toString())) {
ipList.add(msg.obj.toString());
}
tv_receive.append(msg.obj.toString() + " 接收到信息 " + "\n");
}
}
break;
default:
break;
}
}
};
public class SendUDPBrocastListener implements OnClickListener {
@Override
public void onClick(View v) {
if (isRuning) {
isRuning = false;
sendUDPBrocast.setText("发送广播");
System.out.println("现在停止广播..");
} else {
isRuning = true;
sendUDPBrocast.setText("停止广播");
System.out.println("现在发送广播..");
}
}
}
public class BroadcastThread extends Thread {
private Handler mhandler = null;
@SuppressLint("HandlerLeak")
@Override
public void run() {
Looper.prepare();
mhandler = new Handler() {
@Override
public void handleMessage(Message msg) {
String message = (String) msg.obj;
byte[] data = message.getBytes();
DatagramPacket dpSend = null;
dpSend = new DatagramPacket(data, data.length, inetAddress, BROADCAST_PORT);
try {
double start = System.currentTimeMillis();
for (int i = 0 ; i < 15; i ++) {
sendSocket = new DatagramSocket();
sendSocket.send(dpSend);
sendSocket.close();
Thread.sleep(80);
Log.i(TAG, "sendMessage: data " + new String(data));
}
double end = System.currentTimeMillis();
double times = end - start;
Log.i(TAG, "receive: executed time is : "+ times +"ms");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Looper.loop();
}
}
private boolean isExistIp(String revIp) {
if (ipList != null && ipList.size() > 0) {
for (String ip : ipList) {
if (ip != revIp) {
return false;
}
}
}
return false;
}
private class ReceiveThread extends Thread {
@Override
public void run() {
while (true) {
if (isRuning) {
byte[] receiveData = new byte[1024];
DatagramPacket dpReceive = null;
ipList.clear();
dpReceive = new DatagramPacket(receiveData, receiveData.length);
try {
receiveSocket.receive(dpReceive);
} catch (IOException e) {
e.printStackTrace();
}
String recIp = dpReceive.getAddress().toString().substring(1);
if (dpReceive != null) {
Message revMessage = Message.obtain();
revMessage.what = 1;
revMessage.obj = recIp;
Log.i(TAG, "handleMessage: receive ip" + recIp);
myHandler.sendMessage(revMessage);
}
}
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
isRuning = false;
receiveSocket.close();
System.out.println("UDP Server程序退出,关掉socket,停止广播");
finish();
}
}
客户端代码:
package com.example.admin.learnudpcommunicateclent;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.admin.udpclient.R;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhou.jn
*/
public class SocketClientActivity extends Activity {
private static int BROADCAST_PORT = 9999;
private static final String TAG = "SocketConnectActivity";
private TextView ipInfo;
private static String IP;
private boolean isRunning = true;
private Button btnBack;
private DatagramSocket receiveSocket = null;
private DatagramSocket sendSocket = null;
private DatagramPacket dpReceive = null;
private SendThread sendThread;
private String previousContent = new String();
private Button btnClear;
private String receiveIp;
private String sendIp;
@SuppressLint("HandlerLeak")
Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Log.i(TAG, "run handleMessage: ");
ipInfo.append(msg.obj.toString() + "\n");
/**通过确认该客户端已经接收到信息后,再将自己的ip号码发送出去*/
new Thread(){
@Override
public void run() {
try {
Thread.sleep(1500);
/**开一个将前一次接收的内容置为空的线程,解决发送端两次发送相同的信息,接收端他可能就不会做处理的问题*/
previousContent = " ";
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
sendFeedBackToServer(sendThread.mHandler, msg.obj.toString());
break;
case 2:
ipInfo.append(msg.obj.toString());
default:
break;
}
}
};
private void sendFeedBackToServer(Handler mHandler, String ip) {
Message msg = new Message();
msg.obj = ip;
msg.what = 1;
mHandler.sendMessage(msg);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ipInfo = findViewById(R.id.ip_info);
btnBack = findViewById(R.id.btnBack);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
btnClear = findViewById(R.id.btnClear);
btnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ipInfo.setText("");
}
});
ReceiveThread receiveThread = new ReceiveThread();
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager.isWifiEnabled()) {
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
IP = getIpString(wifiInfo.getIpAddress());
System.out.println("本机IP is :" + IP);
}
try {
receiveSocket = new DatagramSocket(BROADCAST_PORT);
sendThread = new SendThread();
sendThread.start();
receiveThread.start();
} catch (Exception e1) {
e1.printStackTrace();
}
}
/**
* 将获取到的int型ip转成string类型
*/
private String getIpString(int i) {
return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "."
+ ((i >> 16) & 0xFF) + "." + (i >> 24 & 0xFF);
}
private class ReceiveThread extends Thread {
@SuppressLint("LongLogTag")
@Override
public void run() {
while (true) {
if (isRunning) {
String receiveContent = null;
byte[] buf = new byte[1024];
dpReceive = new DatagramPacket(buf, buf.length);
try {
receiveSocket.receive(dpReceive);
receiveContent = new String(buf, 0, dpReceive.getLength());
Log.i(TAG, "run: receive message " + receiveContent);
Log.i(TAG, "run: " + dpReceive.getAddress().toString());
receiveIp = dpReceive.getAddress().toString().substring(1);
if (receiveIp != IP) {
sendIp = receiveIp;
}
Log.i(TAG, "run:1 previousContent " + previousContent + " receiveContent " + receiveContent);
if (!previousContent.equals(receiveContent) && !IP.equals(receiveIp)) {
Message msg = myHandler.obtainMessage();
msg.obj = "服务器:" + sendIp + "发送的内容为:" + receiveContent;
msg.what = 1;
myHandler.sendMessage(msg);
}
previousContent = receiveContent;
Log.i(TAG, "run:2 previousContent " + previousContent + " receiveContent " + receiveContent);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private class SendThread extends Thread {
private Handler mHandler;
@SuppressLint("HandlerLeak")
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
DatagramPacket dpSend = null;
byte[] ip = IP.getBytes();
try {
if (receiveIp != IP) {
InetAddress inetAddress = InetAddress.getByName(sendIp);
dpSend = new DatagramPacket(ip, ip.length, inetAddress, BROADCAST_PORT);
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
try {
String sendData = new String(ip, 0, dpSend.getLength());
sendSocket = new DatagramSocket();
sendSocket.send(dpSend);
Log.i(TAG, "run: send message : " + sendData);
sendSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
};
Looper.loop();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
receiveSocket.close();
isRunning = false;
System.out.println("UDP Client程序退出,关掉socket,停止广播");
finish();
}
}
刚开始做这个项目的疑惑及解决办法:
问题一:用一个DatagramSocket进行发送并接收信息,会不会有问题。
答:确实很有问题,所以一定要开(1+n)个DatagramSocket啊,同志们!一个DatagramSocket用于接收信息,另外n个是每次发送新消息的时候就新建一个DatagramSocket进行发送,发送后进行关闭。
问题二:当发送信息的时候,是想着每发送一个信息,就要关闭一个信息。可我想着发送完信息关闭后,接着又要发送消息怎么办?
答:每次新建DatagramSocket再进行关闭就可以啦!
问题三:想在客户端接收到信息后,发送给服务端告诉他我接收到信息了~,可是接收方只要监听端口就可以,不用IP的,那要如何获取接收端的IP呢?
答:通过接收数据包的dpReceive.getAddress()可以获取,并且它的格式为“/10.0.12.53”,所以要取子字符串subString(1)进行获取。
问题四:接收信息的时候咋能接收到自己发送的信息?
答:确实可以接收到自己发送的信息。。。。所以需要自行进行过滤处理,不要接收自己发送的信息,不然是不是会显得很傻。。
项目讲解:
服务器端:我怕客户端接收不到信息,所以我就发了15次信息,并且每次间隔80ms,这样客户端再接收不到信息,俺也没办法了,每次发送信息的时间大约为1200多ms,这么长时间总能接到一条吧,老兄!
发送端拨号为4个255,听说这样是可以发送给局域网,反正IP是个随意的大数就行,比如244.244.0.1都行~
客 户 端:服务器这老兄给我发这么多条相同的信息,我还每条都处理,是不是很傻!所以就进行判断我是不是重复接收了客户端的相同信息,如果相同,处理一次就够了。。。。
ps:UDP中没什么客户端,服务端,只是接收方,发送方而已,我只是这么称呼他们。。。
源码地址:https://download.csdn.net/download/weixin_37716758/10582295(优化的)
源码地址:https://download.csdn.net/download/weixin_37716758/10580690(带有APK)