我有两个应用程序通过TCP/IP连接进行交互;现在我需要他们能够通过串行连接进行交互。
套接字IO和串行IO之间有一些区别,这使得移植不像我希望的那样微不足道。
其中一个差异是关于发送/写入超时的语义,以及应用程序可能对连接中成功传递的数据量做出的假设。知道了这个数量,应用程序也知道以后需要传输什么剩余数据
Socket.send
像socket.send(字符串)这样的调用可能会产生以下结果:
- 整个字符串已被TCP/IP堆栈接受,并且返回字符串的长度
- 字符串的一部分已被TCP/IP堆栈接受,并且返回该部分的长度。应用程序可以发送字符串的其余部分稍后
- 如果套接字配置为使用超时,发送方会用数据淹没连接。这意味着(如果我理解正确的话)字符串已被TCP/IP堆栈接受,因此应用程序稍后可能会尝试发送整个字符串
- 由于的某些问题,引发了socket.error异常连接
PySerial.Serial.write
PySerial API文档介绍了以下关于Serial.write(字符串)的内容:
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.
这个规范给我留下了一些不确定的问题:
- 在哪些情况下,"写入(数据)"返回的写入字节更少而不是数据的长度?只有在非阻塞状态下才可能模式(writeTimeout=0)
- 如果我使用正的writeTimeout,并且SerialTimeoutException为引发,我如何知道连接中有多少字节
我还观察到了serial.write的一些我没有预料到的行为。
该测试尝试通过慢速连接发送一个长字符串。发送端口使用9600,8,N,1,无流量控制。接收端口也处于打开状态,但没有尝试从中读取数据。
- 如果writeTimeout为正但不够大,则发送方预期获取SerialTimeoutException
- 如果writeTimeout设置得足够大,则发送方预期会写入所有数据成功(接收者不想阅读,我们也不想)
- 如果writeTimeout设置为None,则发送方意外地获得SerialTimeoutException而不是阻塞,直到所有数据都断开连接。我是不是错过了什么
我不知道这种行为是否典型。在这种情况下,我在Windows 7 64位上使用PySerial进行实验,使用两个通过零调制解调器电缆连接的USB到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字节的消息:"你好!你好你好你好你好你好你好你好你好你好!"
我很困惑:发送者认为没有发送任何数据,而接收者却得到了所有数据。我一定错过了一些非常基本的东西。。。
欢迎任何意见/建议/问题!
由于它没有文档,让我们看看源代码。我只研究了POSIX和Win32的实现,但很明显,至少在这两个平台上:
- 当
write(data)
返回的写入字节可能少于数据长度、超时或其他情况时,没有的情况;它总是返回完整的len(data)
或引发异常 - 如果使用正
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跟踪器上添加一个功能请求(最好是带有补丁)。