Sending and Receiving Bitmaps per TcpClients - vb.net

So, I'm currently making a LAN-Streaming-Program, which should record the screen of the streamer and display it at the viewer's screen.
My problem is, that when I try to convert the received ByteArray-Image to an image, it gives me a System.ArgumentException.
Here's the code for sending:
Private Sub SendFrame(ByRef IP As String, ByRef Frame As Bitmap)
FrameClient = New TcpClient(IP, 7009)
Dim FrameStream As New MemoryStream
Dim FrameBytes() As Byte
Frame.Save(FrameStream, ImageFormat.Bmp)
FrameBytes = FrameStream.GetBuffer()
SendMessage(IP, "NextFrameSize;" & LocalIP & ";" & FrameBytes.Length)
Using NS As NetworkStream = FrameClient.GetStream
NS.Write(FrameBytes, 0, FrameBytes.Length)
NS.Close()
End Using
End Sub
And here for receiving:
Private Sub CheckForFrames() 'This Sub is called everytime the viewer receives the size of the next Bitmap.
If FrameListener.Pending = True Then
FrameClient = FrameListener.AcceptTcpClient
Dim ImageBytes(NextFrameSize) As Byte
FrameClient.GetStream.Read(ImageBytes, 0, NextFrameSize)
Dim MS As New MemoryStream(ImageBytes)
StreamBox.Image = Image.FromStream(MS, False)
MS.Close()
FlushMemory()
End If
End Sub
I would be thankful for any answers!

Dim ImageBytes(FrameClient.ReceiveBufferSize) As Byte
This returns the size of te buffer, not the actual size of the bitmap:
The size of the receive buffer, in bytes. The default value is 8192
bytes.
So, ImageBytes will probably be 8192 bytes, not the size of your bitmap.
You might want to send the size before you send the bitmap data, and on the receive side read in the size first to initialize your MemoryStream.

Related

Screen sharing through sockets

I'm making my first screen sharing application in VB.NET using sockets to establish the connections.
This is the client side receiving screen images from the server (they are both running in a thread):
Private Sub startscreen()
Using imgstream As NetworkStream = imgclient.GetStream()
Using ms As New MemoryStream
Dim read As Double
Do
If (imgstream.DataAvailable) Then
read = 0
ms.Seek(0, SeekOrigin.Begin)
While imgclient.Available
Dim buffer(imgclient.Available - 1) As Byte
imgstream.Read(buffer, 0, buffer.Length)
ms.Write(buffer, 0, buffer.Length)
read += buffer.Length
End While
Me.Text = "Frame bytes read: " & read
PictureBox1.Image = Image.FromStream(ms)
ms.Flush()
End If
Thread.Sleep(34) 'about 30 FPS
Loop
End Using
End Using
End Sub
And this is the server side:
Private Sub screen()
Using imgstream As NetworkStream = imgclient.GetStream()
Using ms As New MemoryStream
Do
Thread.Sleep(34) 'about 30 FPS
ms.Seek(0, SeekOrigin.Begin)
Using img = ScreenCap()
img.Save(ms, Imaging.ImageFormat.Jpeg)
End Using
ms.WriteTo(imgstream)
Loop
End Using
End Using
End Sub
Public Function ScreenCap() As Image
Dim screenSize As Size = New Size(My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height)
Dim screenGrab As New Bitmap(My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height) ', Imaging.PixelFormat.Format16bppRgb555)
Dim g As Graphics = Graphics.FromImage(screenGrab)
g.CopyFromScreen(New Point(0, 0), New Point(0, 0), screenSize)
g.Dispose()
Return screenGrab
End Function
The main problem is when I call the "Image.FromStream(ms)" function, it sometimes works and others doesn't depending on how many milliseconds I set the thread to wait. Tested on 2 different computers in my LAN, on around 1 second it seems OK but always with a high CPU-Network usage. If I set, as the example says around 34 milliseconds to get all more "LIVE", that function throw an exception because of the MemoryStream. How can I speed it up? Is there any smarter way I'm missing right now? I've also tried putting a delimiter byte (like a char = "*") at the and of the MemoryStream and then send it to the client who read one byte at a time until it found a char equal to the delimiter. But it turned out to be a bad solution because a single byte of the image could represent the delimiter if converted to char. Another question I have is: How can I change the image quality and the color depth? Is it a good approach using what the comment says: "Imaging.PixelFormat.Format16bppRgb555"
Thank you!

