优化:移除 `kmnkt` 依赖,重构 `SocketUtils` #339 #349

pull/364/head
pppscn 12 months ago
parent 354393a231
commit 2cccb9b4fa

@ -192,7 +192,7 @@ dependencies {
//kmnktKotlin MultiplatformsocketUDP/TCP/MQTT
//https://github.com/xuankaicat/kmnkt
implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
implementation files('libs/socket-2.0.0-alpha06-2.aar')
//implementation files('libs/socket-2.0.0-alpha06-2.aar')
testImplementation deps.junit
androidTestImplementation 'androidx.test.ext:junit:1.1.5'

@ -9,6 +9,7 @@ data class SocketSetting(
val port: Int = 0, //端口号
val msgTemplate: String = "", //消息模板
val secret: String? = "", //签名密钥
val response: String? = "", //成功应答关键字
val username: String = "", //用户名
val password: String = "", //密码
val inCharset: String = "", //输入编码
@ -29,4 +30,11 @@ data class SocketSetting(
}
}
fun getUriTypeCheckId(): Int {
return when (method) {
"ssl" -> R.id.rb_uriType_ssl
else -> R.id.rb_uriType_tcp
}
}
}

@ -122,13 +122,14 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
binding!!.etPort.setText(settingVo.port.toString())
binding!!.etMsgTemplate.setText(settingVo.msgTemplate)
binding!!.etSecret.setText(settingVo.secret)
binding!!.etResponse.setText(settingVo.response)
binding!!.etUsername.setText(settingVo.username)
binding!!.etPassword.setText(settingVo.password)
binding!!.etInCharset.setSelectedItem(settingVo.inCharset)
binding!!.etOutCharset.setSelectedItem(settingVo.outCharset)
binding!!.etInMessageTopic.setText(settingVo.inMessageTopic)
binding!!.etOutMessageTopic.setText(settingVo.outMessageTopic)
binding!!.etUriType.setText(settingVo.uriType)
binding!!.rgUriType.check(settingVo.getUriTypeCheckId())
binding!!.etPath.setText(settingVo.path)
binding!!.etClientId.setText(settingVo.clientId)
binding!!.layoutMqtt.visibility = if (checkedId == R.id.rb_method_mqtt) View.VISIBLE else View.GONE
@ -170,6 +171,7 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
}.start()
return
}
R.id.btn_del -> {
if (senderId <= 0 || isClone) {
popToBack()
@ -183,6 +185,7 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
}.show()
return
}
R.id.btn_save -> {
val name = binding!!.etName.text.toString().trim()
if (TextUtils.isEmpty(name)) {
@ -226,13 +229,17 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
val msgTemplate = binding!!.etMsgTemplate.text.toString().trim()
val secret = binding!!.etSecret.text.toString().trim()
val response = binding!!.etResponse.text.toString().trim()
val username = binding!!.etUsername.text.toString().trim()
val password = binding!!.etPassword.text.toString().trim()
val inCharset = binding!!.etInCharset.text.toString().trim()
val outCharset = binding!!.etOutCharset.text.toString().trim()
val inMessageTopic = binding!!.etInMessageTopic.text.toString().trim()
val outMessageTopic = binding!!.etOutMessageTopic.text.toString().trim()
val uriType = binding!!.etUriType.text.toString().trim()
val uriType = when (binding!!.rgUriType.checkedRadioButtonId) {
R.id.rb_uriType_ssl -> "ssl"
else -> "tcp"
}
val path = binding!!.etPath.text.toString().trim()
val clientId = binding!!.etClientId.text.toString().trim()
@ -240,7 +247,7 @@ class SocketFragment : BaseFragment<FragmentSendersSocketBinding?>(), View.OnCli
throw Exception(getString(R.string.invalid_mqtt_message_topic))
}
return SocketSetting(method, address, port.toInt(), msgTemplate, secret, username, password, inCharset, outCharset, inMessageTopic, outMessageTopic, uriType, path, clientId)
return SocketSetting(method, address, port.toInt(), msgTemplate, secret, response, username, password, inCharset, outCharset, inMessageTopic, outMessageTopic, uriType, path, clientId)
}
override fun onDestroyView() {

@ -4,12 +4,6 @@ import android.annotation.SuppressLint
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import com.gitee.xuankaicat.kmnkt.socket.MqttQuality
import com.gitee.xuankaicat.kmnkt.socket.dsl.mqtt
import com.gitee.xuankaicat.kmnkt.socket.dsl.tcp
import com.gitee.xuankaicat.kmnkt.socket.dsl.udp
import com.gitee.xuankaicat.kmnkt.socket.open
import com.gitee.xuankaicat.kmnkt.socket.utils.Charset
import com.google.gson.Gson
import com.idormy.sms.forwarder.database.entity.Rule
import com.idormy.sms.forwarder.entity.MsgInfo
@ -17,7 +11,20 @@ import com.idormy.sms.forwarder.entity.setting.SocketSetting
import com.idormy.sms.forwarder.utils.SendUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.xuexiang.xutil.app.AppUtils
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended
import org.eclipse.paho.client.mqttv3.MqttClient
import org.eclipse.paho.client.mqttv3.MqttConnectOptions
import org.eclipse.paho.client.mqttv3.MqttException
import org.eclipse.paho.client.mqttv3.MqttMessage
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.Socket
import java.net.URLEncoder
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.*
@ -62,113 +69,100 @@ class SocketUtils {
}
if (setting.method == "TCP" || setting.method == "UDP") {
var isReceived = false
var isConnected = false
val socket = if (setting.method == "TCP") {
tcp {
address = setting.address//设置ip地址
port = setting.port//设置端口号
if (!TextUtils.isEmpty(setting.inCharset)) inCharset = Charset.forName(setting.inCharset)//设置输入编码
if (!TextUtils.isEmpty(setting.outCharset)) outCharset = Charset.forName(setting.outCharset)//设置输出编码
}
} else {
udp {
address = setting.address//设置ip地址
port = setting.port//设置端口号
if (!TextUtils.isEmpty(setting.inCharset)) inCharset = Charset.forName(setting.inCharset)//设置输入编码
if (!TextUtils.isEmpty(setting.outCharset)) outCharset = Charset.forName(setting.outCharset)//设置输出编码
}
}
socket.open {
success {
Log.d(TAG, "${setting.method}连接成功")
isConnected = true
//SendUtils.updateLogs(logId, 1, "TCP连接成功")
socket.send(message)
socket.startReceive { str, data ->
isReceived = true
Log.d(TAG, "str=$str,data=$data")
SendUtils.updateLogs(logId, 2, "收到订阅消息str=$str,data=$data")
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
return@startReceive false
}
}
failure {
Log.d(TAG, "${setting.method}连接失败")
val status = 0
SendUtils.updateLogs(logId, status, "TCP连接失败")
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
return@failure false//是否继续尝试连接
}
loss {
Log.d(TAG, "${setting.method}连接断开")
return@loss true//是否尝试重连
}
}
//延时5秒关闭连接
if (isConnected) {
Thread.sleep(10000)
socket.stopReceive()
// 创建套接字并连接到服务器
val socket = Socket(setting.address, setting.port)
Log.d(TAG, "连接到服务器: ${setting.address}:${setting.port}")
try {
// 获取输入流和输出流设置字符集为UTF-8
val input = BufferedReader(InputStreamReader(socket.getInputStream(), Charset.forName(setting.inCharset)))
val output = BufferedWriter(OutputStreamWriter(socket.getOutputStream(), Charset.forName(setting.outCharset)))
// 向服务器发送数据
output.write(message)
output.newLine() // 添加换行符以便服务器使用readLine()来读取
output.flush()
Log.d(TAG, "发送到服务器的消息: $message")
// 从服务器接收响应
val response = input.readLine()
Log.d(TAG, "从服务器接收的响应: $response")
val status = if (!setting.response.isNullOrEmpty() && !response.contains(setting.response)) 0 else 2
SendUtils.updateLogs(logId, status, response)
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} catch (e: Exception) {
e.printStackTrace()
val status = 0
SendUtils.updateLogs(logId, status, e.message.toString())
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
} finally {
// 关闭套接字
socket.close()
if (!isReceived) {
SendUtils.updateLogs(logId, 0, "未收到订阅消息")
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
}
Log.d(TAG, "Disconnected from MQTT broker")
}
return
} else if (setting.method == "MQTT") {
val mqtt = mqtt {
address = setting.address//设置ip地址
port = setting.port//设置端口号
if (!TextUtils.isEmpty(setting.inCharset)) inCharset = Charset.forName(setting.inCharset)//设置输入编码
if (!TextUtils.isEmpty(setting.outCharset)) outCharset = Charset.forName(setting.outCharset)//设置输出编码
if (!TextUtils.isEmpty(setting.username)) username = setting.username
if (!TextUtils.isEmpty(setting.password)) password = setting.password
if (!TextUtils.isEmpty(setting.inMessageTopic)) inMessageTopic = setting.inMessageTopic
if (!TextUtils.isEmpty(setting.outMessageTopic)) outMessageTopic = setting.outMessageTopic
//自定义配置
qos = MqttQuality.ExactlyOnce // 服务质量 详见MqttQuality
if (!TextUtils.isEmpty(setting.uriType)) uriType = setting.uriType //通信方式 默认为tcp
if (!TextUtils.isEmpty(setting.clientId)) clientId = setting.clientId //客户端ID如果为空则为随机值
timeOut = 10 //设置超时时间
cleanSession = true //断开连接后是否清楚缓存,如果清除缓存则在重连后需要手动恢复订阅。
keepAliveInterval = 20 //检测连接是否中断的间隔
//行为配置
threadLock = false //是否启用线程同步锁 默认false
// MQTT 连接参数
val uriType = if (TextUtils.isEmpty(setting.uriType)) "tcp" else setting.uriType
val brokerUrl = "${uriType}://${setting.address}:${setting.port}"
if (!TextUtils.isEmpty(setting.path)) {
brokerUrl.plus(setting.path)
}
mqtt.open {
success {
Log.d(TAG, "MQTT连接成功")
//SendUtils.updateLogs(logId, 1, "MQTT连接成功")
// 订阅并发布后等待至拿到响应消息并赋值给result
// 如果超过10秒没有收到消息则将result设为"消息响应超时"并取消订阅topic
val response = mqtt.sendAndReceiveSync(setting.outMessageTopic, setting.inMessageTopic, message, 10000L) ?: "消息响应超时"
mqtt.close()
val status = if (response == "消息响应超时") 0 else 2
SendUtils.updateLogs(logId, status, "收到订阅消息:$response")
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
return@success
}
failure {
Log.d(TAG, "MQTT连接失败")
val status = 0
SendUtils.updateLogs(logId, status, "MQTT连接失败")
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
return@failure false//是否继续尝试连接
Log.d(TAG, "MQTT brokerUrl: $brokerUrl")
val clientId = if (TextUtils.isEmpty(setting.clientId)) UUID.randomUUID().toString() else setting.clientId
val mqttClient = MqttClient(brokerUrl, clientId, MemoryPersistence())
try {
val options = MqttConnectOptions()
if (!TextUtils.isEmpty(setting.username)) {
options.userName = setting.username
}
loss {
Log.d(TAG, "MQTT失去连接")
return@loss true//是否尝试重连
if (!TextUtils.isEmpty(setting.password)) {
options.password = setting.password.toCharArray()
}
}
options.isCleanSession = true
mqttClient.connect(options)
Log.d(TAG, "Connected to MQTT broker: ${mqttClient.serverURI}")
mqttClient.subscribe(setting.inMessageTopic)
Log.d(TAG, "Subscribed to topic: $setting.inMessageTopic")
val outMessage = message.toByteArray(Charset.forName(setting.outCharset))
val mqttMessage = MqttMessage(outMessage)
mqttMessage.qos = 0 // 设置消息质量服务等级
//异步发布消息
mqttClient.publish(setting.outMessageTopic, mqttMessage)
Log.d(TAG, "Published message to topic: $setting.outMessageTopic")
mqttClient.setCallback(object : MqttCallbackExtended {
override fun connectionLost(cause: Throwable?) {
val response = "Connection to MQTT broker lost: ${cause?.message}"
Log.d(TAG, response)
SendUtils.updateLogs(logId, 0, response)
SendUtils.senderLogic(0, msgInfo, rule, senderIndex, msgId)
}
}
override fun messageArrived(topic: String?, inMessage: MqttMessage?) {
val payload = inMessage?.payload?.toString(Charset.forName(setting.inCharset))
Log.d(TAG, "Received message on topic $topic: $payload")
val status = if (!setting.response.isNullOrEmpty() && !payload?.contains(setting.response)!!) 0 else 2
SendUtils.updateLogs(logId, status, payload.toString())
SendUtils.senderLogic(status, msgInfo, rule, senderIndex, msgId)
}
override fun deliveryComplete(token: IMqttDeliveryToken?) {
Log.d(TAG, "deliveryComplete")
SendUtils.updateLogs(logId, 2, "deliveryComplete")
SendUtils.senderLogic(2, msgInfo, rule, senderIndex, msgId)
}
override fun connectComplete(reconnect: Boolean, serverURI: String?) {
Log.d(TAG, "connectComplete")
}
})
} catch (e: MqttException) {
Log.d(TAG, "An error occurred: ${e.message}")
} finally {
mqttClient.disconnect()
Log.d(TAG, "Disconnected from MQTT broker")
}
}
}
//JSON需要转义的字符

@ -267,6 +267,28 @@
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/webhook_response2"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_Response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/optional"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_mqtt"
android:layout_width="match_parent"
@ -333,15 +355,28 @@
android:text="@string/uri_type"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_uriType"
android:layout_width="0dp"
<RadioGroup
android:id="@+id/rg_uriType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/uri_type_hint"
android:singleLine="true"
app:met_clearButton="true" />
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_uriType_tcp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tcp"
android:textSize="14sp" />
<RadioButton
android:id="@+id/rb_uriType_ssl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl"
android:textSize="14sp" />
</RadioGroup>
</LinearLayout>

@ -411,6 +411,7 @@
<string name="udp">UDP</string>
<string name="tcp">TCP</string>
<string name="mqtt">MQTT</string>
<string name="ssl">SSL</string>
<!--CloneActivity-->
<string name="local_ip">Local IP: </string>
<string name="operating_instruction">Instructions: \n[Note] The APP version of the sender and receiver must be the same!\n1. Please keep the SOURCE and DESTINATION phones in the same Wi-Fi network, and do not turn on isolation. \n2. Tap "Send" on SOURCE mobile phone, and get "server IP" \n3. After filling in "Server IP" on DESTINATION phone, tap "Receive". \n [NOTE:] sender(s), forwarding rule(s) and log(s) will be overwritten after cloning!</string> <!-- 原文是“新旧手机”,英文翻译中处理为“源”手机和“目标”手机,因为担心“新旧”的表述引起混淆(有没一种可能就是用户就是用从新手机的设备复制到旧手机上去呢?)。 -->
@ -780,6 +781,7 @@
<string name="webhook_params_tips" formatted="false">For example: payload=%7B%22text%22%3A%22[msg]%22%7D [msg] will be replaced with SMS content.\nJson format is supported, e.g. {\"text\":\"[msg]\"}.\nNote: msg is automatically URLEncoder except in JSON format</string>
<string name="webhook_secret">Secret: If left empty, the sign will not be calculated</string>
<string name="webhook_response">Successful Response KeywordIf left empty, HTTP status 200 represents success</string>
<string name="webhook_response2">Successful Response KeywordLeaving it blank means sending is considered as successful.</string>
<string name="headers">Headers</string>
<string name="header_key">Key</string>
<string name="header_value">Value</string>

@ -412,6 +412,7 @@
<string name="udp">UDP</string>
<string name="tcp">TCP</string>
<string name="mqtt">MQTT</string>
<string name="ssl">SSL</string>
<!--CloneActivity-->
<string name="local_ip">本机IP</string>
<string name="operating_instruction">严正声明:\n该功能仅限个人新旧手机切换使用用于非法用途后果自负\n\n操作说明\n1.新旧手机连接同一个WiFi网络(禁用AP隔离)如需穿透内网请先配置Frpc\n2.【二选一】旧手机点【推送】按钮,将本机的配置推送到服务端\n3.【二选一】新手机点【拉取】按钮,将拉取服务端的配置到本机\n\n注意事项\n1.客户端与服务端的APP版本必须一致才能克隆!\n2.导入成功后,发送通道、转发规则将完全被覆盖,清空历史记录!\n3.主动请求、保活措施、个性设置不在克隆范围</string>
@ -781,6 +782,7 @@
<string name="webhook_params_tips" formatted="false">例如payload=%7B%22text%22%3A%22[msg]%22%7D [msg]将被替换成短信内容。\n支持Json格式例如{\"text\":\"[msg]\"}。\n注意除JSON格式外msg会自动进行URLEncoder</string>
<string name="webhook_secret">Secret置空则不计算sign</string>
<string name="webhook_response">成功应答关键字置空则http状态200即为成功</string>
<string name="webhook_response2">成功应答关键字:置空则发出即成功</string>
<string name="headers">Headers</string>
<string name="header_key">Key</string>
<string name="header_value">Value</string>

Loading…
Cancel
Save