[sixinice 原创翻译] AsyncStreams 的说明(教程)

52manhua

Member
Licensed User
Longtime User
译者注: AsyncStreams 在传输网络数据中是一个非常有用的对象,翻一下大家好学习.

原始链接:https://www.b4x.com/android/forum/threads/asyncstreams-tutorial.7669/

在 RandomAccessFile 库中提供了一个新的对象类型: AsyncStreams.
AsyncStreams 帮助你 从 inputstream 读取数据 从 OutputStream 写入数据,不需要阻断你的程序执行. 读取和写入是在两个不同的线程进行的.

当接收到新的数据的时候,事件通过这个 NewData 事件触发
当你写入数据到 OutputStream 中的时候,数据添加到一个内部的序列,发送到系统后台中去

AsyncStreams 在使用 网络数据流 或者 蓝牙数据流 是非常有用的.

就是说,如果你的程序可能为等待一个值而不得不中断的时候, 就可以使用这样的流对象.
我们用来处理 串口 和网络的这个方法,等同于使用一个计时器随时检测是否在 byte(数组) 正在被传输,是否存在等待传输的 byte. 但是即使如此,仍然可能有 byte 处于不可用(传输)的状态,我们需要等待直到他们可用.
使用 AsyncStreams 有简单,安全的优点.

这个 AsyncStreams 对象有两种模式: 普通模式 ,"前缀模式" 两种模式Regular mode and "prefix mode". 用起来就像命名描述的那样.

这段代码演示了一个简单的程序,这个程序发送 文本到一个已经连接上的 设备或者电脑:

B4X:
  Sub Process_Globals
  Dim AStreams As AsyncStreams
  Dim Server As ServerSocket
  Dim Socket1 As Socket
  End Sub
  Sub Globals
  Dim EditText1 As EditText
  End Sub

  Sub Activity_Create(FirstTime As Boolean)
  If FirstTime Then
  Server.Initialize(5500, "Server")
  Server.Listen
  Log("MyIp = " & Server.GetMyIP)
  End If
  EditText1.Initialize("EditText1")
  EditText1.ForceDoneButton = True
  Activity.AddView(EditText1, 10dip, 10dip, 300dip, 60dip)
  End Sub

  Sub Server_NewConnection (Successful As Boolean, NewSocket As Socket)
  If Successful Then
  ToastMessageShow("Connected", False)
  Socket1 = NewSocket
  'Can only use prefix mode if both sides of the connection implement the prefix protocol!!!
  AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")
  Else
  ToastMessageShow(LastException.Message, True)
  End If
  Server.Listen
  End Sub

  Sub AStreams_NewData (Buffer() As Byte)
  Dim msg As String
  msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
  ToastMessageShow(msg, False)
  Log(msg)
  End Sub

  Sub AStreams_Error
  ToastMessageShow(LastException.Message, True)
  Log("AStreams_Error")
  End Sub

  Sub AStreams_Terminated
  Log("AStreams_Terminated")
  End Sub

  'press on the Done button to send text
  Sub EditText1_EnterPressed
  If AStreams.IsInitialized = False Then Return
  If EditText1.Text.Length > 0 Then
  Dim buffer() As Byte
  buffer = EditText1.Text.GetBytes("UTF8")
  AStreams.Write(buffer)
  EditText1.SelectAll
  Log("Sending: " & EditText1.Text)
  End If
  End Sub

  Sub Activity_Pause(UserClosed As Boolean)
  If UserClosed Then
  Log("closing")
  AStreams.Close
  Socket1.Close
  End If
  End Sub

只要有一个连接,我们就能初始化 AsyncStreams 对象:
B4X:
  AStreams.InitializePrefix(Socket1.InputStream, False, Socket1.OutputStream, "AStreams")

发现有数据传输的时候,我们就能转换 byte 数组到字符串显示显示:
B4X:
  Sub AStreams_NewData (Buffer() As Byte)
  Dim msg As String
  msg = BytesToString(Buffer, 0, Buffer.Length, "UTF8")
  ToastMessageShow(msg, False)
  End Sub