JPEG Compression causes GDI+ Exception

So, I'm currently working on a LAN-Video-Streaming program, which records single images and sends them over. Because it would be too much to send 30 1920x1080 pictures per second, to get 30FPS, I did some research and found JPEG-Compression. The problem is, that when I try to save the compressed JPEG, it throws an System.Runtime.InteropServices.ExternalException, with the additional information: General error in GDI+.
Here's my code:
Private Sub Stream() Handles StreamTimer.Tick
If Streaming = True Then
Try
ScreenCap = New Bitmap(Bounds.Width, Bounds.Height)
GFX = Graphics.FromImage(ScreenCap)
GFX.CopyFromScreen(0, 0, 0, 0, ScreenCap.Size)
Dim Frame As New Bitmap(ScreenCap, Resolution.Split(";")(0), Resolution.Split(";")(1))
Dim jpgEncoder As ImageCodecInfo = GetEncoder(ImageFormat.Jpeg)
Dim myEncoder As Encoder = Encoder.Quality
Dim myEncoderParameters As New EncoderParameters(1)
Dim myEncoderParameter As New EncoderParameter(myEncoder, Compression)
myEncoderParameters.Param(0) = myEncoderParameter
Frame.Save(My.Computer.FileSystem.SpecialDirectories.Temp & "\LSSFrame.jpg", jpgEncoder, myEncoderParameters) 'Error occurs in this line
Using FS As New FileStream(My.Computer.FileSystem.SpecialDirectories.Temp & "\LSSFrame.jpg", FileMode.Open)
Frame = Image.FromStream(FS)
FrameSizeStatus.Text = Math.Round(FS.Length / 1000) & "KB"
FS.Close()
End Using
PreviewBox.Image = Frame
FPSStat += 1
FlushMemory()
If ViewerIPs.Count > 0 Then
For i = 0 To ViewerIPs.Count - 1
SendFrame(ViewerIPs(i), Frame)
Next
End If
Catch ex As Exception
LostFrames += 1
End Try
End If
End Sub
Any help is appreciated!
In part, you are not disposing of the Graphics or Bitmap objects you create. The error message is not very helpful, but not disposing of those will leave resources unrecovered.
There is also a lot going on in that procedure. If it were broken into parts it might be easier to fine-tune for performance and such.
' form level objects
Private jEncParams As EncoderParameters
Private jpgEncoder As ImageCodecInfo
...
' inititalize somewhere when the process starts:
Dim quality As Int64 = 95
jpgEncoder = GetEncoder(ImageFormat.Jpeg)
Dim myjEnc As Imaging.Encoder = Imaging.Encoder.Quality
jEncParams = New EncoderParameters(1)
' quality is inverse to compression
jEncParams.Param(0) = New EncoderParameter(myjEnc, quality)
Since the encoder and quality elements are not going to change for each screen snap, create them once and resuse them. Then your timer event:
Dim scrBytes = GetScreenSnap(1280, 720)
' do something to send them....maybe queue them?
Console.WriteLine("image size: {0}k", (scrBytes.Length / 1024).ToString)
Optimizing SendFrame is outside the scope of this Q/A, but getting the screen shot is separate from sending.
Private Function GetScreenSnap(w As Int32, h As Int32) As Byte()
Using bmpScrn As New Bitmap(My.Computer.Screen.Bounds.Width, My.Computer.Screen.Bounds.Height)
Using g As Graphics = Graphics.FromImage(bmpScrn)
g.CopyFromScreen(0, 0, 0, 0, bmpScrn.Size)
End Using ' done with graphics
Using bmpThumb As New Bitmap(bmpScrn, w, h),
ms As New MemoryStream
bmpThumb.Save(ms, jpgEncoder, jEncParams)
Return ms.ToArray
End Using ' dispose of bmp
End Using ' dispose of bmpScrn
End Function
For no particular reason, I am thumbnailing the entire screen. Yours seems off using Bounds.Width, Bounds.Height since that would refer to the form. It would only work as a screen snapshot if the form is maximized. keypoints:
This is predicated on the idea that you can/will be sending the byte array in a stream. As such, I leave it as a byte array rather than creating a BMP only to (presumably) convert it back later.
There is no need to create a disk file to get the size. If the array contains encoded bytes, it will be the same size.
I've never used the COMPRESSION param, but I know Quality is inverse to compression.
Resulting sizes for various Quality factors:
100 = 462
95 = 254
90 = 195
80 = 147
Strictly speaking you do not need the encoder, bmpThumb.Save(ms, Imaging.ImageFormat.Jpeg) will also work but a single encoder object offers more fine tuning.
For the sending part, you might want a Stack, Queue or LinkedList to store the byte array. This would further isolate getting images from sending them. The collection would be a sort of ToDo/ToSend list.
Then, if there are multiple recipients, I'd look into perhaps doing SendFrame as a Task, perhaps sending 2-3 at a time. There might be a point where the number of recievers interferes with how fast you can grab new ones.

