您好,登錄后才能下訂單哦!
實現了基本的socket通信(即兩臺設備,一臺用作服務器,一臺用作客戶端),服務器進行監聽,客戶端發送加密數據到服務器,服務器進行解密得到明文。
注意:本項目中使用了ButterKnife及EventBus作為輔助工具,通信建立時默認網絡正常(未做局域網網絡環境檢測),加密方式為AES加密
1.效果圖:
(1)客戶端
(2)服務器端
2.界面布局部分
(1)服務器端布局 function_socket_server.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout > <TextView android:text="網絡加密-服務器端" /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_startListener" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="啟動監聽" /> <Button android:id="@+id/btn_stopListener" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="停止監聽" /> <Button android:id="@+id/btn_getUser" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="刷新用戶" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="本機地址:" /> <TextView android:id="@+id/tv_localAddress" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" /> </LinearLayout> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="接收到的明文:" android:textColor="@color/black" /> <TextView android:id="@+id/tv_receivedContent" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="解密后的明文:" android:textColor="@color/black" /> <TextView android:id="@+id/tv_decryptContent" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" /> </LinearLayout> </ScrollView> </LinearLayout>
(2)客戶端布局 function_socket_client.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout > <TextView android:text="網絡加密-客戶端" /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="服務器地址:" /> <EditText android:id="@+id/edtTxt_serverAddress" android:layout_width="match_parent" android:text="192.168.43.1" android:layout_height="wrap_content" android:singleLine="true" /> </LinearLayout> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="文本內容:" android:textColor="@color/black" /> <EditText android:id="@+id/edtTxt_Content" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/main_background" android:padding="10dp" android:text="123木頭人" /> </LinearLayout> </ScrollView> <Button android:id="@+id/btn_encryptAndSend" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:text="加密并發送" /> </LinearLayout>
(3)用到的style
<!--通用Title的右側按鈕--> <style name="ToolBar_iv_Right"> <item name="android:layout_width">@dimen/toolbar_icon_dimen</item> <item name="android:layout_height">@dimen/toolbar_icon_dimen</item> <item name="android:layout_alignParentRight">true</item> <item name="android:layout_gravity">end</item> <item name="android:clickable">true</item> <item name="android:background">?android:actionBarItemBackground</item> <item name="android:padding">15dp</item> </style> <!--通用Title的TextView--> <style name="ToolBar_tv_Title"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_centerVertical">true</item> <item name="android:layout_marginLeft">@dimen/toolbar_title_haveBack_marginStart</item> <item name="android:layout_marginRight">@dimen/toolbar_title_haveBack_marginEnd</item> <item name="android:gravity">center</item> <item name="android:singleLine">true</item> <item name="android:textColor">@color/white</item> <item name="android:textSize">20sp</item> </style>
3.功能代碼
(1)基類 BaseEventActivity.Java
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import org.greenrobot.eventbus.EventBus; import butterknife.ButterKnife; public abstract class BaseEventActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getIntentData(); setContentView(getLayoutResId()); ButterKnife.bind(this); EventBus.getDefault().register(this); init(); } protected void getIntentData() { } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } protected abstract void init(); protected abstract int getLayoutResId(); }
(2)服務器主界面 Function_Socket.java
import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.view.View; import android.widget.TextView; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import butterknife.BindView; import butterknife.OnClick; /** * 服務器界面 */ public class Function_Socket_Server extends BaseEventActivity { @BindView(R.id.tv_localAddress) TextView tv_localAddress; @BindView(R.id.tv_receivedContent) TextView tv_receivedContent; @BindView(R.id.tv_decryptContent) TextView tv_decryptContent; private LocalService localService;//用于啟動監聽的服務 private ServiceConnection sc;//服務連接 @Override protected void init() { tv_localAddress.setText(ToolUtil.getHostIP()); sc = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { LocalService.LocalBinder localBinder = (LocalService.LocalBinder) service; localService = localBinder.getService(); localService.startWaitDataThread(); ToastUtil.showToast(Function_Socket_Server.this, "監聽已啟動"); } @Override public void onServiceDisconnected(ComponentName name) { } }; connection(); } @Subscribe(threadMode = ThreadMode.MAIN) public void getData(String data) { tv_receivedContent.setText(data); tv_decryptContent.setText(AESUtil.decrypt(ConstantUtil.password, data)); } /** * 綁定service */ private void connection() { Intent intent = new Intent(this, LocalService.class); bindService(intent, sc, BIND_AUTO_CREATE); } @Override protected int getLayoutResId() { return R.layout.function_socket_server; } /** * 獲取連接到本機熱點上的手機ip */ private ArrayList<String> getConnectedIP() { ArrayList<String> connectedIP = new ArrayList<>(); try { //通過讀取配置文件實現 BufferedReader br = new BufferedReader(new FileReader( "/proc/net/arp")); String line; while ((line = br.readLine()) != null) { String[] splitted = line.split(" +"); if (splitted.length >= 4) { String ip = splitted[0]; connectedIP.add(ip); } } } catch (Exception e) { e.printStackTrace(); } return connectedIP; } @OnClick({R.id.btn_startListener, R.id.btn_stopListener, R.id.btn_getUser}) public void onClick(View v) { switch (v.getId()) { case R.id.btn_startListener://啟動監聽 connection(); break; case R.id.btn_stopListener://停止監聽 if (sc != null) unbindService(sc); break; case R.id.btn_getUser://刷新連接到此設備的IP并清空之前接收到的數據 ArrayList<String> connectedIP = getConnectedIP(); StringBuilder resultList = new StringBuilder(); for (String ip : connectedIP) { resultList.append(ip); resultList.append("\n"); } ToastUtil.showToast(this, "連接到手機上的Ip是:" + resultList.toString()); tv_decryptContent.setText(""); tv_receivedContent.setText(""); break; } } public void onDestroy() { super.onDestroy(); if (sc != null) unbindService(sc); } }
(3)客戶端主界面 Function_Socket_Client.java
import android.app.ProgressDialog; import android.util.Log; import android.view.View; import android.widget.EditText; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import butterknife.BindView; import butterknife.OnClick; /** * 客戶端界面 */ public class Function_Socket_Client extends BaseEventActivity { @BindView(R.id.edtTxt_Content) EditText edtTxt_Content; @BindView(R.id.edtTxt_serverAddress) EditText edtTxt_serverAddress; private ProgressDialog mProgressDialog;//加載的小菊花 /** * 初始化 */ @Override protected void init() { } @Override protected int getLayoutResId() { return R.layout.function_socket_client; } @OnClick(R.id.btn_encryptAndSend) public void onClick(View v) { switch (v.getId()) { case R.id.btn_encryptAndSend: String s = edtTxt_Content.getText().toString().trim(); String ip = edtTxt_serverAddress.getText().toString().trim(); if (ToolUtil.IsIpv4(ip)) { new SendDataThread(ip, AESUtil.encrypt(ConstantUtil.password, s), ConstantUtil.port).start();//消息發送方啟動線程發送消息 showProgressDialog("嘗試發送數據到\n\t\t" + ip, true); } else { ToastUtil.showToast(this, "IP不合法!"); } break; } } /** * 連接結果 * * @param resultCode 0:連接超時;1:發送成功 2:失敗 */ @Subscribe(threadMode = ThreadMode.MAIN) public void sendResult(Integer resultCode) { Log.i("succ", "=" + resultCode); dismissProgressDialog(); switch (resultCode) { case ConstantUtil.CODE_SUCCESS: ToastUtil.showToast(this, "發送成功"); break; case ConstantUtil.CODE_TIMEOUT: ToastUtil.showToast(this, "連接超時"); break; case ConstantUtil.CODE_UNKNOWN_HOST: ToastUtil.showToast(this, "錯誤-未知的host"); break; } } /** * 數據加載小菊花 * * @param msg 內容 * @param isCancel 是否允許關閉 true - 允許 false - 不允許 */ public void showProgressDialog(final String msg, final boolean isCancel) { runOnUiThread(new Runnable() { @Override public void run() { try { if (mProgressDialog == null) { mProgressDialog = new ProgressDialog(Function_Socket_Client.this); } if (mProgressDialog.isShowing()) { return; } mProgressDialog.setMessage(msg); mProgressDialog.setCancelable(isCancel); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.setOnCancelListener(null); mProgressDialog.show(); } catch (Exception e) { e.printStackTrace(); } } }); } /** * 隱藏數據加載的進度小菊花 **/ public void dismissProgressDialog() { try { if (mProgressDialog != null && mProgressDialog.isShowing()) { runOnUiThread( new Runnable() { @Override public void run() { mProgressDialog.dismiss(); } } ); } } catch (Exception e) { e.printStackTrace(); } } }
(4)LocalService.java
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; /** * 此服務用于啟動監聽線程 */ public class LocalService extends Service { private IBinder iBinder = new LocalService.LocalBinder(); @Override public IBinder onBind(Intent intent) { return iBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } public void startWaitDataThread() { new ListenThread(ConstantUtil.port).start(); } //定義內容類繼承Binder public class LocalBinder extends Binder { //返回本地服務 public LocalService getService() { return LocalService.this; } } }
(5)ListenThread.java
import org.greenrobot.eventbus.EventBus; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * 監聽線程 */ public class ListenThread extends Thread { private ServerSocket serverSocket; public ListenThread(int port) { try { serverSocket = new ServerSocket(port); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while (true) { try { if (serverSocket != null) { Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); if (inputStream != null) { BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String str; str = in.readLine(); EventBus.getDefault().post(str); socket.close(); } } } catch (IOException e) { e.printStackTrace(); } } } }
(6)SendDataThread.java
import android.util.Log; import org.greenrobot.eventbus.EventBus; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; /** * 數據發送線程 */ public class SendDataThread extends Thread { private Socket socket; private String ip;//接收方的IP private int port;//接收方的端口號 private String data;//準備發送的數據 public SendDataThread(String ip, String data, int port) { this.ip = ip; this.data = data; this.port = port; } @Override public void run() { try { socket = new Socket(); socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//設置超時時間 } catch (UnknownHostException e) { EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST); Log.d("error", "SendDataThread.init() has UnknownHostException" + e.getMessage()); } catch (SocketTimeoutException e) { EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT); Log.d("error", "SendDataThread.init() has TimeoutException:" + e.getMessage()); }catch (IOException e){ Log.d("error", "SendDataThread.init() has IOException:" + e.getMessage()); } if (socket != null&&socket.isConnected()) { try { OutputStream ops = socket.getOutputStream(); OutputStreamWriter opsw = new OutputStreamWriter(ops); BufferedWriter writer = new BufferedWriter(opsw); writer.write(data + "\r\n\r\n");//由于socket使用緩沖區進行讀寫數據,因此使用\r\n\r\n用于表明數據已寫完.不加這個會導致數據無法發送 EventBus.getDefault().post(ConstantUtil.CODE_SUCCESS); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } } }
(7)AESUtil.java
import android.util.Log; import java.io.UnsupportedEncodingException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * AES加密工具類 */ public class AESUtil { // private static final String CipherMode = "AES/ECB/PKCS5Padding";使用ECB加密,不需要設置IV,但是不安全 private static final String CipherMode = "AES/CFB/NoPadding";//使用CFB加密,需要設置IV /** * 生成加密后的密鑰 * * @param password 密鑰種子 * @return isSucceed */ private static SecretKeySpec createKey(String password) { byte[] data = null; if (password == null) { password = ""; } StringBuilder sb = new StringBuilder(32); sb.append(password); while (sb.length() < 32) { sb.append("0"); } if (sb.length() > 32) { sb.setLength(32); } try { data = sb.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return new SecretKeySpec(data, "AES"); } // /** 加密字節數據 **/ private static byte[] encrypt(byte[] content, String password) { try { SecretKeySpec key = createKey(password); System.out.println(key); Cipher cipher = Cipher.getInstance(CipherMode); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec( new byte[cipher.getBlockSize()])); return cipher.doFinal(content); } catch (Exception e) { e.printStackTrace(); } return null; } // /** 加密(結果為16進制字符串) **/ public static String encrypt(String password, String content) { Log.d("加密前", "seed=" + password + "\ncontent=" + content); byte[] data = null; try { data = content.getBytes("UTF-8"); } catch (Exception e) { e.printStackTrace(); } data = encrypt(data, password); String result = byte2hex(data); Log.d("加密后", "result=" + result); return result; } // /** 解密字節數組 **/ private static byte[] decrypt(byte[] content, String password) { try { SecretKeySpec key = createKey(password); Cipher cipher = Cipher.getInstance(CipherMode); cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec( new byte[cipher.getBlockSize()])); return cipher.doFinal(content); } catch (Exception e) { e.printStackTrace(); } return null; } // /** 解密16進制的字符串為字符串 **/ public static String decrypt(String password, String content) { Log.d("解密前", "seed=" + password + "\ncontent=" + content); byte[] data = null; try { data = hex2byte(content); } catch (Exception e) { e.printStackTrace(); } data = decrypt(data, password); if (data == null) return null; String result = null; try { result = new String(data, "UTF-8"); Log.d("解密后", "result=" + result); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return result; } // /** 字節數組轉成16進制字符串 **/ private static String byte2hex(byte[] b) { // 一個字節的數, StringBuilder sb = new StringBuilder(b.length * 2); String tmp ; for (byte aB : b) { // 整數轉成十六進制表示 tmp = (Integer.toHexString(aB & 0XFF)); if (tmp.length() == 1) { sb.append("0"); } sb.append(tmp); } return sb.toString().toUpperCase(); // 轉成大寫 } // /** 將hex字符串轉換成字節數組 **/ private static byte[] hex2byte(String inputString) { if (inputString == null || inputString.length() < 2) { return new byte[0]; } inputString = inputString.toLowerCase(); int l = inputString.length() / 2; byte[] result = new byte[l]; for (int i = 0; i < l; ++i) { String tmp = inputString.substring(2 * i, 2 * i + 2); result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF); } return result; } }
(8)ConstantUti.java
/** * 常量類 */ public class ConstantUtil { public static final int TIME_MILLIS = 5 * 1000;//連接超時時間 public static final int port = 25256;//端口號 public static final String password = "123456885";//加密所使用的密鑰 public static final int CODE_TIMEOUT = 0;//連接超時 public static final int CODE_SUCCESS = 1;//連接成功 public static final int CODE_UNKNOWN_HOST = 2;//錯誤-未知的host }
(9)ToolUtil.java
import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; /** * 工具類 */ public class ToolUtil { /** * 獲取ip地址 * 如果是移動網絡,會顯示自己的公網IP,如果是局域網,會顯示局域網IP * 因此本例中服務器端需要斷開移動網絡以得到本機局域網IP */ public static String getHostIP() { String hostIp = null; try { Enumeration nis = NetworkInterface.getNetworkInterfaces(); InetAddress ia; while (nis.hasMoreElements()) { NetworkInterface ni = (NetworkInterface) nis.nextElement(); Enumeration<InetAddress> ias = ni.getInetAddresses(); while (ias.hasMoreElements()) { ia = ias.nextElement(); if (ia instanceof Inet6Address) { continue;// skip ipv6 } String ip = ia.getHostAddress(); if (!"127.0.0.1".equals(ip)) { hostIp = ia.getHostAddress(); break; } } } } catch (SocketException e) { Log.i("error", "SocketException"); e.printStackTrace(); } return hostIp; } /** * 判斷地址是否為IPV4地址 */ public static boolean IsIpv4(String ipv4) { if (ipv4 == null || ipv4.length() == 0) { return false;//字符串為空或者空串 } String[] parts = ipv4.split("\\.");//因為java doc里已經說明, split的參數是reg, 即正則表達式, 如果用"|"分割, 則需使用"\\|" if (parts.length != 4) { return false;//分割開的數組根本就不是4個數字 } for (String part : parts) { try { int n = Integer.parseInt(part); if (n < 0 || n > 255) { return false;//數字不在正確范圍內 } } catch (NumberFormatException e) { return false;//轉換數字不正確 } } return true; } }
(10)ToastUtil.java
import android.content.Context; import android.widget.Toast; public class ToastUtil { private static Toast mToast = null; /** * Toast方法 * * @param text 需要展示的文本 * @param context 所需上下文 */ public static void showToast(Context context, String text) { if (text != null) { if (mToast == null) { mToast = Toast.makeText(context, text, Toast.LENGTH_SHORT); } else { mToast.setText(text); mToast.setDuration(Toast.LENGTH_SHORT); } mToast.show(); } } }
3.權限及聲明
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!--service部分--> <service android:name="com.test.test.LocalService"/>
代碼到此為止了,功能比較簡單。希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。