Either the server isn't running or you you used the wrong address and/or port when trying to connect to it.
Printable View
Hi Jmc,
I am using your Server app to communicate with remote sensors.
This all works very well and I am able to receive data and send data to the remote sensors using the sendButton_Click function.
My requirement is to be able to send data to the remote sensors without user intervention.
How would I substitute the "Me.hostsComboBox.SelectedItem" for my own Remote IP and Port in the:
Dim host = DirectCast(Me.hostsComboBox.SelectedItem, HostInfo)
Best Regards
I'm guessing that hard-coding them into the app would be inappropriate. Generally this sort of thing is done by adding an entry to the config file and reading the data from there. You can then edit the config file by hand any time to change that data and thereby change how the app behaves. The simplest option for working with the config file is via the Settings page of the project properties. In your case, you'll probably want to use Application scope for one String and one Integer setting. If you then open App.config from the Solution Explorer you can see how they're stored.
Hi jmc,
It would be impossible for me to hard-code the remote IP and Port into the app.
Let me explain:
All remote devices are on Dynamic IP addresses using GSM modems. Every time the GSM re-connects, the IP address changes.
What I am doing on the server side (running your app) is that each message that I receive from a remote device has the unique device ID, I then write the IP, Port and Unique ID to a database.
When I need to send a message to a particular remote device, I retrieve the IP and Port that corresponds to the Unique ID from the database, append the message and send.
One workaround could be reading the IP/Port from the database and setting the hostsComboBox.Text property to that value, the messageTextBox.Text property to the "message" and then invoke the sendButton_click event.
I have not tried this and it seems very cumbersome.
Best Regards
Probably a good thing I didn't recommend that then, isn't it?You don't need to. You just need to do what I suggested, i.e. add a couple of settings and then edit them by hand in the config file after deployment if required.
Hey Jmc, have a quick question for you.
I'm attempting to use your library (which I've modified slightly) in a C# app but I cannot for the life of me figure out how to call or explicitly implement the event handlers for ConnectionAccepted, ConnectionClosed, and MessageReceived. Any ideas or advice?
I appreciate it, and thanks in advance,
RCG
I transitioned from VB.NET to C# about a month ago, and I taught myself .NET/OOP in about the same amount of time, so naturally I have a few problem areas; those just happen to be generics and delegates (which event handlers sort of fall under). Anywho, I got it all fixed up. All I had to do was instantiate a server/client object, subscribe the event handlers to the object members in the class constructor using methods I wrote in the class, and it's good to go.
Thanks again for the help.
Hi all,
I'm looking for some guidance on just how to change the setup of this tcp messageserver so that i can set the port address for listening based on a parameter i read in from the command line eg Arg (1).
I can see that the constructors allow for passing in the port value, however, not sure how to make use of this in my formload method and still have the eventshandlers for ConnectionAccepted, MessageRecieved and ConnectionClosed work.
Thanks
Neil
Hey I am curious, could this be combined into one application that is both a server and a client?
Certainly. The definition of a server is a hardware or software entity that receives connection requests while a client is an entity that requests connections. It's quite possible for one machine or application to be both a client and a server. Exactly how it would be designed depends on exactly what the aim is but there's no issue with a single entity making and accepting connection requests.
Look below -_- I'm a newbie on these forums. :X
Thanks for clearing that up for me! I seem to have done it in this application I made called P2P, but it is very slow and bugy. Do you think you could check out my source and fix it or tell me how I should go about fixing it? I would love to go the Asynchronous route, but I am not sure how to. I am currently using a timer to receive.
Source here:
https://www.dropbox.com/sh/p8racx1l7lqj3br/ZTqA6bxuIA
Hi JMc,
I'm using your sample code (v1.1.0.1) and looking to understand how to set/change the port for the listener (MessageServer) at run time rather than fixed in code.
Can you help me with this?
Thanks
Neil
When a MessageServer object is implemented in C# your library throws a NullReferenceException at this line:
The error occurs when a client connects. I'm assuming the same error will occur for the rest of the event handlers.Code:Protected Overridable Sub OnConnectionAccepted(ByVal e As ConnectionEventArgs)
Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e)
End Sub
Hi JMc,
Seems I haven't been very clear with my question to you. :confused:
I do not have an issue with setting the port value, as it is nothing but an integer in the range of 0 to 65535. Equally, I can use the public property you have provided in your example code to read the current port value that the TCPListener is expecting TCP connections to arrive on.
I do know how to read an integer from a User using Textbox, NumericUpDown, also from application setting using My.Settings and how to read an integer from the command line using the args(i) string array... just to name a few. ;)
If I can use an example from your own code, where you create the object named server in the class using the following statement
The object server is declared using port value of 12345 before any events are activated... as such server exists and is ready for use when the form is loading and for any other subsequent events to use.Code:Private WithEvents server As New MessageServer(12345)
For my application, I am reading from the command line a set of configuration items, which includes the port number that I need the TCPlistener to be operating on.
In this case it is contained in args(2) (exact code I'm using to read command line is below)
Where I am struggling is I can not see a way for me to set the actual port number to be something other value other than by manually changing this value before I build/compile this code.Code:Dim args() As String
Try
' get commandline args
args = Environment.GetCommandLineArgs
sDataSource = args(1).ToUpper
iListenerPort = CInt(args(2))
UpdateLog("Info: Command Line Arguments Loaded")
Catch ex As Exception
' load defaults
sDataSource = "E6410\SQLEXPRESS"
iListenerPort = 6002
End Try
I can not simply apply a new port value in my form load event using the port parameter
eg
as it this parameter is readonly and there is no code in the MessageServer Class to support changing the port number.Code:server.Port(iListenerPort)
I have already attempted to declare the object in my Form load event using the following statement where iListenerPort = 6002 for testing.
And I've added the following event handlersCode:Dim server As New MessageServer(iListenerPort )
The problem I am finding here is that the server object is localised to this event and not accessible to other events, eg those referenced by the event handlersCode:AddHandler server.ConnectionAccepted, AddressOf server_ConnectionAccepted
AddHandler server.MessageReceived, AddressOf server_MessageReceived
AddHandler server.ConnectionClosed, AddressOf server_ConnectionClosed
Very specifically the Hosts object is empty when the server.send method is called.
So, returning to my original question, how do I declare an object (in my case TCPserver) as type MessageServer with a port value that I have read from the command line (using Args() string array) using the constructors that you have provided that is accessible to all other events in my application?
I'm thinking that maybe I need to be looking at how to add code to your MessageServer class to allow me to set the Port property and have the TCPListener (that already exists) stop, change port value and start.
Thanks
Neil
That would suggest that nothing is being assigned to the _synchronisingContext field, so you need to work out why. I doubt that it's got anything to do with C#. I think you'll find that SynchronizationContext.Current will return a null reference if you're not using it in either a WinForms or WPF application. Could that be the reason in your case?
I'm using it in a WinForms application at the moment, so I'm sure that isn't the issue. As for working out why it's throwing that error I'm honestly at a loss. I'm running over it right now and it is showing null. I can connect fine (although I'm seeing 2-3 errors popping up due to the read/write methods, NullReferenceExceptions, IOException, ObjectDisposedException), I can close the server and it'll update all connected client's fine, I can restart the server and reconnect just fine, but when I go to close a client using client.Dispose() the server hangs on that method (OnConnectionClosed now).
If I remove client.Dispose(), the ConnectionClosed event will never be raised, even when the client application is stopped or shut down.
I don't see why it wouldn't when something about C# is causing it not to function correctly. When I use the library in VB.NET 2010 everything works just fine; zero problems that I haven't fixed myself. When I try the same in C# 4 it's a completely different story. For some reason it just isn't jiving well on these lines:
Nothing has been changed, and I know I'm not going crazy. I've ran over the entire library multiple times, and I've very thoroughly went over both the read and send methods line by line to no avail. It has left me completely dumbfounded. The only event that is raised is MessageReceived; both ConnectionClosed and ConnectionAccepted fail, although the client connects.Code:Protected Overridable Sub OnConnectionAccepted(ByVal e As ConnectionEventArgs)
Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e)
End Sub
Please stop talking about "C#" in this thread/section. If have a C# question post it here.
FOREWORD: I opened my account on this forum today for two reasons; 1) to continue discussing a legitimate question in a reasonable tone, and more importantly, to say that this code is the most amazing code I have ever gotten off the internet. Seriously. I'm also a new guy to .net, coming from AmigaBasic (joke, vb6) 1 month ago. In my month of scouring the internet looking and learning to make test programs to learn the language, I have BY FAR learned more from this code than from anything else. Multiple programs from one solution, all while sharing and building a library, with all sorts of fancy commenting and region stuff I never knew existed. The library implements great into other programs, too! THANK YOU so much. I'm a big fan.
That said, I'm working in VB2010 (.net 4.0) and am bumping into a similar issue. The server (as far as I can tell) must be run from a form in order for the SynchronizationContext.Current to not return a null value.
Making a Windows Forms Application launched from sub main (to make it behave as similar as possible to module-based code), I put the following in a blank new form's code (it's basically just a stripped down version of the test server):
Clients can connect and talk, the server repeats the chat, and clients can even shut down the server with "exit". The server only has a single blank window running, which I guess could be hidden, but I what I originally wanted was to run the server from a module.Code:Module serverModule
Public Sub main()
serverWindow.Show()
Application.Run()
End Sub
End Module
Public Class serverWindow
Public WithEvents server As New MessageServer(12345)
Public ReadOnly hosts As New List(Of HostInfo)
Private Sub server_ConnectionAccepted(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionAccepted
hosts.Add(e.Host)
End Sub
Private Sub server_MessageReceived(ByVal sender As Object, ByVal e As Net.MessageReceivedEventArgs) Handles server.MessageReceived
server.Send(e.Message)
If e.Message = "exit" Then Me.Close()
End Sub
Private Sub server_ConnectionClosed(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionClosed
Dim host = e.Host
hosts.Remove(host)
End Sub
Private Sub MainWindow_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
server.Dispose()
Application.Exit()
End Sub
End Class
A nearly identical module-based version:
Whenever a client joins, the same line "Me._synchronisingContext.Post(AddressOf RaiseConnectionAccepted, e)" says object reference not set to an instance of an object.Code:Module serverModule
Public Sub main()
Application.Run()
End Sub
Public WithEvents server As New MessageServer(12345)
Public ReadOnly hosts As New List(Of HostInfo)
Private Sub server_ConnectionAccepted(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionAccepted
hosts.Add(e.Host)
End Sub
Private Sub server_MessageReceived(ByVal sender As Object, ByVal e As Net.MessageReceivedEventArgs) Handles server.MessageReceived
server.Send(e.Message)
If e.Message = "exit" Then shutdown()
End Sub
Private Sub server_ConnectionClosed(ByVal sender As Object, ByVal e As Net.ConnectionEventArgs) Handles server.ConnectionClosed
Dim host = e.Host
hosts.Remove(host)
End Sub
Private Sub shutdown()
server.Dispose()
Application.Exit()
End Sub
End Module
The same error occurs when I try to reproduce the same thing by running the server from a class (without a form). Yet the client seems to run great from a module.
So I guess the question is, would it, and if so, how would it be possible to run a server using your current library without it running from a form?
@drankof, I'm glad that you found the code useful. As I said, SynchronizationContext.Current returns Nothing when used in other than a Windows Forms or WPF app. That's because the whole point of the SynchronizationContext class is to allow you to execute code on the thread that owns a specific control. If you have no controls then it doesn't matter what thread you execute code on. If you don't want to use my library in a GUI app then you can get rid of all the stuff relating to the SynchronizationContext. If you want to be able to use it in both GUI and non-GUI apps then you'll need to do something like this:In non-GUI apps, some additional thread synchronisation may be required to ensure that actions occur in the appropriate sequence but that is beyond the scope of this thread.Code:If mySynchronizationContext Is Nothing Then
'Non-GUI app so call your method directly on the current thread.
Else
'GUI app so invoke your method via the SynchronizationContext.
Else
Wow...it works by executing code on a thread that owns a control, which would need a form, that makes perfect sense. I think that just explained about 1/2 of what I didn't/don't understand regarding how the asynchronization/multithreading(/not pausing just to passively listen for people joining...not sure what word is appropriate) works on this server. Now I just need to finish investigating some of the other intricacies of threading, in general. Thank you so much and for the tip on how to implement it without a form/GUI. :thumb:
Great Work, lean programming!
I develop listeners quite a while for a GPRS project. Let me put a question on the table:
When a GPRS device connects to the listener it takes an IP address and a random port. Then the device looses the connection and later connects to listener with the same address and same port. What is happening ? So far the program crashes. In a previous listener I kept a list of ip addresses and manually I perform a check (if exists) before connection. Do you think any other way to prevent a "same port" situation ?
Best Regards
Vangelis
@jmcilhinney - I looked at your post in the code bank. I was wondering why you chose the ports you did? It seems that you should be using port numbers 49152–65535.
Hi, I said a long time ago that it wasn't receiving packets in order... here is the case...
This is my modified MessageClient.Read:
vb Code:
Private Sub Read(ByVal ar As IAsyncResult) Try 'The stream will be Nothing if the client has been disposed. If Me.stream IsNot Nothing Then Dim buffer = DirectCast(ar.AsyncState, Byte()) 'Complete the asynchronous read and get the first block of data. Dim byteCount = Me.stream.EndRead(ar) If byteCount = 0 Then 'If there is no data when an asynchronous read completes it is because the server closed the connection. Me.OnConnectionClosed(New ConnectionEventArgs(Me.server)) Else 'Start building the message. 'Dim message As New StringBuilder(Me.Encoding.GetString(buffer, 0, byteCount)) OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer}) 'As long as there is more data... While Me.stream.DataAvailable '...read another block of data. byteCount = Me.stream.Read(buffer, 0, Me.BufferSize) OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer}) End While 'Listen asynchronously for another incoming message. Me.stream.BeginRead(buffer, 0, Me.BufferSize, AddressOf Read, buffer) 'Notify any listeners that a message was received. 'Me.OnMessageReceived(New MessageReceivedEventArgs(Me.server, message.ToString())) End If End If Catch ex As IOException 'The callback specified when BeginRead was called may get invoked one last time when the TcpClient is disposed. 'This exception is thrown when EndRead is called on a disposed client stream. End Try End Sub
I will try to describe this the best way i can ... but basically ... when there is no more data available in the Me.stream.DataAvailable loop ... because we have reached the end of the stream that the client has received sofar (but it is still sending such as a large file).. it will then come back to this sub again as the server is still sending ... and Dim buffer = DirectCast(ar.AsyncState, Byte()) then starts reading data from a random chunk... not necessarily the next chunk that was sent...
also ... if i change the code to this it works:
As this seems to make it slow down enough to receive the next chunk ... but this is not good of course as it slows it down ... and the data may take more than 100ms to get the next bit... it is just proof of concept...Code:While Me.stream.DataAvailable
'...read another block of data.
byteCount = Me.stream.Read(buffer, 0, Me.BufferSize)
OnDataChunkRecieved(New DataChunkRecievedEventArgs() With {.Chunk = buffer})
'wait for a second to see if more data is coming...
System.Threading.Thread.Sleep(100)
End While
Any ideas jmcilhinney?
Thanks,
Kris
hello? (*bump)
@i00
Your problem is the overall design. You should never depend on the socket implementation to tell you when a download is complete. Always design at least a minimum protocol. My favored method is attaching a prefix that at the start of a stream that tells me how many bytes to expect so I keep track of the number of bytes at the receiving end that we have received at any point. Using this method, one can determine when a download is complete.
OK ... But if I do this ... I still have a problem ... how do I wait for the rest of the data? because Me.stream.DataAvailable will return false but it will still be transmitting...
Edit: also this download also relies on the data not being long enough to be split into multiple packets
Kris
I'm currently putting together two small applications to demonstrate a good way to handle file transfers over TCP/IP using Winsock through the TcpListener/TcpClient classes. I'll post it in the code bank when I'm done. Hopefully it would make things a little clearer.
Ok, I've posted two sample applications that show how to transfer a file between a client and a server using WinSock. They should give you an idea about to detect completion of data transfers. Here is the Code Bank thread.