day14 channel实战使用 with webSocket,後面离题讲android接localhost

前言,今天写一写就离题了QQ,前面用ktor架websocket,在手机app接起来,复习一下channel的特性,後面离题讲了手机怎麽接到localhost

正文

简单介绍,webSocket是一个客户端和服务器之间进行双向持续对话,server也可以发讯息给client,不像restful api要由client主动发出请求。

其他关於websocket的介绍,自己上网找,网上资源很多,我就直接带code

首先,网上大多会告诉你,这是免费的,但是他的连线极其不稳,有时还连不上

//刚刚测还是连不上
ws://echo.websocket.org

那android本身其实能用MockWebServer去模拟server,但我偏不,我要用ktor自己架,我不只要自己架,我还会告诉你怎麽从实机连线到电脑的localhost

MockWebServer,好像原本是测试用途,我不喜欢这样混用,所以用ktor架了

那用ktor要怎麽架websocket呢?
文档,对的喔我也都是看文档的哈哈哈哈哈

//intelliJ
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused")
fun Application.module() {

    val scope = CoroutineScope(Job())
    install(WebSockets)
    routing {
        webSocket("/chat") {
            send("You are connected!")
            randomResponse(scope, this)
            for(frame in incoming) {
                frame as? Frame.Text ?: continue
                val receivedText = frame.readText()
                send("You said: $receivedText")

            }
        }
    }


}

fun randomResponse(scope: CoroutineScope, socket:DefaultWebSocketServerSession){
    val randomSample = arrayOf(
        "I am hungry",
        "Harry Potter",
        "ciao, mon amigo",
        "To be or not to be",
        "Android developer"
    )
    scope.launch {
        while(isActive){
            delay(1500)
            socket.send(randomSample[(0..4).random()])
            
            yield()
        }
    }
}

除了randomResponse以外,其他都跟文档一样,这整串就是,帮我开启一个websocket,建立一个"/chat"的路径(url path),当有人连上这个路径时,先告诉她"You are connected!", 接着透过coroutine建立一个randomResponse方法,一直传讯息给client,最後再用回圈针对收到的讯息做系统回复

简单,好懂

这边为求方便没有cancel coroutine scope,好孩子不要学

执行後,建议先用网页别人写好的websocket测试,随便找个测试网站,给他ws://localhost:8080/chat`试试,如果收发都没问题就能进下一步

android开发

fragment我用一个textview来接

//Android studio
class SocketFragment : Fragment() {

    private lateinit var binding:FragmentSocketBinding
    private val mAdapter = ChatAdapter()
    private val viewModel by viewModels<SocketViewModel>()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_socket, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        binding = FragmentSocketBinding.bind(view)

       lifecycleScope.launch {
           viewModel.messageChannel.consumeEach {
               Timber.d("in fragment $it")
               binding.allMsg.text = StringBuilder(binding.allMsg.text).append(it)
               binding.allMsg.invalidate()
           }
       }

        binding.sentMsg.setOnClickListener {
            viewModel.sentMessage( binding.ed.text.toString() )
            binding.ed.setText("")
        }

    }
}

socket在viewModel实例,比较好控制生命周期

class SocketViewModel: ViewModel() {

    private var mWebSocket: WebSocket? = null
    private val mWbSocketUrl = "ws://127.0.0.1:8080/chat"
    val messageChannel = Channel<String>()

    init {
        initSocket()
    }

    fun initSocket() {
        val mClient = OkHttpClient.Builder()
            .pingInterval(10, TimeUnit.SECONDS)
            .build()
        val request: Request = Request.Builder()
            .url(mWbSocketUrl)
            .build()

        mWebSocket = mClient.newWebSocket(request, object : WebSocketListener(){
            override fun onMessage( webSocket: WebSocket,  text: String) {
                super.onMessage(webSocket, text)
                viewModelScope.launch {
                    messageChannel.send(text + "\n")
                    Timber.d("receive message $text")
                }
            }
            override fun onOpen(webSocket: WebSocket, response: Response) {
                super.onOpen(webSocket, response)
                Timber.d("success connect")
            }
            override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
                super.onClosing(webSocket, code, reason)
                mWebSocket?.close(code, reason)
                mWebSocket = null
            }
            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                super.onFailure(webSocket, t, response)
                Timber.e("fail connect")
                Timber.e(response?.message)
            }
        })
    }

    fun sentMessage(s:String){
        mWebSocket?.send(s)
    }

    override fun onCleared() {
        super.onCleared()
        messageChannel.cancel()
        mWebSocket?.cancel()
        mWebSocket = null
    }
}

可以看到,我用channel在viewModel和fragment之间传讯息,记得channel的特性吗?

  1. 可以在不同的Coroutine之间传送讯息
  2. 保证传输和接收的顺序

不知道的,可以先看这篇

先给效果,大概长这样

离题一下,讲个麻烦的,android开发接localhost

方法一,模拟器

private val mWbSocketUrl = "ws://10.0.2.2:8080/chat"

方法二,实机有线

在chrome的网址列输入chrome://inspect/#devices

  1. 确定手机usb侦错有打开

  2. 确定Remote Target有设备,可打开手机浏览器确定网页也有被抓到

    大概长这样,我是把chrome调成暗色,所以你们画面可能会是白底的

  3. 设置port, ip address
    其实我也有些不懂,目前测试
    直播流接Discover USB devices就好
    websocket要两个都接

接法是

Discover USB devices

Discover network targets

两个都是接8080的就可以了,其他的是我之前接直播流用的port

private val mWbSocketUrl = "ws://127.0.0.1:8080/chat"

方法三,实机无线

private val mWbSocketUrl = "ws://电脑ip:8080/chat"

大家应该都知道网路有七层吧
network layer

不知道的,也能做,在这里讲网路分层就离题太远了

anyway, 我有时开发会忘记带usb线,这时我就会用无线侦错(跳过不讲无线侦错部分),那方法二是透过chrome的开发功能连接,无线时要怎麽办呢?

从网域连线~~~~

首先把电脑和手机连到同一个wifi,请确定wifi是可信任的,因为等等要开防火墙的port
在cmd下ipconfig拿到电脑的ip位址

window控制台

进阶设置>输入规则>新增规则

选择

post

连线设置

设置,如果建议取消公用,然後将wifi加入至家用或工作

建立好就会这样

电脑的ip每次都会更改,可以参考这篇设置ip,方法三我也是参考这篇的


<<:  15【雷坑】千万别肖想用 APCS 升大学

>>:  Day 15 | 魔术方块AR游戏开发Part4 - 面的旋转(下)+游戏机制

我要成为时间管理大师!

本系列文记录了我近三年的转变,系列文的内容基本上都会与资讯科技扯上边,希望本文也可以对与我有相似背景...

[DAY5] 病识感──当我们关注到测试

能载舟,能覆舟 前几篇似乎说了很多 Rails 的坏话,但其实 Rails 是一套工具,工具没有好坏...

Day 29 - 3D绘图篇 - 噪声地形演算I - 成为Canvas Ninja ~ 理解2D渲染的精髓

再两天 ~!! 在铁人赛的最後,我想要给各位带来的是噪声地形的演算~ 之所以想要写这个题目,原因是...

Day2-他看我是个练武奇才-规格书(递)

成为武林高手的第一步-轻小说阅读模式启动【ON】 ------------------------ ...

04 你的专研不是你的专研

升上高中也有专题研究的学分。为了找到适合的题目,我和同个专研的同学一起到师大资工(和科学班合作的校系...