欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-08-02 18:21:03
实现了基本的socket通信(即两台设备,一台用作服务器,一台用作客户端),服务器进行监听,客户端发送加密数据到服务器,服务器进行解密得到明文。 注意:本项目中使用了bu...

实现了基本的socket通信(即两台设备,一台用作服务器,一台用作客户端),服务器进行监听,客户端发送加密数据到服务器,服务器进行解密得到明文。

注意:本项目中使用了butterknife及eventbus作为辅助工具,通信建立时默认网络正常(未做局域网网络环境检测),加密方式为aes加密

1.效果图:

(1)客户端

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

(2)服务器端

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

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"/>

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