最近在和同學開發一款app,作為課程大作業。其中,涉及到udp socket (多播) 的發送和接收、tcp socket 的發送和接收。作為一個Java的門外漢,在簡單地看了一些理論地資料之后,實際編程中遇到了不少問題。然后,又在網上大搜這方面的博客,找來找去,其實大家寫的東西基本都一樣,因為規則已經訂好了。網上的代碼不全,又有一些錯漏,讓我走了很多彎路,無數次推倒代碼重寫,debug,終於調出了一個實際可運行的版本。 希望初學的童鞋看到我的這篇博客能少走一些彎路.
-----------------------
轉載請注明出處:
http://www.cnblogs.com/zhangzph/p/4475962.html
-----------------------
在給出代碼之前,先簡單介紹一下我的代碼在做什么。
代碼邏輯:
本機發送udp廣播
本機開啟線程,監聽來自別的機器的udp廣播,顯示信息。 然后,對udp來源發送tcp連接
接收來自別的機器的tcp連接,並顯示信息
(這里的udp廣播,我使用udp多播代替了,多播具有廣播的所有優點,而且有更少的缺點,實現上也比較簡單,這里就不再過多地介紹了)
具體ui操作:

start 按鈕用來啟動udp 多播,stop按鈕停止發送 (實際上,由於start 按鈕按下之后只發送一次udp多播,stop按鈕只是用於setEnabled操作)下面有兩個TextView,內容為send的TextView 顯示--本機發送 tcp socket 的信息; 內容為receive的TextView 顯示--本機接收來自別的機器的udp socket 和 tcp socket 的信息.
幾個需要注意的地方:
1. Android Manifest 權限設置、sdk版本信息:
本文所涉及到的這些功能需要獲取 Android 的一些權限,下面是我的權限和版本信息
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
上面 條目 uses-sdk中的信息,需要在build.gradle文件中同步。

