I am having trouble with a socket client application written in vb.net using Visual Studio 2005. The client connects to a C language socket server that is running on OpenVMS. The problem that I have is that when the server sends a packet, the client does not receive the last byte (of every packet!). I can dump the packets on the network and the data is all there. My current solution is to keep my socket messages short (247 bytes) and send one extra byte past the end of my data.
I would like to include more information in my messages and I cannot find a way to make this work. If I knew 100% how long the packets will be on the network, I could work around this by including an extra byte in just the right places. However, I don't want to make any assumptions about the length of the packets.
Does anyone have any suggestions about what the best solution is to this problem?
Here is a sample of my client receive code:
Private Sub ReceiveMsg()
Dim nTotalBytes As Integer
Dim nNumBytes As Integer
Dim nMsgType As Short
Dim nMsgLen As Short
Dim ind As Integer
Try
nNumBytes = -1
While (nNumBytes <> 0)
nTotalBytes = 0
RecvBuffer.Initialize()
nNumBytes = ClientSocket.Receive(RecvBuffer, nTotalBytes, 4, SocketFlags.None)
If nNumBytes > 3 Then
SyncLock ClientSocket
AppendText("")
AppendText("Message Received " & Str(nNumBytes) & " Bytes")
nTotalBytes = nTotalBytes + nNumBytes
nMsgType = BitConverter.ToInt16(RecvBuffer, 0)
nMsgLen = BitConverter.ToInt16(RecvBuffer, 2)
If nMsgLen > 8191 Then
AppendText(" Error - Message length invalid: " & Str(nMsgLen))
nMsgLen = 250
End If
While (nTotalBytes < nMsgLen And nNumBytes > 0)
nNumBytes = ClientSocket.Receive(RecvBuffer, nTotalBytes, (nMsgLen - nTotalBytes), _
SocketFlags.None)
AppendText("Message Received " & Str(nNumBytes) & " Bytes")
nTotalBytes = nTotalBytes + nNumBytes
End While
End SyncLock
AppendText("Total Bytes Received = " & Str(nTotalBytes))
AppendText("MsgLen from Message = " & Str(nMsgLen))
Select Case nMsgType
Case 1
AppendText(" Liftpos = " & System.Text.Encoding.ASCII.GetString(RecvBuffer, 4, 1))
For ind = 0 To NUM_LOCATIONS - 1
Number(ind) = System.Text.Encoding.ASCII.GetString(RecvBuffer, 5 + (ind * 12), 12)
Next
RefreshScreen()
Case Else
AppendText(" Unrecognized message type: " & Str(nMsgType))
End Select
End If
End While
Catch ex As Exception
' Tell the main thread to invoke DisconnectedUI
Dim cb As New SimpleCallback(AddressOf DisconnectedUI)
Me.Invoke(cb)
Return
End Try
This problem has been corrected. The TCP/IP server was using a WRITE function modifier that ended up setting the URG bit in the TCP/IP packets. I believe that the server mangled this and different Windows clients had a variety of problems handling these packets.
Actually, sending an extra byte does NOT fix the problem. As long as I keep my socket message under about 247 bytes, it all gets send in a single packet. Therefore sending an extra byte just makes sure that all of the data is received by the client.
If I send a longer message, then depending on the lenght of the message, one byte is lost at the end of each packet. This means that if I send a longer message, I would have to adjust for this by either making the assumption that it will always be the same or by including a tag of some kind to help me find the offset of the data in the message.
I thought that there should be some better solution.
Related
I am writing a program which needs to read MIDI files. The program reads each series of bytes for each track into a byte array, which is passed into a contructor for a "Track" object. The track object then reads the array and interprets MIDI events from the data.
I became aware of a problem after intense inspection of the program. It seems that sometimes, the byte signalling a MIDI meta-event: 0xFF, is being read as part of the delta time, leading the program to believe it is reading a channel-voice event which then causes errors when interpreting the following event data.
I believe that the trouble is being caused by my function that reads Variable Length Quantities including delta time. I have tried using the function given by the MIDI specification on page 11/12 (converted from C to VB.net with my very limited C knowledge):
Private Function getVariableLengthValue(ByRef currentByte As Integer) As Double
val = data(currentByte)
currentByte += 1
If val & &H80 Then
val = val & &H7F
Do
val = (val << 7) + (data(currentByte) & &H7F)
currentByte += 1
Loop While data(currentByte) & &H80
End If
getVariableLengthValue = val
End Function
as well as my own ugly function developed by reading the Wikipedia page on VLQs
Private Function getVariableLengthValue(ByRef currentByte As Integer) As Double
Dim val As Double = 0
Dim varLengthBytes As New List(Of Byte)
While getMostSigBit(data(currentByte)) = 1
varLengthBytes.Add(data(currentByte))
currentByte += 1
End While
Dim finalByte As Byte = data(currentByte)
varLengthBytes.Add(finalByte)
currentByte += 1
Dim varLengthString As String = ""
If varLengthBytes.Count > 1 Then
For x As Integer = 0 To varLengthBytes.Count - 2
Dim byteString As String = Convert.ToString(varLengthBytes(x), 2)
byteString = byteString.Remove(0, 1)
While byteString.Length < 7
byteString = byteString.Insert(0, "0")
End While
varLengthString = varLengthString + byteString
Next
End If
Dim finalByteString As String = Convert.ToString(varLengthBytes(varLengthBytes.Count - 1), 2)
finalByteString = finalByteString.Remove(0, 1)
While finalByteString.Length < 7
finalByteString = finalByteString.Insert(0, "0")
End While
varLengthString = varLengthString + finalByteString
val = Convert.ToInt64(varLengthString, 2)
getVariableLengthValue = val
End Function
however both seem to give the same issue: reading too far into the data, overspilling the delta time and reading in the event type, knocking the rest of the code out of step with the data being read.
If anybody could help highlight the flaw with my C conversion, I would greatly appreciate it.
I am somewhat new to coding and very new to Stack Overflow, so sorry for any unnecessary or lacking detail. I'l do my best to provide further information.
Many thanks, Roy H
I am working on a VB.net application where I have a very large text file. It is basically a large database of error codes with descriptions of how to clear the code after it. What I would like to do, is on the click of a button, search the text file for the specific code and display all text for just that error code into a text box. I have tried many different ways, but am unable to get it to work properly. I went through the entire text file and added a "|" to the beginning of each fault code so that I could specify where the code starts at.
Here is an example of a couple fault codes:
|ACAL-000 Fail to run DETECT Motn Cause: The AccuCal2 Motion failed to
nm. The AccuCal2 motion cannot be started. Remedy: Clear all the
errors before executing AccuCal2. |ACAL-001 Robot is not ready.
Cause: The robot is not ready. The system cannot issue motion
because it is in an error state. Remedy: Clear all faults, then retry
the operation.
If I search for "ACAL-000", I want it to show everything from the | before ACAL-000 to the bar before ACAL-001.
I would post the code that I have written, but I have tried so many different versions that I don't really know which one to post.
Any help you can provide would be greatly appreciated.
EDIT
Here is my current code after some editing and implementation of what has been recommended. Please see the comments below for more information on how I got to this point. A quick note, I am currently just using "|ACAL-000" for a test search. When this is complete, I have some other (already working) code that will put together a code from a couple of drop down lists.
Function ReadEmbeddedTextFileResource(embeddedResourceName As String) As String
Using stream As Stream = Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedResourceName)
If stream Is Nothing Then
Throw New FileNotFoundException("The resource '" & embeddedResourceName & "' was not found.")
End If
Using reader As StreamReader = New StreamReader(stream, True)
Return reader.ReadToEnd()
End Using
End Using
End Function
Function FindTextBetweenBars(bodyOfTextToSearch As String, textToLookFor As String) As String
Dim i As Integer = bodyOfTextToSearch.IndexOf(textToLookFor)
If i < 0 Then Return Nothing
Dim j As Integer = bodyOfTextToSearch.LastIndexOf("|", i)
If j < 0 Then j = 0
Dim k As Integer = bodyOfTextToSearch.IndexOf("|", i + Len(textToLookFor))
If k < 0 Then k = Len(bodyOfTextToSearch)
Return bodyOfTextToSearch.Substring(j + 1, k - j - 1)
End Function
Private Sub btnShowTroubleshooting_Click(sender As Object, e As EventArgs) Handles btnShowTroubleshooting.Click
Dim allErrorText As String = ReadEmbeddedTextFileResource(My.Resources.FanucCodes)
Dim errorMessage As String = FindTextBetweenBars(allErrorText, "|ACAL-000")
If errorMessage Is Nothing Then errorMessage = "Error code Not found!"
RichTextBoxFanucFaults.Text = errorMessage
End Sub
Here is a function that should do what you want:
Function FindTextBetweenBars(bodyOfTextToSearch As String, textToLookFor As String) As String
Dim i As Integer = bodyOfTextToSearch.IndexOf(textToLookFor)
If i < 0 Then Return Nothing
Dim j As Integer = bodyOfTextToSearch.LastIndexOf("|", i)
Dim k As Integer = bodyOfTextToSearch.IndexOf("|", i + Len(textToLookFor))
If k < 0 Then k = Len(bodyOfTextToSearch)
Return bodyOfTextToSearch.Substring(j + 1, k - j - 1)
End Function
In your button click event handler you can call the function like this:
Dim errorMessage as String = FindTextBetweenBars(My.Resources.FanucCodes, txtErrorCodeToLookFor.Text)
If errorMessage Is Nothing Then errorMessage = "Error code not found!"
txtErrorMessage.Text = errorMessage
where txtErrorMessage is the output textbox to display the error message result,
My.Resources.FanucCodes is your large string resource containing all the error descriptions (with | separators), and txtErrorCodeToLookFor is a textbox that accepts the error code input from the user.
Okay, so I'm working in VB.NET, manually writing error logs to log files (yes, I know, I didn't make the call). Now, if the files are over an arbitrary size, when the function goes to write out the new error data, it should start a new file with a new file name.
Here's the function:
Dim listener As New Logging.FileLogTraceListener
listener.CustomLocation = System.Configuration.ConfigurationManager.AppSettings("LogDir")
Dim loc As String = DateTime.UtcNow.Year.ToString + DateTime.UtcNow.Month.ToString + DateTime.UtcNow.Day.ToString + DateTime.UtcNow.Hour.ToString + DateTime.UtcNow.Minute.ToString
listener.BaseFileName = loc
Dim logFolder As String
Dim source As String
logFolder = ConfigurationManager.AppSettings("LogDir")
If ex.Data.Item("Source") Is Nothing Then
source = ex.Source
Else
source = ex.Data.Item("Source").ToString
End If
Dim errorFileInfo As New FileInfo(listener.FullLogFileName)
Dim errorLengthInBytes As Long = errorFileInfo.Length
If (errorLengthInBytes > CType(System.Configuration.ConfigurationManager.AppSettings("maxFileSizeInBytes"), Long)) Then
listener.BaseFileName = listener.BaseFileName + "1"
End If
Dim msg As New System.Text.StringBuilder
If String.IsNullOrEmpty(logFolder) Then logFolder = ConfigurationManager.AppSettings("LogDir")
msg.Append(vbCrLf & "Exception" & vbCrLf)
msg.Append(vbTab & String.Concat("App: AppMonitor | Time: ", Date.Now.ToString) & vbCrLf)
msg.Append(vbTab & String.Concat("Source: ", source, " | Message: ", ex.Message) & vbCrLf)
msg.Append(vbTab & "Stack: " & ex.StackTrace & vbCrLf)
listener.Write(msg.ToString())
listener.Flush()
listener.Close()
I have this executing in a loop for testing purposes, so I can see what happens when it gets (say) 10000 errors in all at once. Again, I know there are better ways to handle this systemically, but this was the code I was told to implement.
How can I reliably get the size of the log file before writing to it, as I try to do above?
Well, as with many things, the answer to this turned out to be "did you read your own code closely" with a side order of "eat something, you need to fix your blood sugar."
On review, I saw that I was always checking BaseFileName and, if it was over the arbitrary limit, appending a character and writing to that file. What I didn't do was check to see if that file or, indeed, other more recent files existed. I've solved the issue be grabbing a directory list of all the files matching the "BaseFileName*" argument in Directory.GetFiles and selecting the most recently accessed one. That ensures that the logger will always select the more current file to write to or -if necessary- use as the base-name for another appended character.
Here's that code:
Dim directoryFiles() As String = Directory.GetFiles(listener.Location.ToString(), listener.BaseFileName + "*")
Dim targetFile As String = directoryFiles(0)
For j As Integer = 1 To directoryFiles.Count - 1 Step 1
Dim targetFileInfo As New FileInfo(targetFile)
Dim compareInfo As New FileInfo(directoryFiles(j))
If (targetFileInfo.LastAccessTimeUtc < compareInfo.LastAccessTimeUtc) Then
targetFile = directoryFiles(j)
End If
Next
Dim errorFileInfo As New FileInfo(listener.Location.ToString() + targetFile)
Dim errorLengthInBytes As Long = errorFileInfo.Length
I have a small routine that send some strings trough TCP socket to a remote server from another company and the remote server is not under my control.
My company and the remote company are sharing some data.
They say that they are not receiving all the data but losing some ones, about 15% of the packets are being loosed.
I'm trying to figure out what's happening here.
The packets are relatively small about 70 bytes each.
I'm sending about 300 to 900 packets in the same second every 2 minutes.
I make a sniff with Microsoft Network Monitor 3 and see that some packets are being joined in one big packet of different sizes and some packets are incomplete when that happen.
Plus the checksum, on the sniffer, are marked as incorrect.
This is my code, So I miss something? It is possible to send one small packet(70bytes) by every TCP packet?
Try
If Not IsNumeric(Port) Then
Throw New ArgumentException("Invalid port.(" & Port & ")")
End If
Dim Msgs() As String
Dim Tx As New TcpClient()
Dim stream As NetworkStream = Nothing
Dim CloseStream As Boolean = False
Dim LingerMode As New LingerOption(True, 5)
Tx.NoDelay = True
Tx.LingerState = LingerMode
Tx.SendTimeout = 5000
Tx.Connect(Host, Port)
Msgs = Msg.Split(Chr(10))
For x As Integer = 0 To Msgs.Length - 1
If Msgs(x).Length = 58 Then
' Translate the passed message into ASCII and store it as a Byte array.
Dim data As [Byte]() = System.Text.Encoding.ASCII.GetBytes(Msgs(x))
' Get a client stream for reading and writing.
' Stream stream = client.GetStream();
stream = Tx.GetStream()
CloseStream = True
' Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length)
Else
If Not Msgs(x).Length = 0 Then
WriteRTBLog("Error - Len - " & Msgs(x), Color.Red)
End If
Continue For
End If
Next
If CloseStream = True Then
stream.Close()
End If
Tx.Close()
Return "OK" 'responseData
Catch ex As Exception
Return "Error: " & ex.Message
End Try
Thanks for reading.
More info:
I get a log from the remote application (TCP dump) and some packets are incomplete, about 16bytes instead of 70.
I'm trying to fix two warnings. The first warning is an implicit conversion from long to string on the oInfo.Length. The second is Implicit conversion from string to double on 'strSize'. This is an old .NET 1 project I'm try to convert to 4.0. How do I fix these warnings while keeping the logic?
Dim oInfo As New System.IO.FileInfo(Server.MapPath(strVirtualPath))
Dim strSize As String = ""
Try
If oInfo.Exists Then
strSize = **oInfo.Length**
If **strSize** < 1048576 Then
strSize = System.Math.Round(Convert.ToInt64(strSize) / 1024, 2) & " kb"
Else
strSize = System.Math.Round(Convert.ToInt64(strSize) / 1048576, 2) & " mb"
End If
End If
Catch ex As Exception
oInfo.Length refers to System.IO.FileInfo.Length, which is a long.
So, you can't play strSize one time as Long, other as String.
In fact, you even don't need to store oInfo.Length into another variable. It's populated in object creation (when FileInfo retrieves information from file). Doing that, you don't need to Convert the value to Long.
I would rewrite this code as:
Dim oInfo As New System.IO.FileInfo(Server.MapPath(strVirtualPath))
Dim strSize As String = ""
Try
If oInfo.Exists Then
If oInfo.Length < 1048576 Then
strSize = System.Math.Round(oInfo.Length / 1024, 2) & " KB"
Else
strSize = System.Math.Round(oInfo.Length / 1024 / 1024, 2) & " MB"
End If
End If
Catch ex As Exception
Something like the following. Note that if it's a number it should be stored in a datatype designed for numbers. The only time a number should be stored in a string is for display purposes (eg comma separation, units, etc)
Dim FInfo As New System.IO.FileInfo(Server.MapPath(strVirtualPath))
Dim Result As String
Try
If oInfo.Exists Then
Dim FileSize = FInfo.Length
If FileSize < 1048576 Then
Result = System.Math.Round(FileSize / 1024, 2) & " kb"
Else
Result = System.Math.Round(FileSize / 1048576, 2) & " mb"
End If
End If
Catch ex As Exception
....
Note that this is still inadequate (unless you know you'll never get a file smaller than 1KB or larger than 1GB). A much better solution can be found here
Edit: In response to André Figueiredo... FileInfo.Length is defined as:
Public ReadOnly Property Length() As Long
<SecuritySafeCritical()>
Get
If Me._dataInitialised = -1 Then
MyBase.Refresh()
End If
If Me._dataInitialised <> 0 Then
__Error.WinIOError(Me._dataInitialised, MyBase.DisplayPath)
End If
If(Me._data.fileAttributes And 16) <> 0 Then
__Error.WinIOError(2, MyBase.DisplayPath)
End If
Return CLng(Me._data.fileSizeHigh) << 32 Or (CLng(Me._data.fileSizeLow) And CLng((CULng(-1))))
End Get
End Property
The _dataInitialised seems to be set by the (native) base class so I can't see when it's set. One would hope it's on construction but that doesn't seem to be the case as it's checked in a number of places.
Of course, it's all moot since the OP seems to be using Option Strict = False which adds ~30% performance penalty due to all the type checking.
Start here:
Dim strSize As Double