1

我有两个应用程序通过 TCP/IP 连接进行交互;现在我需要他们能够通过串行连接进行交互。

套接字 IO 和串行 IO 之间存在一些差异,这使得移植没有我希望的那么简单。

区别之一是关于发送/写入超时的语义以及应用程序可能对成功通过连接的数据量做出的假设。知道这个数量后,应用程序也知道它需要在以后选择传输哪些剩余数据。

套接字发送

像 socket.send(string) 这样的调用可能会产生以下结果:

  1. 整个字符串已被 TCP/IP 堆栈接受,并返回字符串的长度。
  2. 字符串的一部分已被 TCP/IP 堆栈接受,并返回该部分的长度。应用程序可以稍后传输字符串的其余部分。
  3. 如果套接字配置为使用超时并且发送方使用数据淹没了连接,则会引发 socket.timeout 异常。这意味着(如果我理解正确的话) TCP/IP 堆栈没有接受字符串的任何字节,因此应用程序可能会稍后尝试发送整个字符串。
  4. 由于连接的一些问题,引发了 socket.error 异常。

PySerial.Serial.write

PySerial API 文档对 Serial.write(string) 进行了如下说明:

write(data)
  Parameters:   
    data – Data to send.
  Returns:  
    Number of bytes written.
  Raises 
    SerialTimeoutException:
      In case a write timeout is configured for the port and the time is exceeded.

  Changed in version 2.5: Write returned None in previous versions.

这个规范给我留下了一些不确定的问题:

  1. 在哪些情况下,“write(data)”返回的写入字节数可能少于数据的长度?是否只能在非阻塞模式下(writeTimeout = 0)?
  2. 如果我使用正的 writeTimeout 并且引发了 SerialTimeoutException,我怎么知道有多少字节进入了连接?

我还观察到一些我没想到的 serial.write 行为。

该测试尝试通过慢速连接发送长字符串。发送端口使用 9600,8,N,1 并且没有流控。接收端口也已打开,但没有尝试从中读取数据。

  1. 如果 writeTimeout 为正但不够大,则发送者预期会收到 SerialTimeoutException。
  2. 如果 writeTimeout 设置得足够大,则发送方预期会成功写入所有数据(接收方不关心读取,我们也不关心)。
  3. 如果 writeTimeout 设置为 None,则发送方会意外获得 SerialTimeoutException 而不是阻塞,直到所有数据都断开连接。我错过了什么吗?

我不知道这种行为是否典型。万一这很重要,我在 Windows 7 64 位上使用 PySerial 进行实验,使用两个 USB-to-COM 适配器通过零调制解调器电缆连接;该设置似乎是可操作的,因为 Tera Term 的两个实例可以通过它相互交谈。

了解人们是否以除中止连接和通知用户问题之外的任何方式处理串行写入超时将很有帮助。

由于我目前不知道在超时发生之前写入的数据量,因此我正在考虑使用非阻塞写入并将类套接字超时语义保持在该级别之上的解决方法。我不希望这是一个非常有效的解决方案 (:-)),但幸运的是,我的应用程序交换相对不频繁和短消息,因此性能应该在可接受的范围内。

[编辑]

仔细研究非阻塞串行写入

我写了一个简单的程序来看看我是否理解非阻塞写是如何工作的:

import serial

p1 = serial.Serial("COM11") # My USB-to-COM adapters appear at these high port numbers
p2 = serial.Serial("COM12")

message = "Hello! " * 10
print "%d bytes in the whole message: %r" % (len(message), message)

p1.writeTimeout = 0 # enabling non-blocking mode
bytes_written = p1.write(message)
print "Written %d bytes of the message: %r" % (bytes_written, message[:bytes_written])  

print "Receiving back %d bytes of the message" % len(message)
message_read_back = p2.read(len(message))
print "Received back %d bytes of the message: %r" % (len(message_read_back), message_read_back) 

p1.close()
p2.close()

我得到的输出是这样的:

整个消息中的 70 个字节:'你好!你好!你好!你好!你好!你好!你好!你好!你好!你好!'
写入 0 字节的消息:''
接收回 70 字节的消息
接收回 70 字节的消息:'你好!你好!你好!你好!你好!你好!你好!你好!你好!你好!'

我很困惑:发送者认为没有数据发送,但接收者得到了一切。我必须在这里遗漏一些非常基本的东西......

非常欢迎任何意见/建议/问题!

4

1 回答 1

1

由于没有记录,让我们看一下源代码。我只查看了POSIXWin32实现,但很明显,至少在这两个平台上:

  1. 任何情况下都不会write(data)返回写入的字节数少于数据的长度、超时或其他情况;它总是要么返回 full len(data),要么引发异常。
  2. 如果您使用正数writeTimeout并且SerialTimeoutException引发了,则根本无法判断发送了多少字节。

特别是,在 POSIX 上,到目前为止发送的字节数仅存储在一个本地变量中,一旦引发异常,该变量就会丢失;在 Windows 上,它只做了一个重叠WriteFile,并且除了成功的“写了所有东西”之外的任何东西都会引发异常。

我假设您至少关心这两个平台中的一个。(如果不是,您可能没有编写跨平台代码,并且可以查看您关心的一个平台。)因此,您的问题没有直接的解决方案。

如果您描述的解决方法是可以接受的,或者是不同的解决方法(比如一次只写入一个字节——这可能效率更低,但可能更简单),那么就这样做。

或者,您将不得不编辑write您关心的实现(无论您是通过分叉包并编辑您的分叉,Serial.write在运行时进行猴子补丁,还是只是编写一个serial_write函数并调用serial_write(port, data)而不是port.write(data)在您的脚本中)以提供您想要的信息。

这看起来不太难。例如,在 POSIX 版本中,您只需在任何一行len(data)-t之前的某个地方存储。raise writeTimeoutError您可以将其粘贴在Serial对象的属性中,或者将其作为额外参数传递给异常构造函数。(当然,如果您正在尝试编写跨平台程序,并且您对所有平台的了解不足以编写适当的实现,那么这可能不是一个好的答案。)

实际上,鉴于实现您想要的东西并不难,您可能希望在 pyserial 跟踪器上添加一个功能请求(最好是带有补丁)。

于 2013-08-22T02:23:37.140 回答