最近在和同学开发一款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); } }
如果有错漏的地方,还请批评指正。毕竟是初学者,不出错,不疏忽是不可能的。