20212324 2023-2024-2 《移动平台开发与实践》第4次作业

本文介绍了在AndroidStudio中使用Kotlin实现Socket编程,包括TCP和UDP的应用,以及如何在模拟器间进行通信。实验涉及创建Socket连接、使用adb工具和优化用户体验的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

20212324 2023-2024-2 《移动平台开发与实践》第4次作业

1.实验内容

(1)掌握在Android Studio上实现Socket编程的原理、过程。

  • Socket 是一种通信机制,用于在网络上的两个节点之间进行通信。
  • 在 Android 平台上,Socket 编程是一种常见的方式,用于实现网络通信,包括客户端和服务端之间的通信。
  • TCP 是一种面向连接的、可靠的、基于字节流的协议,适用于对数据传输的完整性要求较高的场景。
  • UDP 是一种无连接的、不可靠的、基于数据报的协议,适用于对数据传输速度要求较高、允许丢失部分数据的场景。

(2)掌握使用Kotlin语言实现Android Socket服务端和客户端。

  • 使用 Kotlin 实现 Socket通信,包括创建 Socket 对象、建立连接、发送和接收数据等操作。
  • 在 Kotlin 中使用 Socket 类来创建 TCP 套接字对象,Socket 类提供了连接到远程服务器、发送数据、接收数据等方法,是进行网络通信的基础类。

(3)进一步掌握Android Studio自带的模拟器使用和adb工具的使用。

2.实验过程

(1)代码编写过程

  • 本次实验实现两台虚拟机互相连接,实时进行类似微信式通信聊天。这需要程序同时用到server类和client类相关函数完成收发。

  • 设计UI界面,界面需要有启动自己的监听端口和发送给对应ip对应端口信息两部分,还需要完成数据的回显。

    <?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"
        android:padding="16dp">
    
        <EditText
            android:id="@+id/server_port"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="端口" />
    
        <Button
            android:id="@+id/start_server_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="启动本机监听端口" />
    
        <EditText
            android:id="@+id/client_address"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="对方ip" />
    
        <EditText
            android:id="@+id/client_port"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="登入节点" />
    
        <EditText
            android:id="@+id/message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="说点什么" />
    
        <Button
            android:id="@+id/send_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送!" />
    
        <TextView
            android:id="@+id/status"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="bottom" />
    
    </LinearLayout>
    
  • 根据老师提供的Server端和Client端编写MainActivity,实现的核心思路是双方能够进行即时通信,实际上服务端和客户端使用的代码相同,如下:

    package com.example.ssocket
    
    import android.os.Bundle
    import android.os.Handler
    import android.os.Looper
    import android.util.Log
    import android.widget.Button
    import android.widget.EditText
    import android.widget.TextView
    import androidx.appcompat.app.AppCompatActivity
    import java.io.IOException
    
    interface MessageCallback {
        fun onMessageReceived(message: String)
    }
    
    class MainActivity : AppCompatActivity(), MessageCallback {
    
        private lateinit var serverPortEditText: EditText
        private lateinit var clientAddressEditText: EditText
        private lateinit var clientPortEditText: EditText
        private lateinit var messageEditText: EditText
        private lateinit var startServerButton: Button
        private lateinit var sendButton: Button
        private lateinit var statusTextView: TextView
    
        private var serverThread: Thread? = null
        private var isServerRunning = false
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            initializeUI()
            setListeners()
        }
    
        private fun initializeUI() {
            serverPortEditText = findViewById(R.id.server_port)
            clientAddressEditText = findViewById(R.id.client_address)
            clientPortEditText = findViewById(R.id.client_port)
            messageEditText = findViewById(R.id.message)
            startServerButton = findViewById(R.id.start_server_button)
            sendButton = findViewById(R.id.send_button)
            statusTextView = findViewById(R.id.status)
        }
    
        private fun setListeners() {
            sendButton.setOnClickListener {
                sendClientMessage()
            }
            startServerButton.setOnClickListener {
                startServer()
            }
        }
    
        private fun startServer() {
            val portStr = serverPortEditText.text.toString().trim()
            val port = portStr.toIntOrNull()
            if (port != null && !isServerRunning) {
                isServerRunning = true
                serverThread = Thread {
                    try {
                        val server = Server(port, this)
                        statusTextView.append("Server started on port $port\n")
                        server.acceptClients()
                    } catch (e: IOException) {
                        runOnUiThread {
                            statusTextView.append("Error starting server: ${e.message}\n")
                        }
                    }
                }
                serverThread?.start()
            } else {
                runOnUiThread {
                    statusTextView.append("Please enter a valid port or stop the current server first.\n")
                }
            }
        }
    
        private fun sendClientMessage() {
            val address = clientAddressEditText.text.toString()
            val portStr = clientPortEditText.text.toString().trim()
            val port = portStr.toIntOrNull()
            val message = messageEditText.text.toString()
    
            if (address.isNotEmpty() && port != null && message.isNotEmpty()) {
                Thread {
                    try {
                        val client = Client(address, port)
                        runOnUiThread {
                            statusTextView.append("Message sent to server: $message\n")
                        }
                        val response = client.sendAndReceive(message) {
                            Log.d("MySocket", "Received response: $it")
                            runOnUiThread {
                                statusTextView.append("Received response: $it\n")
                            }
                        }
                    } catch (e: IOException) {
                        Log.e("MySocket", "Error occurred while sending/receiving data", e)
                        runOnUiThread {
                            statusTextView.append("Error occurred: ${e.message}\n")
                        }
                    }
                }.start()
            } else {
                runOnUiThread {
                    statusTextView.append("Please enter a valid server address, port, and message.\n")
                }
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            isServerRunning = false
            serverThread?.interrupt()
        }
    
        override fun onMessageReceived(message: String) {
            runOnUiThread {
                statusTextView.append("Message received from client: $message\n")
            }
        }
    }
    
  • Client类和Server类为老师提供的代码,但为了完成server接收到数据后的回显,需要进一步修改server端代码

    • 要实现在server端接收到消息后将消息传送给 MainActivity 并在 statusTextView 中显示,可以使用回调函数(callback)。这样,当server端接收到消息时,它就可以调用 MainActivity 中的回调函数,从而在 UI 中更新消息。

    • 这样,就需要在MainActivity中定义回调函数:

      interface MessageCallback {
          fun onMessageReceived(message: String)
      }
      
    • 之后修改MainActivity和Server,完成相关逻辑(MainActivity代码如上)

    • server:

    package com.example.ssocket
    
    import android.util.Log
    import java.io.IOException
    import java.net.ServerSocket
    import java.net.Socket
    
    class Server(private val port: Int, private val messageCallback: MessageCallback) {
        fun acceptClients() {
            Thread {
                try {
                    val serverSocket = ServerSocket(port)
                    Log.d("MySocket", "Server started on port $port")
                    while (true) {
                        val clientSocket = serverSocket.accept()
                        Log.d("MySocket", "Client connected from ${clientSocket.inetAddress.hostAddress}")
                        handleClient(clientSocket)
                    }
                } catch (e: IOException) {
                    Log.e("MySocket", "Error occurred while starting server", e)
                }
            }.start()
        }
    
        private fun handleClient(clientSocket: Socket) {
            try {
                val inputStream = clientSocket.getInputStream()
                val outputStream = clientSocket.getOutputStream()
    
                // 读取客户端发送的消息
                val buffer = ByteArray(1024)
                val bytesRead = inputStream.read(buffer)
                val messageFromClient = String(buffer, 0, bytesRead)
                Log.d("MySocket", "Data received from client: $messageFromClient")
    
                // 调用 MainActivity 的回调函数,传递消息
                messageCallback.onMessageReceived(messageFromClient)
    
                // 发送 "get it" 响应回客户端
                outputStream.write("get it".toByteArray())
                outputStream.flush()
            } catch (e: IOException) {
                Log.e("MySocket", "Error occurred while handling client", e)
            } finally {
                try {
                    clientSocket.close()
                } catch (e: IOException) {
                    Log.e("MySocket", "Error occurred while closing client socket", e)
                }
            }
        }
    }
    
    • client:
    package com.example.ssocket
    
    import android.util.Log
    import java.io.BufferedReader
    import java.io.InputStreamReader
    import java.io.OutputStreamWriter
    import java.net.Socket
    
    class Client(private val serverAddress: String, private val serverPort: Int) {
    
        fun sendAndReceive(message: String, onReceive: (String) -> Unit): String {
            return try {
                val socket = Socket(serverAddress, serverPort)
                val out = OutputStreamWriter(socket.getOutputStream(), "UTF-8")
                val inBuffer = BufferedReader(InputStreamReader(socket.getInputStream(), "UTF-8"))
    
                out.write(message)
                out.flush()
    
                val response = inBuffer.readLine()
                onReceive(response)
                response
            } catch (e: Exception) {
                Log.e("MySocket", "Error occurred while sending/receiving data", e)
                ""
            }
        }
    }
    
  • 在Manifest文件中添加权限

        <uses-permission android:name="android.permission.INTERNET" />
    

