Client.kt
class ClientManager(
var address: String,
var onLicked: ((String?, Exception?) -> Unit)? = null
) {
private val scope = MainScope()
private var socket: Socket? = null
@Synchronized
private fun getSocket(block: (Socket) -> Unit) {
socket?.apply {
block.invoke(this)
} ?: let {
scope.launch(Dispatchers.IO) {
try {
socket = Socket(address, SERVER_PORT).apply {
e("启动客户端,IP:${this.inetAddress}端口号:${this.port}")
onLicked?.invoke(inetAddress?.hostAddress, null)
block.invoke(this)
}
} catch (e: Exception) {
e.printStackTrace()
onLicked?.invoke("", e)
}
}
}
}
fun startClient(
onReceive: ((String?) -> Unit)? = null,
success: ((Exception?) -> Unit)? = null
) {
getSocket {
scope.receive(it, onReceive, success)
}
}
fun sendMsg(msg: String, result: ((Exception?) -> Unit)? = null) {
getSocket {
scope.send(socket, msg, result)
}
}
fun close(onCutoff: ((String?) -> Unit)? = null) {
socket?.closed(onCutoff)
scope.cancel()
socket = null
}
}
internal fun Socket.closed(onCutoff: ((String?) -> Unit)? = null) {
try {
close()
e("关闭客户端")
onCutoff?.invoke(this.inetAddress?.hostAddress)
} catch (e: Exception) {
e.printStackTrace()
}
}
internal fun CoroutineScope.send(
socket: Socket?,
msg: String,
result: ((Exception?) -> Unit)? = null,
onCutoff: ((String?) -> Unit)? = null
): Job {
return this.launch(Dispatchers.IO) {
try {
withTimeout(Constant.TIME_OUT_SEND) {
socket?.getOutputStream()?.let {
e("客户端发送:$msg")
it.write(msg.toByteArray(Charsets.UTF_8))
it.flush()
withMain { result?.invoke(null) }
}
?: withMain { result?.invoke(NullPointerException("socket or OutputStream is null")) }
}
} catch (e: Exception) {
e.printStackTrace()
socket?.closed(onCutoff)
withMain { result?.invoke(e) }
}
}
}
internal fun CoroutineScope.receive(
socket: Socket?,
onReceive: ((String?) -> Unit)? = null,
success: ((Exception?) -> Unit)? = null,
onCutoff: ((String?) -> Unit)? = null
): Job {
return this.launch(Dispatchers.IO) {
socket?.apply {
if (isClosed) {
withMain { success?.invoke(SocketException("Socket is closed")) }
}
while (!isClosed) {
try {
getInputStream()?.use { input ->
withMain { success?.invoke(null) }
val buffer = ByteArray(1024)
var len: Int
while (input.read(buffer).also { len = it } != -1) {
val msg = String(buffer, 0, len)
if (!msg.isReceipt()) {
getOutputStream()?.let {
it.write(
"${Constant.TAG_RECEIPT_HEADER}客户端回执:已收到消息".toByteArray(
Charsets.UTF_8
)
)
it.flush()
}
}
val s = msg.removeHeader()
withMain { onReceive?.invoke(s) }
e("收到来自${socket.inetAddress.hostAddress}:${socket.port}的数据为:$s")
}
withMain { onReceive?.invoke("断开连接") }
socket.closed(onCutoff)
}
?: withMain { success?.invoke(NullPointerException("socket or InputStream is null")) }
} catch (e: Exception) {
e.printStackTrace()
socket.closed(onCutoff)
withMain { success?.invoke(e) }
}
}
} ?: withMain { success?.invoke(NullPointerException("socket is null")) }
}
}
Server.kt
class ServerManager(var launch: ((String?, Exception?) -> Unit)? = null) {
private var clientCount = 0
private val socketMap = HashMap<String, Socket>()
private val clientAddress = ArrayList<String>()
private val scope = MainScope()
private var serverSocket: ServerSocket? = null
private fun addSocket(address: String?, socket: Socket?) {
if (!(address.isNullOrEmpty() || clientAddress.contains(address))) {
socket?.apply {
clientAddress.add(address)
socketMap[address] = socket
clientCount = socketMap.size
e(address, clientCount)
}
}
}
private fun removeSocket(address: String?) {
if (!address.isNullOrEmpty()) {
clientAddress.remove(address)
socketMap.remove(address)
clientCount = socketMap.size
}
}
@Synchronized
private fun getServerSocket(block: (ServerSocket) -> Unit) {
serverSocket?.apply {
block.invoke(this)
} ?: let {
try {
serverSocket =
ServerSocket(
Constant.SERVER_PORT,
Constant.MAX_BACKLOG,
InetAddress.getByName(getIpV4String())
).apply {
e("启动服务 IP:${this.localSocketAddress}")
launch?.invoke(this.inetAddress.hostAddress, null)
block.invoke(this)
}
} catch (e: Exception) {
launch?.invoke("", e)
}
}
}
fun startServer(
onReceive: ((String?) -> Unit)? = null,
onLink: ((address: String?, socket: Socket?, error: Exception?) -> Unit)? = null
) {
getServerSocket {
scope.accept(it, onReceive, {
removeSocket(it)
}) { address, socket, error ->
onLink?.invoke(address, socket, error)
addSocket(address, socket)
}
}
}
fun close() {
serverSocket?.closed()
scope.cancel()
socketMap.forEach {
try {
it.value.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
socketMap.clear()
clientAddress.clear()
}
fun sendMsg(msg: String, result: ((Exception?) -> Unit)? = null) {
scope.launch(Dispatchers.IO) {
try {
var count = 0
socketMap.forEach {
it.value.getOutputStream()?.let { out ->
out.write(msg.toByteArray(Charsets.UTF_8))
out.flush()
val s = "服务端发送:$msg"
e(s)
count++
}
}
if (count != clientCount) {
withMain { result?.invoke(SocketException("${clientCount - count} failed")) }
}
} catch (e: Exception) {
e.printStackTrace()
withMain { result?.invoke(e) }
}
}
}
fun sendMsg(address: String, msg: String, result: ((Exception?) -> Unit)? = null) {
scope.launch(Dispatchers.IO) {
try {
socketMap[address]?.getOutputStream()?.let { out ->
out.write(msg.toByteArray(Charsets.UTF_8))
out.flush()
val s = "服务端发送:$msg"
e(s)
withMain { result?.invoke(null) }
}
?: withMain { result?.invoke(NullPointerException("socket or OutputStream is null")) }
} catch (e: Exception) {
e.printStackTrace()
withMain { result?.invoke(e) }
}
}
}
private fun getIpV4String(): String? {
try {
val enNetI: Enumeration<NetworkInterface> = NetworkInterface.getNetworkInterfaces()
while (enNetI.hasMoreElements()) {
val netI: NetworkInterface = enNetI.nextElement()
val enumIp: Enumeration<InetAddress> = netI.inetAddresses
while (enumIp.hasMoreElements()) {
val ip: InetAddress = enumIp.nextElement()
if (ip is Inet4Address && !ip.isLoopbackAddress) {
return ip.hostAddress
}
}
}
} catch (e: SocketException) {
e.printStackTrace()
}
return "127.0.0.1"
}
}
internal fun ServerSocket.closed(onCutoff: ((String?) -> Unit)? = null) {
try {
close()
e("关闭服务端")
onCutoff?.invoke(this.inetAddress?.hostAddress)
} catch (e: Exception) {
e.printStackTrace()
}
}
internal fun CoroutineScope.accept(
serverSocket: ServerSocket?,
onReceive: ((String?) -> Unit)? = null,
onCutoff: ((String?) -> Unit)? = null,
success: ((address: String?, socket: Socket?, error: Exception?) -> Unit)? = null
): Job {
return this.launch(Dispatchers.IO) {
val mutex = Mutex()
serverSocket?.apply {
if (isClosed) {
withMain { success?.invoke("", null, SocketException("serverSocket is closed")) }
}
while (!isClosed) {
try {
accept()?.let { socket ->
socket.remoteSocketAddress?.apply {
mutex.withLock(this) {
withMain {
success?.invoke(
socket.inetAddress.hostAddress,
socket,
null
)
}
receive(socket, onReceive, onCutoff)
}
}
}
} catch (e: Exception) {
serverSocket.closed()
withMain { success?.invoke("", null, e) }
}
}
} ?: withMain { success?.invoke("", null, NullPointerException("serverSocket is null")) }
}
}
internal fun CoroutineScope.receive(
socket: Socket?,
onReceive: ((String?) -> Unit)? = null,
onCutoff: ((String?) -> Unit)? = null
): Job {
return this.launch(Dispatchers.IO) {
socket?.apply {
try {
getInputStream()?.let { input ->
val buffer = ByteArray(1024)
var len: Int
while (input.read(buffer).also { len = it } != -1) {
val msg = String(buffer, 0, len)
if (!msg.isReceipt()) {
getOutputStream()?.let {
it.write(
"${Constant.TAG_RECEIPT_HEADER}服务端回执:已收到消息".toByteArray(
Charsets.UTF_8
)
)
it.flush()
}
}
val s = msg.removeHeader()
withMain { onReceive?.invoke(s) }
e("收到来自${socket.inetAddress.hostAddress}:${socket.port}的数据为:$s")
}
withMain { onReceive?.invoke("断开连接") }
socket.closed(onCutoff)
}
} catch (e: Exception) {
socket.closed(onCutoff)
e.printStackTrace()
}
}
}
}
工具
Constant
public interface Constant {
String TAG_RECEIPT_HEADER = "${HZ}$:";
int SERVER_PORT = 10086;
long TIME_OUT_SEND = 30000;
long TIME_OUT_LINK = 30000;
int MAX_BACKLOG = 50;
}
Utils.kt
internal fun String.isReceipt() = startsWith(Constant.TAG_RECEIPT_HEADER)
internal fun String.removeHeader() = if (isReceipt()) StringBuffer(this)
.replace(0, getHeaderLength(), "")
.toString() else this
internal fun getHeaderLength() = Constant.TAG_RECEIPT_HEADER.length
internal suspend fun <T> withMain(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.Main, block = block)
}
internal suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.IO, block = block)
}
Log.kt
private fun getInfo(): String {
val targetElement = Thread.currentThread().stackTrace[4]
var className = targetElement.className
val classNameInfo = className.split("\\.".toRegex()).toTypedArray()
if (classNameInfo.isNotEmpty()) {
className = classNameInfo[classNameInfo.size - 1]
}
if (className.contains("$")) {
className = className.split("\\$".toRegex()).toTypedArray()[0]
}
return "Thread: ${Thread.currentThread().name}, ${targetElement.methodName}(${className}.java:${targetElement.lineNumber})${
System.getProperty(
"line.separator"
)
}"
}
internal fun e(vararg msgs: Any?) {
val info = getInfo()
Log.e("header", info)
msgs.forEach {
Log.e("body", it.toString())
}
Log.e("end", "________________________________________")
}
internal fun i(vararg msgs: Any?) {
val info = getInfo()
Log.i("header", info)
msgs.forEach {
Log.i("body", it.toString())
}
Log.i("end", "________________________________________")
}
internal fun d(vararg msgs: Any?) {
val info = getInfo()
Log.d("header", info)
msgs.forEach {
Log.d("body", it.toString())
}
Log.d("end", "________________________________________")
}
internal fun w(vararg msgs: Any?) {
val info = getInfo()
Log.w("header", info)
msgs.forEach {
Log.w("body", it.toString())
}
Log.w("end", "________________________________________")
}
MainActivity
class MainActivity : AppCompatActivity() {
private var clientManager: ClientManager? = null
private var serverManager: ServerManager? = null
override fun onDestroy() {
clientManager?.close()
serverManager?.close()
super.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
serverManager ?: let {
serverManager = ServerManager { s, e ->
e(s, e?.message)
}.apply {
startServer({
e(it)
onReceive(it)
}, { s, sc, e ->
e(s, e?.message)
if (s.isNullOrEmpty()) {
onReceive("连接客户端失败")
} else {
onReceive("连接客户端:$s")
}
})
}
}
btn_link?.setOnClickListener {
link()
}
btn_client_send?.setOnClickListener { clientSend() }
btn_server_send?.setOnClickListener { serverSend() }
tv_receive_msg?.movementMethod = ScrollingMovementMethod.getInstance()
}
@SuppressLint("SetTextI18n")
fun onReceive(msg: String?) {
runOnUiThread {
tv_receive_msg?.text = "${tv_receive_msg?.getString()}\n$msg"
}
}
private fun link() {
et_server_address?.getString()?.apply {
if (!isNullOrEmpty()) {
clientManager = ClientManager(this) { s, e ->
e(s, e)
if (s.isNullOrEmpty()) {
onReceive("连接服务端失败")
} else {
onReceive("连接服务端:$s")
}
}.apply {
startClient({
e(it)
onReceive(it)
}, {
e(it?.message)
})
}
} else {
toast("地址不能为空")
}
}
}
private fun clientSend() {
et_send_msg?.getString()?.apply {
if (!isNullOrEmpty()) {
clientManager?.sendMsg(this) {
e(it?.message)
}
et_send_msg?.setText("")
} else {
toast("消息不能为空")
}
}
}
private fun serverSend() {
et_send_msg?.getString()?.apply {
if (!isNullOrEmpty()) {
serverManager?.sendMsg(this) {
e(it?.message)
}
et_send_msg?.setText("")
} else {
toast("消息不能为空")
}
}
}
private fun TextView.getString() = text?.toString() ?: ""
private fun toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}