Please help, Sockets / Threading

G

Guest

Hello Everyone

I am making an instant messenger program. I have used the MSDN sockets example to get started with this. I have transfered the code that is found within this project into a class and can access it fine

The problem is, that the messenger sends a message to the server which relays the message to the correct user: great, but there is a thread running that is reading the incomming data and passes it to a process sub that checks to see what the server has sent

Now i am trying to get a new instance of a form to load up when a message is received but when it runs the form loads up but just lags and doesnt load properly, its like its trying to do the same thing over and over again

Its weird because data gets sent to the server - > server sends to the client - > client opens a new window ( this lags ), but there is only one lot of data and it just keeps lagging like it is doing it over and over and over. How can i get this to work

If you need some code examples, just get me on my msn which is (e-mail address removed) ( THIS IS NOT MY EMAIL JUST MSN )

Thanks to anyone that can help.
 
S

Sven Groot

Matt said:
Hello Everyone,

I am making an instant messenger program. I have used the MSDN
sockets example to get started with this. I have transfered the code
that is found within this project into a class and can access it
fine.

The problem is, that the messenger sends a message to the server
which relays the message to the correct user: great, but there is a
thread running that is reading the incomming data and passes it to a
process sub that checks to see what the server has sent.

Now i am trying to get a new instance of a form to load up when a
message is received but when it runs the form loads up but just lags
and doesnt load properly, its like its trying to do the same thing
over and over again.

It sounds like you are trying to create the new form in the context of the
thread that handles the incoming data. This thread will typically block
until new data comes in, preventing your form, which is running in that
thread, from executing it's code.

What you want to do is create all your UI in the same threads. Create a
function that handles the 'create new form' bit. Create a delegate for this
function's signature, or have the signature match either the EventHandler or
MethodInvoker delegate, that's fastest. Now use the Invoke method on your
main form to run this function in your UI thread's context.

That should take care of it, unless I misunderstand your intentions.
 
G

Guest

Hi Matt

I have also worked on similar project. I think you need to change your arch.... I will online tom... In my office msn is blocked. Tom... I will chat with you from my home. My MSN ID is (e-mail address removed).

Sadha Sivam
Malleable Minds Software Pvt Ltd
 
M

Matthew Lanham

Thats exactly what is happening Sven Groot, i am a new vb.net developer
what a change from vb6 hehe.

Anychance you could give me a quick example of what i have to do.

Here is where the code is happening:

THIS PART IS DONE ON THE FORM LOAD
----------------------------------------
Public Sub tryConnect()
Try
' The TcpClient is a subclass of Socket, providing higher
level
' functionality like streaming.
client = New TcpClient(strServer, PORT_NUM)

' Start an asynchronous read invoking DoRead to avoid
lagging the user
' interface.
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE,
AddressOf DoRead, Nothing)

Catch Ex As Exception
End
End Try
If strUsername > "" Then
'When the user first enters the program
'The connect command is sent to the server
SendData("CNT" & "|" & strUsername & "|" & strNickName)
End If
End Sub
-----------------------------------------
THIS PART IS NEXT
-----------------------------------------
Public Sub DoRead(ByVal ar As IAsyncResult)
Dim BytesRead As Integer
Dim strMessage As String

Try
' Finish asynchronous read into readBuffer and return number
of bytes read.
BytesRead = client.GetStream.EndRead(ar)
If BytesRead < 1 Then
' If no bytes were read server has close. Disable input
window.
Exit Sub
End If
' Convert the byte array the message was saved into, minus
two for the
'Chr(13) and Chr(10)
strMessage = Encoding.ASCII.GetString(readBuffer, 0,
BytesRead - 2)

-----------------------------------------
THIS PART OF THIS SUB IS WHERE IT CHECKS THE COMMANDS, PASSES TO ANOTHER
SUB
-----------------------------------------
ProcessCommands(strMessage)

' Start a new asynchronous read into readBuffer.
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE,
AddressOf DoRead, Nothing)
Catch e As Exception
MsgBox(e.Message)
End Try