2. 注意udp廣播,和udp廣播監聽需要綁定同一個端口
3. 其他的有關IDE抽風的問題,比如我的Android Studio,有時候你修改了代碼,重新把程序燒進手機的時候,它竟然會用緩存中代碼的老版本來燒程序。。。 還有,有時候project加載太慢,程序崩潰之后,logcat好長時間都不出錯誤信息,嚴重影響debug。
4. 建議使用android sdk版本比較新的手機進行測試。 我測試的時候,用一部4.4和5.1的成功了。混合另外一部4.0.x的則有時候不太靈通。
github上的項目鏈接:
https://github.com/zhangpzh/Anjay
主要代碼:
xml 源碼:
<LinearLayout 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" android:orientation="vertical" tools:context=".MyActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_horizontal" > <Button android:id="@+id/start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="start" /> <Button android:id="@+id/stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="150dp" android:text="stop" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_horizontal" > <TextView android:id="@+id/send_information" android:layout_marginTop="50dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="send" android:layout_marginRight="110dp" /> <TextView android:id="@+id/receive_information" android:layout_marginTop="50dp" android:text="receive" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout>
Java 源碼:
(github 上面的代碼已經把各個通信內部類給模塊化了,不再像下面這樣,全都定義在一個Activity里。但是為了集中展示app的功能,下面仍使用一個文件顯示)
package com.example.user.anjay;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.user.anjay.R;
import org.apache.http.conn.util.InetAddressUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
public class MyActivity extends Activity {
private static String LOG_TAG = "WifiMulticastActivity";
Button startBroadCast;
Button stopBroadCast;
TextView send_label;
TextView receive_label;
/* 用於 udpReceiveAndTcpSend 的3個變量 */
Socket socket = null;
MulticastSocket ms = null;
DatagramPacket dp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
startBroadCast = (Button) findViewById(R.id.start);
stopBroadCast = (Button) findViewById(R.id.stop);
send_label = (TextView) findViewById(R.id.send_information);
receive_label = (TextView) findViewById(R.id.receive_information);
send_label.append("\n\n");
receive_label.append("\n\n");
startBroadCast.setOnClickListener(listener);
stopBroadCast.setOnClickListener(listener);
/* 開一個線程接收tcp 連接*/
new tcpReceive().start();
/* 開一個線程 接收udp多播 並 發送tcp 連接*/
new udpReceiveAndtcpSend().start();
}
private View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v == startBroadCast ) {
startBroadCast.setEnabled(false);
stopBroadCast.setEnabled(true);
/* 新開一個線程 發送 udp 多播 */
new udpBroadCast("hi ~!").start();
}
else {
startBroadCast.setEnabled(true);
stopBroadCast.setEnabled(false);
}
}
};
/* 發送udp多播 */
private class udpBroadCast extends Thread {
MulticastSocket sender = null;
DatagramPacket dj = null;
InetAddress group = null;
byte[] data = new byte[1024];
public udpBroadCast(String dataString) {
data = dataString.getBytes();
}
@Override
public void run() {
try {
sender = new MulticastSocket();
group = InetAddress.getByName("224.0.0.1");
dj = new DatagramPacket(data,data.length,group,6789);
sender.send(dj);
sender.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
/*接收udp多播 並 發送tcp 連接*/
private class udpReceiveAndtcpSend extends Thread {
@Override
public void run() {
byte[] data = new byte[1024];
try {
InetAddress groupAddress = InetAddress.getByName("224.0.0.1");
ms = new MulticastSocket(6789);
ms.joinGroup(groupAddress);
} catch (Exception e) {
e.printStackTrace();
}
while (true) {
try {
dp = new DatagramPacket(data, data.length);
if (ms != null)
ms.receive(dp);
} catch (Exception e) {
e.printStackTrace();
}
if (dp.getAddress() != null) {
final String quest_ip = dp.getAddress().toString();
/* 若udp包的ip地址 是 本機的ip地址的話,丟掉這個包(不處理)*/
//String host_ip = getLocalIPAddress();
String host_ip = getLocalHostIp();
System.out.println("host_ip: -------------------- " + host_ip);
System.out.println("quest_ip: -------------------- " + quest_ip.substring(1));
if( (!host_ip.equals("")) && host_ip.equals(quest_ip.substring(1)) ) {
continue;
}
final String codeString = new String(data, 0, dp.getLength());
receive_label.post(new Runnable() {
@Override
public void run() {
receive_label.append("收到來自: \n" + quest_ip.substring(1) + "\n" +"的udp請求\n");
receive_label.append("請求內容: " + codeString + "\n\n");
}
});
try {
final String target_ip = dp.getAddress().toString().substring(1);
send_label.post(new Runnable() {
@Override
public void run() {
send_label.append("發送tcp請求到: \n" + target_ip + "\n");
}
});
socket = new Socket(target_ip, 8080);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
/* 接收tcp連接 */
private class tcpReceive extends Thread {
ServerSocket serverSocket;
Socket socket;
BufferedReader in;
String source_address;
@Override
public void run() {
while(true) {
serverSocket = null;
socket = null;
in = null;
try {
Log.i("Tcp Receive"," new ServerSocket ++++++++++");
serverSocket = new ServerSocket(8080);
socket = serverSocket.accept();
Log.i("Tcp Receive"," get socket ++++++++++++++++");
if(socket != null) {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder sb = new StringBuilder();
sb.append(socket.getInetAddress().getHostAddress());
String line = null;
while ((line = in.readLine()) != null ) {
sb.append(line);
}
source_address = sb.toString().trim();
receive_label.post(new Runnable() {
@Override
public void run() {
receive_label.append("收到來自: "+"\n" +source_address+"\n"+"的tcp請求\n\n");
}
});
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
if (in != null)
in.close();
if (socket != null)
socket.close();
if (serverSocket != null)
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public String getLocalHostIp() {
String ipaddress = "";
try {
Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces();
// 遍歷所用的網絡接口
while (en.hasMoreElements()) {
NetworkInterface nif = en.nextElement();// 得到每一個網絡接口綁定的所有ip
Enumeration<InetAddress> inet = nif.getInetAddresses();
// 遍歷每一個接口綁定的所有ip
while (inet.hasMoreElements()) {
InetAddress ip = inet.nextElement();
if (!ip.isLoopbackAddress()
&& InetAddressUtils.isIPv4Address(ip
.getHostAddress())) {
return ip.getHostAddress();
}
}
}
}
catch(SocketException e)
{
Log.e("feige", "獲取本地ip地址失敗");
e.printStackTrace();
}
return ipaddress;
}
private String getLocalIPAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
Log.e(LOG_TAG, ex.toString());
}
return null;
}
// 按下返回鍵時,關閉 多播socket ms
@Override
public void onBackPressed() {
ms.close();
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.my, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
如果有錯漏的地方,還請批評指正。畢竟是初學者,不出錯,不疏忽是不可能的。
