【2023】Kotlin教程
第三篇 Kotlin进阶
第22章 网络编程
现代的应用程序都离不开网络,网络编程是非常重要的技术。Kotlin标准库网络编程源自于Java提供java.net包,其中包含了网络编程所需要的最基础一些类和接口。这些类和接口面向两个不同的层次:
- 基于Socket的低层次网络编程。Socket采用TCP、UDP等协议,这些协议属于低层次的通信协议,编程过程比较复杂。
- 基于URL的高层次网编程。URL采用HTTP和HTTPS这些属于高层次的通信协议,相对低层编程过程比较容易。
所谓低层次网络编程并不等于它功能不强大。恰恰相反,正因为层次低,Socket编程与基于URL的高层次网络编程比较,能够提供更强大的功能和更灵活的控制,但是要更复杂一些。
22.3 UDP Socket低层次网络编程
UDP(用户数据报协议)就像日常生活中的邮件投递,是不能保证可靠地寄到目的地。UDP是无连接的,对系统资源的要求较少,UDP可能丢包不保证数据顺序。但是对于网络游戏和在线视频等要求传输快、实时性高、质量可稍差一点的数据传输,UDP还是非常不错的。
UDP Socket网络编程比TCP Socket编程简单多,UDP是无连接协议,不需要像TCP一样监听端口,建立连接,然后才能进行通信。
22.3.3 案例:文件上传工具
使用UDP Socket将之前我们的文件上传工具重新实现一下。
案例服务器端UploadServer 代码如下:
package com.dingjiaxiong
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
fun main() {
println("服务端运行...")
DatagramSocket(8080).use { socket ->
FileOutputStream("D:\\DingJiaxiong\\IdeaProjects\\KotlinStudy\\chapter22\\src\\com\\dingjiaxiong\\TestDir\\subDir\\udpupload.jpg").use { fout ->
BufferedOutputStream(fout).use { out ->
// 准备一个缓冲区
val buffer = ByteArray(1024)
// 循环接收数据报包
while (true) {
// 创建数据报包对象, 用来接收数据
val packet = DatagramPacket(buffer, buffer.size)
// 接收数据报包
socket.receive(packet)
// 接收数据长度
val len = packet.length
if (len == 3) {
// 获得结束标志
val flag = String(buffer, 0, 3)
// 判断结束标志, 如果是bye结束接收
if (flag == "bye") {
break
}
}
// 写入数据到文件输出流
out.write(buffer, 0, len)
}
println("接收完成!")
}
}
}
}
与TCP Socket不同UDP Socket 无法知道哪些数据包已经是最后一个了,因此需要发送方发出一个特殊的数据包,包中包含了一些特殊标志。
再看看案例客户端UploadClient代码如下:
package com.dingjiaxiong
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
fun main() {
println("客户端运行...")
DatagramSocket().use { socket ->
FileInputStream("D:\\DingJiaxiong\\IdeaProjects\\KotlinStudy\\chapter22\\src\\com\\dingjiaxiong\\TestDir\\test.jpg").use { fin ->
BufferedInputStream(fin).use { input ->
// 创建远程主机IP地址对象
val address = InetAddress.getByName("localhost")
// 准备一个缓冲区
val buffer = ByteArray(1024)
// 首次从文件流中读取数据
var len = input.read(buffer)
while (len != -1) {
// 创建数据报包对象
val packet = DatagramPacket(buffer, len, address, 8080)
// 发送数据报包
socket.send(packet)
// 再次从文件流中读取数据
len = input.read(buffer)
}
// 创建数据报对象
val packet = DatagramPacket("bye".toByteArray(), 3, address, 8080)
// 发送结束标志
socket.send(packet)
println("上传完成!")
}
}
}
}
先运行起来服务端
运行上传客户端
没毛病。上述是上传文件客户端,发送数据不会堵塞线程,因此没有使用子线程。这个结束标志是字符串bye,服务器端接收到这个字符串则结束接收数据包。