当用户输入字符串到 edittext 中的时候, 文本就被发送:

B4X:
  Sub EditText1_EnterPressed
  If AStreams.IsInitialized = False Then Return
  If EditText1.Text.Length > 0 Then
  Dim buffer() As Byte
  buffer = EditText1.Text.GetBytes("UTF8")
  AStreams.Write(buffer)
  EditText1.SelectAll
  Log("Sending: " & EditText1.Text)
  End If
  End Sub

"前缀"模式

当对象初始化为前缀模式的时候,数据需要满足下面的条件: 每一条消息,比特数组应该给出前缀标明数组的长度 (长度为 int 类型). 因此如果其他设备发送给我们一个 100byte 的数据这条数据需要包含4个比特的容量保存 100 这个值,后面跟着 100 比特的数据
NewData 事件会触发因为接收到这个 100 个字节事件接收的数据并不包含4个字节的前缀.
当你发送数据,使用 Write 或者 Write2 的时候,比特数组的长度自动作为前缀添加到消息里面.
如果你在传输的两边设备都能使用前缀的时候,那么尽量用这种模式. 因为拥有 带有长度的前缀信息 ,当 NewData 事件触发时会获得完整的数据 . 举个例子,需要100比特的数据,只有 60比特传输完毕,程序会等待 剩下的 40比特到达. 在普通模式中,这个事件会触发两次,你需要自己处理两个部分的消息
AsyncStreams 对象同样需要处理这钟情况: 当只需要 100 比特的时候,却有多余数据传输到达了.

注意,这个 WriteStream 方法,只适用于前缀模式,使用一个包含错误侦测内部的协议.

错误处理

错误事件会被触发,如果有任何错误的话. 你能使用 LastException 找到错误的原因. 在多数情况中如果发生错误,你想要关闭这个链接.

关闭连接对象的时候, Terminated 事件会被触发.

相关链接:
AsyncStreamsText 类 ,提供了一个备选,使用 前缀模式来传输文本.
新的例子,配合使用 starter Service 和 B4xSerialzator: https://www.b4x.com/android/forum/threads/network-asyncstreams-b4xserializator.72149/

如何选择 模式 和 框架?

有四种模式或者框架可以选择: AsyncStreams, AsyncStreams前缀模式,AsyncStreams 文本类,AsyncStreams 对象类

当双方的连接都是用 前缀模式 , 只能使用前缀模式在很多的情况下下,你都能在传输的双方使用这种模式. 举个例子,比如建立一个聊天程序.
你需要使用 AsyncStreamsObject. AsyncStreamsObject 不能显示进度,虽然能传输任何大小的文件. 如果你需要传输一个非常大的文件,你可以参考一下下面附件中的 FileTransfer 例子.

如果你和一个非 b4a 的应用进行交互, 不能使用前缀模式协议,你有两个选择:
-其一,数据发送和接收是基于文本,每个消息都以 一个 结束字符结尾,你就可以使用 AsyncStreamsText 举个例子,比如你需要连接一个第三方的 gps. 注意你可能修改这个类,适应不同的分隔符号.
使用 AsyncStreamsText 类 的优点在于,它能正确地建立消息,你不会收到截断的消息或者多余的消息You will not receive partial messages (or multiple messages together).
- 其二,在其他情况中,你应该使用 AsyncStreams 的普通模式. 在不能确定,每一条发送的消息都是一条单独的消息的时候. 事实上, 或多或少地保证不会发生这样的情况. 所以你的代码需要自己解决, 修正接收和 处理这些消息.

要点

当接受到一个新消息的时候,后台线程需要处理数据的,朝主线程队列发送一条消息. 这样的消息会触发 NewData 事件. 如果你接受很多重复的消息的时候,如果显示一个模式窗口,比如 msgbox ,事件的队列可能会发生改变.
 
Last edited:
Top