我有一个USB设备,可以在Windows上创建虚拟串行端口。我正在使用VB.Net来写入和读取端口。我的设备响应特定大小的字节集,但我发现SerialPort.Read(字节数组,偏移量,数字字节)不返回完整的数字字节,但它也不会超时或生成异常。重复调用返回其他片段(最多需要3次调用)。我不明白为什么这个读取方法的行为方式呢?它认为所请求的字节数只是一个建议吗? :-)。我希望它会等待完成整个请求,除非它超时。
使用pySerial 的Python代码具有相同的问题。
那么,我在这里做错了什么?我期待太多了吗?
Private Sub SetupVirtualSerialPort()
Dim portName As String = "COM" + (m_DeviceContext * -1).ToString
Const baud As Int32 = 9600 '7680000
Const parity As Parity = parity.None
Const databits As Int32 = 8
Const stopbits As StopBits = stopbits.One
m_SerialPort = New SerialPort(portName, baud, parity, databits, stopbits)
m_SerialPort.WriteTimeout = VSPtimeout
m_SerialPort.ReadTimeout = VSPtimeout
m_SerialPort.ReadBufferSize = 2 * RETURN_BUFFER_SIZE
m_SerialPort.WriteBufferSize = 2 * COMMAND_BUFFER_SIZE
m_SerialPort.Open()
' Register event handlers
AddHandler m_SerialPort.ErrorReceived, AddressOf m_DriverInterface.Handle_VSP_Error
End Sub
Public Function WriteReadVSPort(ByVal commandLength As Int32, ByVal returnLength As Int32) As Int32
Const RetryLimit As Int32 = 5
Dim NumberRetries As Int32 = 0
Dim Offset As Int32 = 0
Dim ExceptionOccurred As Boolean = False
Dim NumberBytes As Int32 = 0
Try ' Writing
m_SerialPort.Write(m_CommandBuffer, 0, commandLength)
Catch exc As InvalidOperationException
MessageBox.Show("InvalidOperationException", Application.ProductName)
ExceptionOccurred = True
Catch exc As TimeoutException
MessageBox.Show("TimeoutException", Application.ProductName)
ExceptionOccurred = True
End Try
If Not ExceptionOccurred Then
Try ' Reading
' Working around a problem here: reads are returning fewer
' bytes than requested, though not signalling a timeout exception.
' Therefore, we retry if we got fewer bytes than expected, up to five times.
While NumberRetries < RetryLimit And returnLength > Offset
NumberBytes = m_SerialPort.Read(m_ReturnBytes, Offset, returnLength - Offset)
Offset += NumberBytes
NumberRetries += 1
If returnLength <> NumberBytes Then
System.Diagnostics.Debug.Print("Number of bytes read (" & NumberBytes &
") not what was requested (" & returnLength & "). Accumulated " & Offset)
End If
End While
Catch exc As InvalidOperationException
MessageBox.Show("InvalidOperationException", Application.ProductName)
ExceptionOccurred = True
Catch exc As TimeoutException
MessageBox.Show("TimeoutException", Application.ProductName)
ExceptionOccurred = True
End Try
If ExceptionOccurred Then
Return 1
Else
Return 0
End If
End Function
谢谢。
答案 0 :(得分:3)
这对于处理IO(包括流和端口)来说是完全正常的。基本上,您需要检查返回的值并循环。例如:
int offset = 0, read, remaining = ...;
while(remaining > 0 &&
(read = source.Read(buffer, offset, remaining) > 0)
{
offset += read;
remaining -= read;
}
if(remaining > 0) throw new EndOfStreamException();
如果您的消息不是固定长度,则可能需要添加长度前缀(在每个消息之前)或消息分隔符(在每个之后)。
答案 1 :(得分:1)
我收集了使用DataReceived事件并使代码事件驱动而不是循环的建议。我发现在一次读操作中,虚拟串行端口仍然不能用于我的21120字节消息。更短的消息长度正确完成。但是,当我将串行端口数据接收阈值设置为21119字节并将串行端口读取缓冲区设置为我的消息大小的10倍时,我发现 1.只有12672个字节可用(不是21119)触发DataReceived事件,并且对完整大小执行Read()时返回相同的数字。 2.如果字节数不等于我的阈值,如果我当时没有读取,则不会触发进一步的DataReceived事件 但是,如果(并且仅当)我读取12672个字节,则另一个DataReceived事件会伴随剩余的8448个字节。
我无法理解为什么这样做。欢迎提出进一步的意见。
但是,我认为我会为了别人的利益而分享我目前的代码。
有些类变量是:
Private m_SerialPort As SerialPort = Nothing
Private Debug As Int16
Private m_CommandBuffer(COMMAND_BUFFER_SIZE) As Byte
Private m_ReturnBytes(RETURN_BUFFER_SIZE) As Byte
Private m_WaitingOnBytes As Int32
Private m_VSP_Offset As Int32 = 0
Private m_waitHandle As New System.Threading.ManualResetEvent(True) ' Initialize to signaled
Private m_waitHandle2 As New System.Threading.ManualResetEvent(False) ' Initialize to UN-signaled
事件处理程序子例程
Public Sub Handle_VSP_DataReceived_Dev(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim NumberBytes As Int32
If m_SerialPort.BytesToRead > 0 And m_SerialPort.BytesToRead >= m_WaitingOnBytes Then
' This handles the case where the event was triggered, there was data and its length matched
' or exceeded the requested amount.
System.Diagnostics.Debug.Print("DR-Dev: Bytes to read: " & m_SerialPort.BytesToRead & ", waiting for: " & m_WaitingOnBytes)
NumberBytes = m_SerialPort.Read(m_ReturnBytes, m_VSP_Offset, m_WaitingOnBytes)
System.Diagnostics.Debug.Print("DR-Dev: got " & NumberBytes & " bytes, released wait handle")
m_WaitingOnBytes = 0
m_waitHandle.Set() ' Release the wait handle so the thread running WriteReadVSPort can proceed
ElseIf m_SerialPort.BytesToRead > 0 And m_WaitingOnBytes > 0 Then
' Handle the case where the full request is not delivered. Note:
' This should not need to be done, but it seems that the
' Serial Port is sending the event before all the data is
' received and the threshold is crossed and then not
' sending another event until the buffer is read.
' So, here we do a partial read, if we are waiting on a
' read operation and adjust the offset and remaining bytes
' we are waiting for.
System.Diagnostics.Debug.Print("DR-Dev: Bytes to read: " & m_SerialPort.BytesToRead & ", waiting for: " & m_WaitingOnBytes)
NumberBytes = m_SerialPort.Read(m_ReturnBytes, m_VSP_Offset, m_WaitingOnBytes)
If NumberBytes = m_WaitingOnBytes Then
' We actually got all the data, though the serial port did not report it had it ready. Fine,
' proceed as above
System.Diagnostics.Debug.Print("DR-Dev: got " & m_WaitingOnBytes & " bytes, released wait handle")
m_WaitingOnBytes = 0
m_waitHandle.Set() ' Release the wait handle so the thread running WriteReadVSPort can proceed
Else ' Mark this as a partial read
System.Diagnostics.Debug.Print("DR-Dev: got partial " & NumberBytes & " while waiting for: " &
m_WaitingOnBytes & " bytes, continue to hold WriteReadVSPort")
m_WaitingOnBytes -= NumberBytes
m_VSP_Offset += NumberBytes
End If
End If
End Sub
执行写入命令/读取响应活动的功能
Public Function WriteReadVSPort(ByVal commandLength As Int32, ByVal returnLength As Int32) As Int32
Dim ExceptionOccurred As Boolean = False
Dim NumberBytes As Int32 = 0
Dim RetriesRemaining As Int32 = 4
Dim Finished As Boolean = False
' Important to set up for reading response before the command is written
' because another thread will handle the DataReceived event and process
' the received data without intervention from the thread executing
' this(subroutine.
m_VSP_Offset = 0
m_WaitingOnBytes = returnLength
' Set the DataReceived event threshold
m_SerialPort.ReceivedBytesThreshold = m_WaitingOnBytes - 1
' Set waitHandle so it will block the thread executing this routine until the data is received
m_waitHandle.Reset()
Try ' Writing
m_SerialPort.Write(m_CommandBuffer, 0, commandLength)
Catch exc As InvalidOperationException
MessageBox.Show("InvalidOperationException when writing to Serial Port COM" & -1 * DeviceContext, Application.ProductName)
ExceptionOccurred = True
Catch exc As TimeoutException
MessageBox.Show("TimeoutException when writing to Serial Port COM" & -1 * DeviceContext, Application.ProductName)
ExceptionOccurred = True
End Try
If Not ExceptionOccurred Then
Try ' Reading all done by Event Handler
' wait for event handler to complete its job, running in another thread
System.Diagnostics.Debug.Print("WR_VSP: waiting on waitHandle, bytes avail: " &
m_SerialPort.BytesToRead & ", want bytes: " & m_WaitingOnBytes)
If m_waitHandle.WaitOne(VSPtimeout) Then
' The WaitOne call returned True, meaning that Handle_VSP_DataReceived_Dev was able to receive all the requested data
System.Diagnostics.Debug.Print("WR_VSP: proceeding")
Else
' The WaitOne call timed out. Give it some retries before throwing an exception
While Not Finished And RetriesRemaining > 0
System.Threading.Thread.Sleep(VSPtimeout)
If m_SerialPort.BytesToRead > 0 And m_SerialPort.BytesToRead >= m_WaitingOnBytes Then
NumberBytes = m_SerialPort.Read(m_ReturnBytes, m_VSP_Offset, m_WaitingOnBytes)
System.Diagnostics.Debug.Print("WR_VSP: timeout mode, got " & m_WaitingOnBytes & " bytes")
Finished = True
Else
RetriesRemaining -= 1
End If
End While
If Not Finished Then
Throw New TimeoutException("Device failed to send the requested number of bytes.")
End If
End If
Catch exc As InvalidOperationException
MessageBox.Show("InvalidOperationException when reading from Serial Port COM" & -1 * DeviceContext, Application.ProductName)
ExceptionOccurred = True
Catch exc As TimeoutException
MessageBox.Show("TimeoutException when reading from Serial Port COM" & -1 * DeviceContext, Application.ProductName)
ExceptionOccurred = True
End Try
End If
If ExceptionOccurred Then
Return 1
Else
Return 0
End If
End Function