End Sub
-----------------------------------------
THE FOLLOWING SUB PROCESSES THE COMMANDS
-----------------------------------------
Public Sub ProcessCommands(ByVal strMessage As String)
'TMSG|1234567890|Matt|1234567890|Message
Dim dataArray() As String
Dim lstR As ListViewItem
dataArray = Split(strMessage, Chr(124))
Select Case dataArray(0)
Case "RNICK"
'lstR = lstContacts.Items.Add(dataArray(2))
'lstR.SubItems.Add(dataArray(1))
-----------------------------------------
THIS IS THE PROBLEM AREA, IDEALLY IT WOULD BE GOOD TO HAVE THIS WHOLE
SUB RUNNING OUT OF THE THREAD SO I CAN EXECUTE CODE HERE
-----------------------------------------
Case "TMSG"
'open message window
'display received message
'Open a new window, or open window with tabs
If F4 Is Nothing Then
F4 = New frmMessage
F4.Show()
End If
Case "ERROR"
MsgBox(dataArray(1), MsgBoxStyle.Exclamation, "An Error
Has Occured")
End Select
End Sub
-----------------------------------------

I would apprechiate any further help you can give me.

Kind Regards

Matt
 
M

Matthew Lanham

I aint done much with delegates but i know its something to do like:

Dim nForm As NewForm
-------------------------------------------
I dont really know where to put this or if im even doing it right, can
you help.
-------------------------------------------
nForm = New NewForm(AddressOf createNewForm)
nForm.Invoke()
-------------------------------------------

Public Sub createNewForm()
If F4 Is Nothing Then
F4 = New frmMessage
F4.Show()
End If
End Sub
 
S

Sven Groot

Matthew said:
Thats exactly what is happening Sven Groot, i am a new vb.net
developer what a change from vb6 hehe.

First of all, the class that holds the code you show below needs a reference
to the active instance of some other part of the UI. In VB6, this reference
will be globally available under the name of the form, in VB.NET it's not.
You need to explicitly put a variable in the class (or a global variable,
but if a member is possible use that, it's better from a design point of
view) with the ProcessCommands sub and set it to the instance of your main
form. Suppose we call that variable MainForm.

Now you create a function somewhere (most likely in the class with
ProcessMessage) that reads as follows:
Public Sub CreateNewForm()
If F4 Is Nothing Then
F4 = New frmMessage
F4.Show()
End If
End Sub

Because this sub has no parameters, you don't need to create your own
delegate type, it already matches the type of the MethodInvoker delegate.

Now in the ProcessMessage sub, where you'd normally execute the code from
above, you put:
MainForm.Invoke(New MethodInvoker(AddressOf CreateNewForm))

This will cause the CreateNewForm method to run in the thread context of the
form MainForm points to, which is what you want.
 
M

Matthew Lanham

Can you explain this part please, if you could edit the code i gave
showing me where to make the changes that would be brilliant.

I am trying to do this honestly.
------------------------------------------------------
First of all, the class that holds the code you show below needs a
reference to the active instance of some other part of the UI. In VB6,
this reference will be globally available under the name of the form, in
VB.NET it's not. You need to explicitly put a variable in the class (or
a global variable, but if a member is possible use that, it's better
from a design point of view) with the ProcessCommands sub and set it to
the instance of your main form. Suppose we call that variable MainForm.
 
S

Sven Groot

Matthew said:
Can you explain this part please, if you could edit the code i gave
showing me where to make the changes that would be brilliant.

I am trying to do this honestly.
------------------------------------------------------
First of all, the class that holds the code you show below needs a
reference to the active instance of some other part of the UI. In VB6,
this reference will be globally available under the name of the form,
in VB.NET it's not. You need to explicitly put a variable in the
class (or a global variable, but if a member is possible use that,
it's better from a design point of view) with the ProcessCommands sub
and set it to the instance of your main form. Suppose we call that
variable MainForm.
------------------------------------------------------

Upon reading your code better, it would appear all these functions are
inside a single Form class. Am I right?

If so, you can pretty much ignore the paragraph above, and follow these
instructions. You say you want to run the entire ProcessCommands method in
another thread. That's certainly possible, but do note it will be slightly
slower than using a method that takes no parameters. Still, in your case
it's probably necessary to do it this way (or redesign a large part of your
code), because of some of the methods you call. You must keep in mind that
the System.Windows.Forms.Control class is not thread safe (all controls and
forms are children of this class). I quote the documentation:
"Note: There are four methods on a control that are safe to call from any
thread: Invoke, BeginInvoke, EndInvoke, and CreateGraphics. For all other
method calls, you should use one of the invoke methods to marshal the call
to the control's thread."

But back to the business at hand. You want to use Invoke to run
ProcessCommands in the UI thread, and since ProcessCommands has a non-empty
argument list you need a custom delegate type.

Add to the top of your form a declaration for this delegate type:
Private Delegate Sub ProcessCommandsDelegate(ByVal strMessage As String)
-----------------------------------------
THIS PART IS NEXT
-----------------------------------------
Public Sub DoRead(ByVal ar As IAsyncResult)
Dim BytesRead As Integer
Dim strMessage As String

