实验四 Android Socket编程技术

实验四 Android Socket编程技术

1. 实验目的

  • 掌握Android平台上Socket编程的基本概念。
  • 学习使用Kotlin语言实现Android Socket服务端和客户端。
  • 理解TCP/UDP协议在Socket通信中的应用。

2.实验过程

2.1 Android socket涉及的知识点

socket编程可以通过多种的高级编程语言来实现,本次实验使用的是kotlin语言并且相对应的要加入一些UI控件来实现简单的消息通讯。如下是本次实验我使用到的知识点。
关于使用kotlin实现socket

  1. 创建和关闭 Socket:在 Kotlin 中,我们使用 Socket(InetAddress, port) 构造函数创建一个 Socket。close() 函数用于关闭一个 Socket。
val socket = Socket(InetAddress.getByName("192.168.1.1"), 5000)

socket.close()
  1. 输入输出流:Socket 通信通过输入输出流进行,对应的函数是 getInputStream() 和 getOutputStream()。Kotlin 支持 use 函数,可以自动关闭流。
      socket.getOutputStream().use { it.write("Hello".toByteArray()) }
  1. 线程处理:所有的网络操作都需要在子线程中进行。Kotlin 的 Thread 类和 runOnUiThread 函数让线程处理变得简单。
   Thread {
       // socket operations
       runOnUiThread {
           // UI operations
       }
   }.start()

关于Android控件的使用

  1. 使用EditText让用户输入要发送的消息,用Button做为发送动作的触发,用TextView显示接收到的信息。
  2. Android规定网络操作不能在主线程进行,需要开启子线程进行网络请求。所以需要使用Handler或者其它的方式,把网络请求运行在子线程,然后回到主线程更新UI。
  3. 本次实验使用的是handler。Handler用于处理线程中的消息。当你在子线程中获取到数据后,可以通过Handler将数据发送到主线程,然后在主线程中更新UI。
    receive_message.append("\n"+ response)。

2.2 实验过程

2.2.1代码部分

本次实验参考指导书给的Java代码和布局文件的模式进行了修改。

布局文件activity_main.xml布局如下。服务端和客户端布局大致相同,不同的只是更换控件的id。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/startServer"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:text="Start Server" />
        <Button
            android:id="@+id/stopServer"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:text="Stop Server" />
    </LinearLayout>
    <TextView
        android:id="@+id/receive_message"
        android:layout_width="match_parent"
        android:layout_height="365dp" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <EditText
            android:id="@+id/edit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="7.5"/>
        <Button
            android:id="@+id/send"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2.5"
            android:text="send"/>
    </LinearLayout>
</LinearLayout>

接下来是客户端的MainActivity.kt代码

class MainActivity : AppCompatActivity() {
 
