android基于socket的局域网内服务器与客户端加密通信
程序员文章站
2022-08-02 18:21:03
实现了基本的socket通信(即两台设备,一台用作服务器,一台用作客户端),服务器进行监听,客户端发送加密数据到服务器,服务器进行解密得到明文。
注意:本项目中使用了bu...
实现了基本的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"/>
代码到此为止了,功能比较简单。希望对大家的学习有所帮助,也希望大家多多支持。