Android Socket通讯
Posted 初学者-Study
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Socket通讯相关的知识,希望对你有一定的参考价值。
Socket通讯
前言
Socket通讯在很多地方都会用到,android上同样不例外,Socket不是一种协议,而是一个编程调用接口(API),属于传输层,通过Socket,我们才能在Andorid平台上通过 TCP/IP协议进行开发。先看看效果图:
正文
先说明一下流程:
① 准备两台Android手机(真机)。
② 连接同一个WIFI网络 。
③ 服务端开启服务。
④ 客户端连接服务。
⑤ 服务端与客户端进行消息发送接收。
那么根据这个流程我们开始写代码。
一、创建项目
创建一个名为SocketDemo的项目,使用Kotlin。
因为涉及到网络通讯,所以需要在AndroidManifest.xml配置网络权限。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
然后再配置一下app的build.gradle,在android闭包下添加:
buildFeatures
viewBinding true
这里开启项目的viewBinding,其他的就没啥好配置的了,进入正式的编码环节。
二、构建主页面布局
创建项目会默认有一个MainActivity,这个页面既是服务端,又是客户端。修改一下activity_main.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RadioGroup
android:id="@+id/rg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<RadioButton
android:id="@+id/rb_server"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="Socket服务端" />
<RadioButton
android:id="@+id/rb_client"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Socket客户端" />
</RadioGroup>
<LinearLayout
android:id="@+id/lay_server"
android:layout_width="match_parent"
android:layout_height="110dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_ip_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="Ip地址:" />
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="开启服务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/radioGroup" />
</LinearLayout>
<LinearLayout
android:id="@+id/lay_client"
android:layout_width="match_parent"
android:layout_height="110dp"
android:orientation="vertical"
android:visibility="gone">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/op_code_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_ip_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="连接Ip地址"
android:inputType="text"
android:lines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_connect_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="连接服务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_start_service" />
</LinearLayout>
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="16dp"
android:text="信息" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_msg"
android:layout_width="0dp"
android:gravity="center_vertical"
android:layout_height="40dp"
android:hint="发送给客户端"
android:textSize="14sp"
android:layout_weight="1"
android:background="@drawable/shape_et_bg"
android:padding="10dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_send_msg"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="发送"
app:cornerRadius="8dp" />
</LinearLayout>
</LinearLayout>
这里面有一个输入框的背景样式,在drawable下新增shape_et_bg.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<solid android:color="#EEE" />
</shape>
说明一下页面的内容,首先是RadioButton切换服务端和客户端,服务端则显示当前手机的IP地址和开启服务按钮,客户端则显示一个输入框,和连接服务按钮。中间主要内容就是服务端和客户端交互的信息,底部是一个输入框和发送消息按钮。预览的效果如图所示:
三、服务端
在com.llw.socket包下新建一个server包,我们服务端的代码就写在这个server包下。新建一个ServerCallback接口,代码如下:
interface ServerCallback
//接收客户端的消息
fun receiveClientMsg(success: Boolean, msg: String)
//其他消息
fun otherMsg(msg: String)
下面就是主要的服务端代码了,在server包下新建一个SocketServer类,代码如下:
object SocketServer
private val TAG = SocketServer::class.java.simpleName
private const val SOCKET_PORT = 9527
private var socket: Socket? = null
private var serverSocket: ServerSocket? = null
private lateinit var mCallback: ServerCallback
private lateinit var outputStream: OutputStream
var result = true
/**
* 开启服务
*/
fun startServer(callback: ServerCallback): Boolean
mCallback = callback
Thread
try
serverSocket = ServerSocket(SOCKET_PORT)
while (result)
socket = serverSocket?.accept()
mCallback.otherMsg("$socket?.inetAddress to connected")
ServerThread(socket!!, mCallback).start()
catch (e: IOException)
e.printStackTrace()
result = false
.start()
return result
/**
* 关闭服务
*/
fun stopServer()
socket?.apply
shutdownInput()
shutdownOutput()
close()
serverSocket?.close()
/**
* 发送到客户端
*/
fun sendToClient(msg: String)
Thread
if (socket!!.isClosed)
Log.e(TAG, "sendToClient: Socket is closed")
return@Thread
outputStream = socket!!.getOutputStream()
try
outputStream.write(msg.toByteArray())
outputStream.flush()
mCallback.otherMsg("toClient: $msg")
Log.d(TAG, "发送到客户端成功")
catch (e: IOException)
e.printStackTrace()
Log.e(TAG, "向客户端发送消息失败")
.start()
class ServerThread(private val socket: Socket, private val callback: ServerCallback) :
Thread()
override fun run()
val inputStream: InputStream?
try
inputStream = socket.getInputStream()
val buffer = ByteArray(1024)
var len: Int
var receiveStr = ""
if (inputStream.available() == 0)
Log.e(TAG, "inputStream.available() == 0")
while (inputStream.read(buffer).also len = it != -1)
receiveStr += String(buffer, 0, len, Charsets.UTF_8)
if (len < 1024)
callback.receiveClientMsg(true, receiveStr)
receiveStr = ""
catch (e: IOException)
e.printStackTrace()
e.message?.let Log.e("socket error", it)
callback.receiveClientMsg(false, "")
代码从上往下看,首先是初始化一些变量,然后就是startServer()函数,在这里进行回调接口的初始化然后开一个子线程进行ServerSocket的构建,构建成功之后会监听连接,得到一个socket,这个socket就是客户端,这里将连接客户端的地址显示出来。然后再开启一个子线程去处理客户端发送过来的消息。这个地方服务端和客户端差不多,下面看ServerThread中的代码。Socket通讯,发送和接收对应的是输入流和输入流,通过socket.getInputStream()得到输入流,获取字节数据然后转成String,通过接口回调,最后重置变量。关闭服务就没好说的,代码一目了然。最后就是发送到客户端的sendToClient()函数。接收发送字符串,开启子线程,获取输出流,写入字节数据然后刷新,最后回调到页面。
四、客户端
在com.llw.socket包下新建一个client包,我们客户端的代码就写在这个client包下。新建一个ClientCallback接口,代码如下:
interface ClientCallback
//接收服务端的消息
fun receiveServerMsg(msg: String)
//其他消息
fun otherMsg(msg: String)
下面就是主要的客户端代码了,在client包下新建一个SocketClient类,代码如下:
object SocketClient
private val TAG = SocketClient::class.java.simpleName
private var socket: Socket? = null
private var outputStream: OutputStream? = null
private var inputStreamReader: InputStreamReader? = null
private lateinit var mCallback: ClientCallback
private const val SOCKET_PORT = 9527
/**
* 连接服务
*/
fun connectServer(ipAddress: String, callback: ClientCallback)
mCallback = callback
Thread
try
socket = Socket(ipAddress, SOCKET_PORT)
ClientThread(socket!!, mCallback).start()
catch (e: IOException)
e.printStackTrace()
.start()
/**
* 关闭连接
*/
fun closeConnect()
inputStreamReader?.close()
outputStream?.close()
socket?.apply
shutdownInput()
shutdownOutput()
close()
Log.d(TAG, "关闭连接")
/**
* 发送数据至服务器
* @param msg 要发送至服务器的字符串
*/
fun sendToServer(msg: String) 以上是关于Android Socket通讯的主要内容,如果未能解决你的问题,请参考以下文章
天地币:所用到的 Android Socket 通讯编程技术试验