    private var mMainHandler: Handler? = null
    private var socket: Socket? = null
    private val mThreadPool = Executors.newCachedThreadPool()
    private var isReader: InputStream? = null
    private var isr: InputStreamReader? = null
    private var br: BufferedReader? = null
    private var response: String? = null
    private var outputStream: OutputStream? = null
    var threadRunning = false
 
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val receive_message:TextView = findViewById(R.id.receive_message)
        receive_message.gravity = Gravity.LEFT
        mMainHandler = Handler(Handler.Callback { msg ->
            when (msg.what) {
                0 -> receive_message.append("\n"+ response)
            }
            false
        })
        val btnConnect:Button = findViewById(R.id.connect)
        btnConnect.setOnClickListener {
            mThreadPool.execute {
                try {
                    socket = Socket("10.0.2.2", 8080)
                    threadRunning = true
                    val readThread = ReadThread()
                    readThread.start()
                    println(socket!!.isConnected)
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
 
 
        val btnSend:Button = findViewById(R.id.send)
        btnSend.setOnClickListener {
            val mEdit:EditText = findViewById(R.id.edit)
            val message = mEdit.text.toString()
            receive_message.gravity = Gravity.RIGHT
            receive_message.append("\nSent: $message")
            mThreadPool.execute {
                try {
                    outputStream = socket!!.getOutputStream()
                    outputStream!!.write((message + "\n").toByteArray(charset("utf-8")))
                    outputStream!!.flush()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
            mEdit.setText("")
        }
        val btnDisconnect:Button = findViewById(R.id.disconnect)
        btnDisconnect.setOnClickListener {
            threadRunning = false
            try {
                outputStream!!.close()
                br!!.close()
                socket!!.close()
                println(socket!!.isConnected)
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
    inner class ReadThread : Thread() {
        override fun run() {
            super.run()
            while (threadRunning) {
                try {
                    isReader = socket!!.getInputStream()
                    isr = InputStreamReader(isReader)
                    br = BufferedReader(isr)
                    response = br!!.readLine()
 
                    if (response != null) {
                        val msg = Message.obtain()
                        msg.what = 0
                        mMainHandler!!.sendMessage(msg)
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
    }
}

在本代码中,主要有以下几个重要部分。

  1. 初始化部分:在 MainActivity 类中,定义了一些用于网络通信的变量,用于记录 TCP 连接、用于接收和发送数据的对象、用于记录从服务器接收的回复等。
  2. 连接服务器部分:当点击 connect 按钮时,使用定义的线程池 mThreadPool 创建了一个新线程,尝试通过 TCP 连接服务器,服务器的 IP 地址和端口在 Socket 构造函数中给出。如果连接成功,就创建并启动一个 ReadThread 线程来读取服务器返回的消息。
  3. 当点击 send 按钮时,通过 outputStream 向服务器发送输入框中的文本内容。
  4. 读取服务器消息部分:在 ReadThread 中,当 socket 连接正常且 threadRunning 为 true 时,不断尝试从服务器读取传回的消息。

服务端的MainActivity.kt代码与客户端的代码相对应,接下来给出服务端代码的关键部分,即与客户端实现连接的代码片段。

  1. 建立 ServerSocket

    serverSocket = ServerSocket(8081)
    

    这行代码在服务器端创建一个绑定到指定端口(此处为8081)的 ServerSocket。

  2. 接受客户端的连接请求:

    socket = serverSocket!!.accept()
    

    accept() 方法用于接受客户端发出的连接请求并返回一个 Socket 对象,这个对象就代表了服务器与客户端之间的连接。

  3. 开启服务器的代码:

val btnStartServer:Button = findViewById(R.id.startServer)
btnStartServer.setOnClickListener {
    mThreadPool.execute {
        try {
            serverSocket = ServerSocket(8081)
            while (true) {
                socket = serverSocket!!.accept()
                threadRunning = true
                val readThread = ReadThread()
                readThread.start()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

以上代码块在按钮btnStartServer的点击事件中开启一个新线程来开启服务器并接受客户端连接请求。
4. 关闭服务器的代码:

val btnStopServer:Button = findViewById(R.id.stopServer)
btnStopServer.setOnClickListener {
    threadRunning = false
    try {
        outputStream!!.close()
        br!!.close()
        serverSocket!!.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

以上代码块在按钮btnStopServer的点击事件中尝试关闭服务器,同时关闭输入、输出流及socket连接。

2.2.2运行部分

运行的结果如下所示。可以实现两边的简单通讯

3.遇到的问题以及解决方法

问题一:客户机收到信息没有立即打印信息

解决:手动输入一个换行符解决

问题二:不知道如何实现两个安卓模拟器的通信,客户端直接连接服务端的IP不起作用

解决:问题二的解决方法:需要把模拟器的端口映射到PC的端口,通过连接PC端的端口来把请求重定向发送给模拟器。

4.学习感悟

本次实验中,在 Android 平台上实现 TCP 客户端和服务器的基本框架。这两个例子都使用了 Socket 编程和多线程技术,主要涉及了以下的知识点:

  1. Socket 编程:通过 Socket 对象进行网络通信。客户端使用 Socket(ip, port) 方法连接到服务器,服务器使用 ServerSocket(port) 创建一个服务器 Socket,并使用 accept() 方法接受客户端的连接。
  2. 多线程技术:由于网络操作不能在主线程(UI线程)中进行,因此我们需要使用多线程技术。在这里,一个新的线程被创建来等待并处理网络连接。
  3. 输入输出流:使用 getInputStream() 和 getOutputStream() 获取输入和输出流,从而能够读取和发送数据。
  4. 在Android中,更新UI需要在主线程中进行。但是我们不能直接在其他线程中更新UI,可以借助Handler实现线程间的通信来更新UI。

通过进行Android平台上Socket编程的实验,我对Socket编程的基本概念有了更深入的理解。在实验过程中,我学会了如何在Android平台上使用Kotlin语言来实现Socket服务端和客户端,加深了我对于Socket编程的基本原理的理解,包括Socket的建立、连接、通信和关闭等过程。这为我今后在Android开发中处理网络通信提供了重要的基础

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值