VB.NET Display progress of file decryption?

I'm using this code to encrypt/decrypt files:
Public Shared Sub encryptordecryptfile(ByVal strinputfile As String, _
ByVal stroutputfile As String, _
ByVal bytkey() As Byte, _
ByVal bytiv() As Byte, _
ByVal direction As CryptoAction)
Try
fsInput = New System.IO.FileStream(strinputfile, FileMode.Open, FileAccess.Read)
fsOutput = New System.IO.FileStream(stroutputfile, FileMode.OpenOrCreate, FileAccess.Write)
fsOutput.SetLength(0)
Dim bytbuffer(4096) As Byte
Dim lngbytesprocessed As Long = 0
Dim lngfilelength As Long = fsInput.Length
Dim intbytesincurrentblock As Integer
Dim cscryptostream As CryptoStream
Dim csprijndael As New System.Security.Cryptography.RijndaelManaged
Select Case direction
Case CryptoAction.ActionEncrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateEncryptor(bytkey, bytiv), _
CryptoStreamMode.Write)
Case CryptoAction.ActionDecrypt
cscryptostream = New CryptoStream(fsOutput, _
csprijndael.CreateDecryptor(bytkey, bytiv), _
CryptoStreamMode.Write)
End Select
While lngbytesprocessed < lngfilelength
intbytesincurrentblock = fsInput.Read(bytbuffer, 0, 4096)
cscryptostream.Write(bytbuffer, 0, intbytesincurrentblock)
lngbytesprocessed = lngbytesprocessed + CLng(intbytesincurrentblock)
End While
cscryptostream.Close()
fsInput.Close()
fsOutput.Close()
Catch ex As Exception
End Try
End Sub
Is I need to get the percentage of this process being done as an integer. I am going to use a background worker, so I need to call for this sub from the background worker and be able to keep refreshing a progress bar that the background worker reports to. Is this possible?
Thanks in advance.
There are a couple of things you can do to make your cryptor more efficient and other issues:
A method like encryptordecryptfile which then requires a "mode" argument to know which action to take means it really might be better off as 2 methods
The way you are going, you will be raising a blizzard of ProgressChanged events which the ProgressBar wont be able to keep up with given the animation. A 700K file will result in 170 or so progress reports of tiny amounts
Some of the crypto steps can be incorporated
You have a lot of things not being disposed of; you could run out of resources if you run a number of files thru it in a loop.
It might be worth noting that you can replace the entire While block with fsInput.CopyTo(cscryptostream) to process the file all at once. This doesnt allow progress reporting though. Its also not any faster.
Rather than a BackgroundWorker (which will work fine), you might want to implement it as a Task. The reason for this is that all those variables need to make their way from something like a button click to the DoWork event where your method is actually called. Rather than using global variables or a class to hold them, a Task works a bit more directly (but does involve one extra step when reporting progress). First, a revised EncryptFile method:
Private Sub EncryptFile(inFile As String,
outFile As String,
pass As String,
Optional reporter As ProgressReportDelegate = Nothing)
Const BLOCKSIZE = 4096
Dim percentDone As Integer = 0
Dim totalBytes As Int64 = 0
Dim buffSize As Int32
' Note A
Dim key = GetHashedBytes(pass)
Dim iv = GetRandomBytes(16)
Dim cryptor As ICryptoTransform
' Note B
Using fsIn As New FileStream(inFile, FileMode.Open, FileAccess.Read),
fsOut As New FileStream(outFile, FileMode.OpenOrCreate, FileAccess.Write)
fsOut.SetLength(0)
' Note C
'ToDo: work out optimal block size for Lg vs Sm files
If fsIn.Length > (2 * BLOCKSIZE) Then
' use buffer size to limit to 20 progress reports
buffSize = CInt(fsIn.Length \ 20)
' to multiple of 4096
buffSize = CInt(((buffSize + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE)
' optional, limit to some max size like 256k?
'buffSize = Math.Min(buffSize, BLOCK256K)
Else
buffSize = BLOCKSIZE
End If
Dim buffer(buffSize-1) As Byte
' Note D
' write the IV to "naked" fs
fsOut.Write(iv, 0, iv.Length)
Using rij = Rijndael.Create()
rij.Padding = PaddingMode.ISO10126
Try
cryptor = rij.CreateEncryptor(key, iv)
Using cs As New CryptoStream(fsOut, cryptor, CryptoStreamMode.Write)
Dim bytesRead As Int32
Do Until fsIn.Position = fsIn.Length
bytesRead = fsIn.Read(buffer, 0, buffSize)
cs.Write(buffer, 0, bytesRead)
If reporter IsNot Nothing Then
totalBytes += bytesRead
percentDone = CInt(Math.Floor((totalBytes / fsIn.Length) * 100))
reporter(percentDone)
End If
Loop
End Using
Catch crEx As CryptographicException
' ToDo: Set breakpoint and inspect message
Catch ex As Exception
' ToDo: Set breakpoint and inspect message
End Try
End Using
End Using
End Sub
Note A
One of the standard crypto tasks it could handle is creating the Key and IV arrays for you. These are pretty simple and could be shared/static members.
Public Shared Function GetHashedBytes(data As String) As Byte()
Dim hBytes As Byte()
' or SHA512Managed
Using hash As HashAlgorithm = New SHA256Managed()
' convert data to bytes:
Dim dBytes = Encoding.UTF8.GetBytes(data)
' hash the result:
hBytes = hash.ComputeHash(dBytes)
End Using
Return hBytes
End Function
Public Shared Function GetRandomBytes(size As Integer) As Byte()
Dim data(size - 1) As Byte
Using rng As New RNGCryptoServiceProvider
' fill the array
rng.GetBytes(data)
End Using
Return data
End Function
As will be seen later, you can store the IV in the encrypted file rather than saving and managing it in code.
Note B
Using blocks close and dispose of resources for you. Basically, if something has a Dispose method, then you should wrap it in a Using block.
Note C
You dont want to report progress for every block read, that will just overwhelm the ProgressBar. Rather than another variable to keep track of when the progress has changed by some amount, this code starts by creating a buffer size which is 5% of the input file size so there will be about 20 reports (every 5%).
As the comments indicate, you may want to add some code to set minimum/maximum buffer size. Doing so would change the progress report frequency.
Note D
You can write the IV() to the filestream before you wrap it in the CryptoStream (and of course read it back first when Decrypting). This prevents you from having to store the IV.
The last part is kicking this off as a Task:
Dim t As Task
t = Task.Run(Sub() EncryptFile(inFile, oFile, "MyWeakPassword",
AddressOf ReportProgress))
...
What a BGW does is execute the work on one thread, but progress is reported on the UI thread. As a Task, all we need to do is use Invoke:
Delegate Sub ProgressReportDelegate(value As Int32)
Private Sub ReportProgress(v As Int32)
If progBar.InvokeRequired Then
progBar.Invoke(Sub() progBar.Value = v)
Else
progBar.Value = v
progBar.Invalidate()
End If
End Sub
The Encryptor will work either directly or as a Task. For small files, you can omit the progress report entirely:
' small file, no progress report:
EncryptFile(ifile, oFile, "MyWeakPassword")
' report progress, but run on UI thread
EncryptFile(ifile, oFile, "MyWeakPassword",
AddressOf ReportProgress)
' run as task
Dim t As Task
t = Task.Run(Sub() EncryptFile(ifile, oFile, "MyWeakPassword",
AddressOf ReportProgress))
...and if you had a list of files to do, you could run them all at once and perhaps report total progress.

Issue sending big string over TCP

I have the following function to send and receive data it works OK for short strings like 150 bytes, but with a string of 2500 bytes it get stuck.
I tried to send some data with HTWin and nothing get the other end of the net.
So receiver on the other end never get even a character.
I tried also with the old Hyperterminal with same result.
Private Function SendReport(ByVal Msg As String, ByVal Host As String, ByVal Port As String) As String
Try
Dim Tx As New TcpClient()
Dim stream As NetworkStream = Nothing
Dim CloseStream As Boolean = False
Dim LingerMode As New LingerOption(True, 5)
' String to store the response ASCII representation.
Dim responseData As [String] = [String].Empty
Tx.NoDelay = True
Tx.LingerState = LingerMode
Tx.SendTimeout = 10000
Tx.ReceiveTimeout = 10000
Tx.Connect(Host, Port)
' Translate the passed message into ASCII and store it as a Byte array.
Dim data As [Byte]() = System.Text.Encoding.ASCII.GetBytes(Msg)
' Get a client stream for reading and writing.
' Stream stream = client.GetStream();
stream = Tx.GetStream()
CloseStream = True
'stream.WriteTimeout = 100
' Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length)
'Tx.Connected
' Receive the TcpServer.response.
' Buffer to store the response bytes.
data = New [Byte](256) {}
' Read the first batch of the TcpServer response bytes.
Dim bytes As Int32 = stream.Read(data, 0, data.Length)
responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes)
If CloseStream = True Then
stream.Close()
End If
Tx.Close()
Return responseData
Catch ex As Exception
WriteRTBLog(ex.Message, Color.Red)
WriteRTBLog(ex.StackTrace, Color.DarkRed)
Return "Error"
End Try
End Function
If I split the big string into smaller one and call this function over and over all the data reach the other end.
Also, I need to get an ACK packet that is big too and the code must wait for and special character that indicates the end.
I was thinking in to create a Thread with a TcpListener but, Is possible to have a TcpListener in the same port of a TcpClient?
The whole idea is to send a big string that is separated by an special character and finished with another special character.
The other end receive the data, split the string using the first special character and send one ACK by every item in the split function.
And in the last ACK send the second special character to indicate the end of the communication, so my application can process the received data properly.
I think I'm drowning in a glass of water.

BeginReceive trailing Null character/chr(0)

I'm coding an ascynchronous socket client for transferring files (following this Microsoft article) and notice that using BeginReceive corrupts the transfer because it adds a single Null character/chr(0) at the end of each packet. What could be causing this issue? I thought it might be the sending side, but I tested it with SendFile and had the same result.
In the Microsoft article it converts the bytes to an ASCII string and appends it to a StringBuilder. I want to save the bytes on-the-fly, so I barely modified the ReceiveCallback like so:
Private Shared Sub ReceiveCallback(ByVal ar As IAsyncResult)
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim client As Socket = state.workSocket
Dim bytesRead As Integer = client.EndReceive(ar)
If bytesRead > 0 Then
FileIO.FileSystem.WriteAllBytes(Application.StartupPath & "\test.exe", state.buffer, True)
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
Else
receiveDone.Set()
End If
End Sub
The problem is a misconception on how Receive, or BeginReceive & EndReceive work.
When you call Receive and give it a buffer and a size, you are specifying the maximum amount of data to receive. It is the bytesRead that tells you how much you actually received. You need to only write that number of bytes to your output file, as only that portion of your buffer was populated with data.
See here for more details:
http://msdn.microsoft.com/en-us/library/w3xtz6a5