Try
' Finish asynchronous read into readBuffer and return
number of bytes read.
BytesRead = client.GetStream.EndRead(ar)
If BytesRead < 1 Then
' If no bytes were read server has close. Disable
input window.
Exit Sub
End If
' Convert the byte array the message was saved into, minus
two for the
'Chr(13) and Chr(10)
strMessage = Encoding.ASCII.GetString(readBuffer, 0,
BytesRead - 2)

Now, replace the call to ProcessCommands with the following:
Me.Invoke(New ProcessCommandsDelegate(AddressOf ProcessCommands), _
New Object() {strMessage})

Now ProcessCommands gets run in the proper thread context.
' Start a new asynchronous read into readBuffer.
client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE,
AddressOf DoRead, Nothing)
Catch e As Exception
MsgBox(e.Message)

Don't use MsgBox. It's the VB6 way. The .Net way is MessageBox.Show(). Also,
always(!) use the overloads that take an IWin32Window owner as their first
parameter. I've found it to be rather unreliable in picking the owner in
certain situations if you don't explicitly specify it, especially if you're
doing this in a different thread from the form that's supposed to be the
owner. Therefore, replace the call to MessageBox with this:
MessageBox.Show(Me, e.Message, "Error!")
End Try

End Sub
-----------------------------------------
THE FOLLOWING SUB PROCESSES THE COMMANDS
-----------------------------------------
Public Sub ProcessCommands(ByVal strMessage As String)
'TMSG|1234567890|Matt|1234567890|Message
Dim dataArray() As String
Dim lstR As ListViewItem
dataArray = Split(strMessage, Chr(124))
Select Case dataArray(0)
Case "RNICK"
'lstR = lstContacts.Items.Add(dataArray(2))
'lstR.SubItems.Add(dataArray(1))
-----------------------------------------
THIS IS THE PROBLEM AREA, IDEALLY IT WOULD BE GOOD TO HAVE THIS WHOLE
SUB RUNNING OUT OF THE THREAD SO I CAN EXECUTE CODE HERE
-----------------------------------------
Case "TMSG"
'open message window
'display received message
'Open a new window, or open window with tabs
If F4 Is Nothing Then
F4 = New frmMessage
F4.Show()
End If
Case "ERROR"
MsgBox(dataArray(1), MsgBoxStyle.Exclamation, "An Error
Has Occured")

As above: MessageBox.Show(Me, dataArray(1), "An Error has occurred",
MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation)
End Select
End Sub
-----------------------------------------

Hope this helps. Also, you might want to make some of your methods Private
instead of Public. Anything that doesn't need to be called from another
class should be Private.
 
M

Matthew Lanham

I think i have nearly got it the only thing is all my code is in its own
class e.g. the functions i have shown you exist in clsClient and i have
them defined on form using:

Public clsClient As New clsClient

I tried to put this into a class:

Me.Invoke(New ProcessCommandsDelegate(AddressOf ProcessCommands), _
New Object() {strMessage})


I get an error saying that invoke is not a member of clsClient. What do
i do now, sorry i will get there and thank you very much for your help.
 
S

Sven Groot

Matthew Lanham said:
I think i have nearly got it the only thing is all my code is in its own
class e.g. the functions i have shown you exist in clsClient and i have
them defined on form using:

Public clsClient As New clsClient

Although supported, I wouldn't particularly recommend using variables with
the same name as their type. It doesn't aid the readability of your code.
Again, does it need to be public?
I tried to put this into a class:

Me.Invoke(New ProcessCommandsDelegate(AddressOf ProcessCommands), _
New Object() {strMessage})


I get an error saying that invoke is not a member of clsClient. What do
i do now, sorry i will get there and thank you very much for your help.

No trouble, we're here to help. The 'Me' keyword refers to the current
instantiation of the class it's used in, in this case clsClient, which
indeed has no Invoke. Your form, which inherits Form, which inherits
Control, does have an Invoke method, because Control defines it. In this
case the paragraph that I told you to ignore in the last post applies again.
You need to get a reference to some piece of the UI into clsClient, in this
case your form.

So, add a declaration to clsClient:
Public ThreadUI As System.Windows.Forms.Form ' yes it must be public ^_^
(Feel free to replace the generic Form type with the specific class name of
your form)

Now on the form, in either the Form_Load event or in the form's constructor
(Public Sub New(), it's hidden in the designer code region) (doesn't really
matter where, as long as it's before you call any other methods on the
clsClient variable), add the following line:
clsClient.ThreadUI = Me

And in clsClient, replace the line that reads Me.Invoke from above with:
ThreadUI.Invoke(New ProcessCommandsDelegate(AddressOf ProcessCommands), _
New Object() {strMessage})
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top