首页 > 系统 > Android > 正文

android基于socket的局域网内服务器与客户端加密通信

2019-12-12 03:06:47
字体:
来源:转载
供稿:网友

实现了基本的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 style="@style/ToolBar">  <TextView   style="@style/ToolBar_tv_Title"   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 style="@style/ToolBar">  <TextView   style="@style/ToolBar_tv_Title"   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"/>

代码到此为止了,功能比较简单。希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有0条评论
用户名: 密码:
验证码: 匿名发表