(2)效果演示

  • 本程序能够让两个开启端口监听的虚拟机(主机)在已知对方ip和端口的情况下像微信一样互相“聊天”。在聊天框中,用户能够知道自己已经发送的信息和接收到的信息,并可知道端口开启等其他信息

  • 演示效果如下:

    2024-05-06 21-04-45

3.学习中遇到的问题及解决

  • 问题1:两台虚拟机使用的ip地址相同无法通信

  • 问题1解决方案:使用adb查看虚拟机设备,并修改端口映射,从而以物理机(10.0.2.2)为中介实现10.0.2.2上端口和虚拟机端口的映射,通过这样的单向桥梁实现两台虚拟机的互联

    image-20240506214943889

    image-20240506214949476

    image-20240506214817170

  • 问题2:在编写时一开始实现了接收到的信息在logcat上面的显示,但无法直接在程序聊天框中显示

  • 问题2解决方案:chat了一下以搜索找到了解决方法(见代码编写过程),但程序无法正确运行。之后通过问chat和设置断点找错误的方式找到问题的原因是chat把前面创建对象的代码弄错了,成功纠错!

4.学习感悟和思考

  • 理论与实践的结合:虽然之前学习过 Socket 编程的理论并编写过c语言代码,但亲自动手实践并在 Android Studio 上实现更高级的socket,让我对网络通信的原理和过程有了更加深刻的理解。理论是基础,而实践则是检验理论的最好方式。

  • 问题解决能力的提升:在实验过程中,我遇到了关于虚拟机 IP 地址和端口映射的问题。通过查阅资料、调试代码、使用生成式AI和使用 adb 工具,我不仅解决了问题,还学会了如何使用这些工具来诊断和修复问题。

  • 用户体验的关注:设计 UI 界面时,需要站在使用者角度,更加关注用户体验。一开始我的程序不是这样的,但我感觉那样十分反人类,于是推倒重建形成了这个版本

5.参考材料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值