TCP的理解:
TCP:[传输控制协议],(Transmission Control Protocol),通过面向连接的、端到端的可靠数据报发送来保证可靠性。
TCP是通过Socket
完成的,是双向的,端与端之间可以互相发送和接收消息
Socket
属于传输层,因为 TCP / IP
协议属于传输层,解决的是报文如何在网络中传输的问题,功能是建立端与端的连接。
建立和断开
TCP的建立需要三次握手,断开需要四次挥手
三次握手:
-
第一次握手:Client向Server发送了一个报文,表示请求连接
可以理解为:Server确认Client发送报文成功,自己接收成功;Client什么都不可确认。
-
第二次握手:Server向Client发送了一个报文,表示同意了你的请求
可以理解为:Server确认Client发送报文成功,自己接收成功;Client确认自己发送、接收都成功,Server发送成功。
-
第三次握手:Client向Server发送了一个报文,表示收到了你的同意信息
可以理解为:Server和Client都确认自己和对方发送、接收都成功。
四次挥手:保证所有数据都发送接收完成
-
第一次:A对B说:我吃饱了,可以走了
-
第二次:B对A说:好的,我知道你吃饱了
-
第三次:B对A说:我也吃饱了,可以走了
-
第四次:A对B说:好的,走吧
经过四次对话之后,A和B都知道对方已经吃饱了,就可以离开了。
使用
在这个Demo中结合Service
使用的, 因为实现的是定时持续发送和接收,结合Service
可保证在后台更好的存活。如果不使用Service
的话,也可以使用Handler
,这样也可以保证线程的安全,避免下面提到的异常。
- 申请权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-
创建
Socket
对象:mSocket = Socket()
这里需要注意:如果你只是创建了一个
Socket
对象,使用的是无参构造函数,那么你这一步可以在主线程中操作,但是如果你使用的是有参构造函数:socket = Socket("192.168.1.1", 8080)
,那么你千万不能再主线程中执行了,会报:android.os.NetworkOnMainThreadException
异常。第二步的执行连接ip
和hostName
也是同理,不可再主线程中执行。 -
连接
ip
或者hostName
,并且指定超时时间为多少,切记不可在主线程中完成,在Kotlin中可以使用协程来完成,代码如下:
GlobalScope.launch {
mSocket = Socket()
try {
mSocket!!.connect(InetSocketAddress("10.64.1.213", 8080), 2000)
if (mSocket!!.isConnected) {
sendData("taonce")
receiverData()
}
} catch (e: Exception) {
Log.d("taonce", "连接出错:${e.message}")
}
}
- 定时循环发送报文:使用
Timer
和TimerTask
相结合,启动定时加循环作用
/**
* 定时发送数据
*/
private fun sendData(message: String) {
if (mSendTimer == null) {
mSendTimer = Timer()
}
if (mSendTimerTask == null) {
mSendTimerTask = timerTask {
try {
outputStream = mSocket!!.getOutputStream()
outputStream!!.write(message.toByteArray())
outputStream!!.flush()
Log.d("taonce", "发送给服务端内容为:$message")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// 每10秒发送一次
mSendTimer!!.schedule(mSendTimerTask!!, 0, 10 * 1000)
}
-
获取服务端的报文:实现的是定时获取
注意点:在
readLine()
的时候有可能会造成阻塞,如果服务端发送完报文之后不是以/n
结尾或者没有close
掉输出流,那么客户端在接收的时候会一直处于阻塞状态。
/**
* 定时接收数据
*/
private fun receiverData() {
if (mReceiverTimer == null) {
mReceiverTimer = Timer()
}
if (mReceiverTimerTask == null) {
mReceiverTimerTask = timerTask {
try {
inputStream = mSocket!!.getInputStream()
val data = inputStream!!.bufferedReader().use {
it.readLine()
}
Log.d("taonce", "获取服务端内容为:$data")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
mReceiverTimer!!.schedule(mReceiverTimerTask, 0, 10 * 1000)
}
- 开启服务,Mac可以使用Sokit调试Socket,Windows可以使用TCP调试助手,当然还有其他很多可以使用,因为这两种是我本人亲测,所以推荐大家使用。
val intent = Intent(MainActivity.this, SocketService::java.class);
startService(intent);
结果如下图所示:
tcp-client.png tcp-server.png
注意的是:使用Sokit调试的时候需要将手机和电脑连在同一个个WiFi下。
注意内存泄漏
注意点:
-
IO流的关闭也就是
OutputStream
和InputStream
的关闭就会导致Socket的关闭,如果你想在关闭IO流之后还进行Socket的操作,那么可以使用socket?.shutdownOutput()
和socket?.shutdownInput()
,这两个可以关闭IO流但是不会关闭Socket。 -
关闭任何一个IO流最好是用
try-catch
语句包含起来。如果将多个IO流的关闭放在一起,第一个没有关闭成功,那么后续的IO流将都不会关闭成功。 -
在Service的
onDestroy()
方法中需要释放掉所有的输入输出流和定时器还有Sokcet
,代码如下:
override fun onDestroy() {
// 销毁定时器
mSendTimer?.purge()
mSendTimer?.cancel()
mReceiverTimer?.purge()
mReceiverTimer?.cancel()
mReceiverTimerTask?.cancel()
mSendTimerTask?.cancel()
mSendTimer = null
mReceiverTimer = null
mSendTimerTask = null
mReceiverTimerTask = null
// 销毁输入输出流
try {
outputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
try {
inputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
// 销毁Socket
mSocket?.close()
mSocket = null
super.onDestroy()
}
通过上面代码可以发现,Kotlin在执行这些操作的时候比Java的写法要简单多了,不需要再一个个先判断是否为空,直接用安全运算符?.
就行了,简洁方便。
完整代码如下:
package com.example.taoyongxiang.kotlintcp
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.InputStream
import java.io.OutputStream
import java.net.InetSocketAddress
import java.net.Socket
import java.util.*
import kotlin.concurrent.timerTask
/**
* Author: taoyongxiang
* Date: 2018/11/10
* Project: MyTCP
* Desc:
*/
class TcpService : Service() {
private val mBinder = TcpBinder()
private var mSocket: Socket? = null
//定时发送
private var mSendTimer: Timer? = null
private var mSendTimerTask: TimerTask? = null
private var outputStream: OutputStream? = null
//定时接收
private var mReceiverTimer: Timer? = null
private var mReceiverTimerTask: TimerTask? = null
private var inputStream: InputStream? = null
override fun onBind(intent: Intent?): IBinder {
return mBinder
}
inner class TcpBinder : Binder() {
fun getService(): TcpService {
return this@TcpService
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
initTcpParams()
return super.onStartCommand(intent, flags, startId)
}
private fun initTcpParams() {
if (mSocket == null) {
Log.d("taonce", "thread: ${Thread.currentThread()}")
// 连接不可在主线程!!!!
// 这里使用Kotlin中的协程,Kotlin版本为1.3.0,coroutines版本为1.0.1
// Java中可使用Thread来连接
GlobalScope.launch {
Log.d("taonce", "thread: ${Thread.currentThread()}")
mSocket = Socket()
try {
mSocket!!.connect(InetSocketAddress("192.168.0.104", 8080), 2000)
if (mSocket!!.isConnected) {
sendData("taonce")
receiverData()
}
} catch (e: Exception) {
Log.d("taonce", "连接出错:${e.message}")
}
}
}
}
/**
* 定时发送数据
*/
private fun sendData(message: String) {
if (mSendTimer == null) {
mSendTimer = Timer()
}
if (mSendTimerTask == null) {
mSendTimerTask = timerTask {
try {
outputStream = mSocket!!.getOutputStream()
outputStream!!.write(message.toByteArray())
outputStream!!.flush()
Log.d("taonce", "发送给服务端内容为:$message")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// 每10秒发送一次
mSendTimer!!.schedule(mSendTimerTask!!, 0, 10 * 1000)
}
/**
* 定时接收数据
*/
private fun receiverData() {
if (mReceiverTimer == null) {
mReceiverTimer = Timer()
}
if (mReceiverTimerTask == null) {
mReceiverTimerTask = timerTask {
try {
inputStream = mSocket!!.getInputStream()
val data = inputStream!!.bufferedReader().use {
it.readLine()
}
Log.d("taonce", "获取服务端内容为:$data")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
mReceiverTimer!!.schedule(mReceiverTimerTask, 0, 10 * 1000)
}
override fun onDestroy() {
// 销毁定时器
mSendTimer?.purge()
mSendTimer?.cancel()
mReceiverTimer?.purge()
mReceiverTimer?.cancel()
mReceiverTimerTask?.cancel()
mSendTimerTask?.cancel()
mSendTimer = null
mReceiverTimer = null
mSendTimerTask = null
mReceiverTimerTask = null
// 销毁输入输出流
try {
outputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
try {
inputStream?.close()
} catch (e: Exception) {
e.printStackTrace()
}
// 销毁Socket
mSocket?.close()
mSocket = null
super.onDestroy()
}
}
写在最后
每个人不是天生就强大,你若不努力,如何证明自己,加油!
Thank You!
--Taonce
如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入
专注Kotlin和Android知识的公众号
网友评论