当前位置: 移动技术网 > IT编程>移动开发>Android > android基于socket的局域网内服务器与客户端加密通信

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

2019年07月24日  | 移动技术网IT编程  | 我要评论

murdaralph,dede教程,tlm42v68pk

实现了基本的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"/>

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网