I'm writing an app that sends messages over a network to another PC that processes the message and sends back a reply (e.g. a boolean or some other data). My network communication subs (based on WCF) just give me the ability to send a message to the other PC and process any received messages.
The issue is that any response received is processed by my designated network message event handler sub, but I have no way of feeding it back to the sub that called for the original message to be sent.
To quickly illustrate, the situation is something like this:
Private Function NetSendMessage(ByVal message As String) As Boolean
'Send network message to PC with device connected
'....
'returns true if message sent
End Function
Private Sub NetMessageReceived(ByVal sender As Object, ByVal e As MessageEventArgs) Handles NetComm.OnReceiveMessage
'Handles incoming messages
'....
End Sub
Private Function MoveForward() As Boolean
Return (MoveLeftWheel() And MoveRightWheel())
End Function
Private Function MoveLeftWheel() As Boolean
If NetSendMessage("MoveLeftWheel") = False Then Return False
'Network message went through, now we need to know if the command was executed, which the remote PC will tell us
Return ______ *(here is the trouble-- how do I get the boolean response from the other side as to whether this worked or not?)*
End Function
Private Function MoveRightWheel() As Boolean
If NetSendMessage("MoveRightWheel") = False Then Return False
Return ______
End Function
The solutions I've thought of so far are more complex than this probably needs to be.
One idea I had was to add an ID number to identify each message (which the other side would include in its response), and then I'd have an ArrayList for a "message queue." The NetMessageReceived sub would add any new message to this ArrayList, and any function that wants a reply would monitor the queue to see if its response arrived. It'd be something like this:
Private NetMessageQueue as New ArrayList
Private LatestMessageID as Integer
Private Sub NetMessageReceived(ByVal sender As Object, ByVal e As MessageEventArgs) Handles NetComm.OnReceiveMessage
'Handles incoming messages
NetMessageQueue.Add(e.Message.ToString)
End Sub
Private Function MoveLeftWheel() As Boolean
'Send network message to robot PC
Private MyMessageID as Integer = LatestMessageID + 1
NetSendMessage(MyMessageID & "|" & "MoveLeftWheel")
Do
For Each msg As String in NetMessageQueue
If msg.Split("|")(0) = MyMessageID.ToString Then
'This is the response message we're waiting for
Return Boolean.Parse(msg.Split("|")(1))
End If
Next
Loop
End Function
This seems like a very cumbersome / unnecessarily taxing way to do this. I was thinking along the lines of maybe add a NetSendReceive function that, say, MoveRightWheel could call; NetSendReceive would call NetSendMessage, monitor the message queue for its response (perhaps via a delegate/IAsyncResult), and then return the response message (to MoveRightWheel, which would be waiting for it).
There's probably a better way to do this- any ideas?
What if you wrap all of this in a class? Then the idea would be that you'd only have one "NetComm" object in each instance. Say you call the class MessagingUtility. And whenever you want to send a new message you'll have to call
Dim myMessage As New MessagingUtility()
myMessage.NetSendMessage("Hello World")
Or something like that.
This will prevent any possibility of you losing track of the context of a message when a lot of other messages are being sent around the same time. Because after all, every property of a given message you'll ever want will be contained neatly in the myMessage object.
Related
I'm new to multi-threading, so I need a general guidance how to proceed.
In a nutshell, I need to call an external webservice thousands of times per second. The response is roughly 1 second per record, which does not work well if I have a million of records to send. So, I've been tasked to use multi-threading to open multiple threads (the number is controlled dynamically) to simultaneously call WS. So, if I can open 100 threads that call WS simultaneously, the task should finish much faster... in theory.
Code is at the bottom, I've cut a lot of unnecessary pieces out of it so please let me know if something makes no sense
The idea behind this code is you would call in Threads.Process, pass DataTable with data that needs to be sent out via WS. This process would run until we processed all records in the data table and _threads (which holds a list of background objects currently working) has zero items in it.
StartThreads would instantiate new background objects if number of simultaneous threads permits and there is new data to be worked on. When a background object completes its task, in order to pass data back to the static object to log its data, it calls Threads.ThreadFinished and passes itself as parameter at which point logging info would be recorded and backgroundobject is removed from _threads.
However, during my testing I noticed that threads are overlaping when calling Threads.ThreadFinished, I tried to offset it with SyncLock to keep it thread safe, but it still does not work 100% fool proof (probably because there are other shared functions still not protected). Since I don't understand this threading very well, I have a feeling there is an easier way of doing this. So, am I on a right track? Should I keep going or switch to a different method? I've researched other multi-threading methods like ThreadPool, but I ended up with this method.
Public Class Threads
Public Shared Sub ProcessRecords(ByVal dt As DataTable)
_threads.Clear()
_startTime = Now
_dt = dt
While Working()
StartThreads()
Thread.Sleep(100)
End While
End Sub
Private Shared Sub StartThreads()
While _threads.Count < Settings.NumberOfThreads AndAlso _rowCounter < _dt.Rows.Count
Dim id As String = GetRandomChar(, 20) ' Generate a random ID for logging purposes
Dim tw As New ThreadWorker(_dt.Rows(_rowCounter), _rowCounter, id, _dt.Rows(_rowCounter)("id").ToString())
_rowCounter += 1
_threads.Add(tw)
End While
End Sub
Public Shared Sub ThreadFinished(ByVal tw As ThreadWorker)
SyncLock tw
Log(tw.Log)
_threads.Remove(tw)
End SyncLock
StartThreads()
End Sub
Private Shared Function Working() As Boolean
Return _rowCounter < _dt.Rows.Count OrElse _threads.Count > 0
End Function
End Class
Public Class ThreadWorker
Private _bw As System.ComponentModel.BackgroundWorker
Private _logDebug As New StringBuilder
Sub New(ByVal dr As DataRow)
_bw = New System.ComponentModel.BackgroundWorker
_bw.WorkerReportsProgress = True
_bw.WorkerSupportsCancellation = True
AddHandler _bw.DoWork, AddressOf bw_Working
AddHandler _bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
_bw.RunWorkerAsync()
End Sub
Private Sub bw_Working(ByVal Sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
' Call webservice ...
_logDebug.AppendLine("Response from WS")
End Sub
Public Sub bw_RunWorkerCompleted(ByVal Sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
_logDebug.AppendLine("Job completed")
Threads.ThreadFinished(Me)
End Sub
End Class
EDIT 1:
I think I've solved an issue with threads getting 'lost' - basically I have to SyncLock _threads in all Shared functions (SyncLock tw doesn't seem to do it's job). Still, this feels dirty and hacky at best. I feel like there is a better way to do things. I am currently looking into ThreadPool method, but I am a bit put off by its number of thread limits. I will potentially need to open thousands of threads.
What else I noticed is that the more concurrent threads I open, the longer it takes to get initial response from the WS. For example, if I work 1 record/thread at a time, it takes 1 second to get a response from WS. If I open 100 threads, it takes up to a minute to get a response. I suspect networking is buckling here, or maybe its a windows limitation?
I am coding a poker game in vb.net, and I want to create a multiplayer game using the internet, whether it being on WAN or LAN. I really have no idea where to start, and I don't know what to search on Google. I know that I will have to listen on a port, and I need to send and receive packets. No searches on Google are helping. Any links/ideas?
Also, how would I be able to show a list box of all the games currently in progress and allow the user to join? And how to create a new game?
I'd recommend learning the UdpClient class, it's a good option for beginners.
You first start by 'setting up' two clients, one to send data, and one to listen for incoming data. Once you've assigned the clients with the appropriate addresses and port numbers you then start the listening client in a loop, so that you can consistently listen for data.
Then you 'wire up' your sending client to some form of trigger, (in the example i've given below, i've set my sending client up to a button event) so you can send data at irregular intervals, or you can set your client in a loop to continuously send data.
Once you've done that, you now need to convert the data you want to send from string to a byte array, then you can finally send it, and vis versa for receiving data (from byte array to string).
Here's a simple example,
Imports System.Net.Sockets
Imports System.Threading
Imports System.Text
Imports System.Net
Public Class Form1
Private Const port As Integer = 9653 'Or whatever port number you want to use
Private Const broadcastAddress As String = "255.255.255.255"
Private receivingClient As UdpClient
Private sendingClient As UdpClient
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
InitializeSender()
InitializeReceiver()
End Sub
Private Sub InitializeSender()
sendingClient = New UdpClient(broadcastAddress, port) 'Use broadcastAddress for sending data locally (on LAN), otherwise you'll need the public (or global) IP address of the machine that you want to send your data to
sendingClient.EnableBroadcast = True
End Sub
Private Sub InitializeReceiver()
receivingClient = New UdpClient(port)
ThreadPool.QueueUserWorkItem(AddressOf Receiver) 'Start listener on another thread
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim stringToSend As String = TextBox1.Text 'Assuming you have a textbox with the data you want to send
If (Not String.IsNullOrEmpty(stringToSend)) Then
Dim data() As Byte = Encoding.ASCII.GetBytes(stringToSend)
sendingClient.Send(data, data.Length)
End If
End Sub
Private Sub Receiver()
Dim endPoint As IPEndPoint = New IPEndPoint(IPAddress.Any, port) 'Listen for incoming data from any IP on the specified port
Do While True 'Notice that i've setup an infinite loop to continually listen for incoming data
Dim data() As Byte
data = receivingClient.Receive(endPoint)
Dim message As String = Encoding.ASCII.GetString(data) 'Recived data as string
Loop
End Sub
End Class
And now for adding a list box for available games.
Short answer, it's impossible very, very hard to do unless you have a server.
Longer answer, you'll need a server, and assuming you have a server you'll need to create an additional program to handle the data that's being sent to it, and to send data to other users.
I could go on to explain how to setup the additional program etc, etc, but judging that your network programming skills are still 'spreading out their wings', I'd suggest that you leave out advanced features like this until you have some more experience, then when you're more confident have a try by yourself and if you're still struggling just pop a question on here and I'm sure someone will help you.
So I'm trying to make a very simple system to send messages from a client to a server (and later on from server to client as well, but baby steps first). I'm not sure exactly how to use UDPClient to send and receive messages (especially to receive them), mostly because I don't have anything triggering the ReceiveMessage() function and I'm not sure what would.
Source Code is at this link, go to File>Download. It is already built if you want to just run the exe.
So my question is basically: How can I easily use UDPClient, how can I get this system to work and what are some tips for executing this kind of connection? Anything I should watch out for (threading, issues with code,etc)?
Source.
You need first need to set up two UdpClients. One client for listening and the other for sending data. (You'll also need to pick a free/unused port number and know the IP address of your target - the machine you want to send data to.)
To set up the receiver,
Instantiate your UdpClient variable with the port number you chose earlier,
Create a new thread to avoid blocking while receiving data,
Loop over the client's receive method for as long as you want to receive data (the loop's execution should be within the new thread),
When you receive one lot of data (called a "packet") you may need to convert the byte array to something more meaningful,
Create a way to exit the loop when you want to finish receiving data.
To set up the sender,
Instantiate your UdpClient variable with the port number you chose earlier (you may want to enable the ability to send broadcast packets. This allows you to send data to all listeners on your LAN),
When you need to transmit data, convert the data to a byte array and then call Send().
I'd suggest that you have a quick skim read through this.
Here's some code to get you started off...
'''''''''''''''''''''''Set up variables''''''''''''''''''''
Private Const port As Integer = 9653 'Port number to send/recieve data on
Private Const broadcastAddress As String = "255.255.255.255" 'Sends data to all LOCAL listening clients, to send data over WAN you'll need to enter a public (external) IP address of the other client
Private receivingClient As UdpClient 'Client for handling incoming data
Private sendingClient As UdpClient 'Client for sending data
Private receivingThread As Thread 'Create a separate thread to listen for incoming data, helps to prevent the form from freezing up
Private closing As Boolean = False 'Used to close clients if form is closing
''''''''''''''''''''Initialize listening & sending subs'''''''''''''''''
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
InitializeSender() 'Initializes startup of sender client
InitializeReceiver() 'Starts listening for incoming data
End Sub
''''''''''''''''''''Setup sender client'''''''''''''''''
Private Sub InitializeSender()
sendingClient = New UdpClient(broadcastAddress, port)
sendingClient.EnableBroadcast = True
End Sub
'''''''''''''''''''''Setup receiving client'''''''''''''
Private Sub InitializeReceiver()
receivingClient = New UdpClient(port)
Dim start As ThreadStart = New ThreadStart(AddressOf Receiver)
receivingThread = New Thread(start)
receivingThread.IsBackground = True
receivingThread.Start()
End Sub
'''''''''''''''''''Send data if send button is clicked'''''''''''''''''''
Private Sub sendBut_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles sendBut.Click
Dim toSend As String = tbSend.Text 'tbSend is a textbox, replace it with whatever you want to send as a string
Dim data() As Byte = Encoding.ASCII.GetBytes(toSend)'Convert string to bytes
sendingClient.Send(data, data.Length) 'Send bytes
End Sub
'''''''''''''''''''''Start receiving loop'''''''''''''''''''''''
Private Sub Receiver()
Dim endPoint As IPEndPoint = New IPEndPoint(IPAddress.Any, port) 'Listen for incoming data from any IP address on the specified port (I personally select 9653)
While (True) 'Setup an infinite loop
Dim data() As Byte 'Buffer for storing incoming bytes
data = receivingClient.Receive(endPoint) 'Receive incoming bytes
Dim message As String = Encoding.ASCII.GetString(data) 'Convert bytes back to string
If closing = True Then 'Exit sub if form is closing
Exit Sub
End If
End While
End Sub
'''''''''''''''''''Close clients if form closes''''''''''''''''''
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
closing = True 'Tells receiving loop to close
receivingClient.Close()
sendingClient.Close()
End Sub
Here are a few other exmples: Here, here, here and here.
Imports System.Threading
Shared client As UdpClient
Shared receivePoint As IPEndPoint
client = New UdpClient(2828) 'Port
receivePoint = New IPEndPoint(New IPAddress(0), 0)
Dim readThread As Thread = New Thread(New ThreadStart(AddressOf WaitForPackets))
readThread.Start()
Public Shared Sub WaitForPackets()
While True
Dim data As Byte() = client.Receive(receivePoint)
Console.WriteLine("=" + System.Text.Encoding.ASCII.GetString(data))
End While
End Sub
I'm realizing a .NET chat application but i still have that error:
I can send only a message per connection.
For example. With the code below, i can send only one message that can be received correctly by the other peer, but if i send another message message, on the same connection,it won't be received by the remote PC.
Here is the code:
Dim client_TCP As New TcpClient
Private Sub send_obj(ByVal obj As Object)
Dim bf As New BinaryFormatter
Dim tosend As Packet
tosend.data = obj
bf.Serialize(client_TCP.GetStream(), tosend)
client_TCP.GetStream.Flush()
End Sub
Private Sub connect_to_port()
Try
client_TCP = New TcpClient(client_data.getIP(), client_data.getPort())
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
Private Sub disconnect_from_port()
client_TCP.Close()
End Sub
And here is the listener:
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles timer.Tick
If client_TCP_listener.Pending = True Then
....
End If
End Sub
So, to send a message I always need to do this (example):
Dim b As Byte
b = 1
disconnect_from_port()
connect_to_port()
client_TCP.GetStream().WriteByte(b)
client_TCP.GetStream().Flush()
I tried to put\remove the flush from both the code. Nothing happened.
Have you got any ideas?!
How do you know that it is not received? Did you try emulating remote host with your own code?
TCP is a stream protocol that means that you can send two bytes doing two separate calls to client_TCP.GetStream().WriteByte(b) and the remote party can receive those 2 bytes using one Receive call. You should not make any assumptions on the I/O API call patterns (number of writes and number of receives) but analyze the data you're sending receiving via stream connection.
So to send messages using stream protocol you have to introduce the notion of the application protocol. For instance [2bytes-size][size-bytes of message] - which means that if we want to send "hello" message we at first will send 2 bytes containing the size of the "hello" string and then the string itself.
make sure that receiving end is also using .net BinaryFormatter for deserlizing the object.
I keep running into situations where I don't know what event I have to listen to in order to execute my code at the correct time. Is there any way to get a log of all events that is raised? Any way to filter that log based on what object raised the event?
EDIT: Final solution:
Private Sub WireAllEvents(ByVal obj As Object)
Dim parameterTypes() As Type = {GetType(System.Object), GetType(System.EventArgs)}
Dim Events = obj.GetType().GetEvents()
For Each ev In Events
Dim handler As New DynamicMethod("", Nothing, parameterTypes, GetType(main))
Dim ilgen As ILGenerator = handler.GetILGenerator()
ilgen.EmitWriteLine("Event Name: " + ev.Name)
ilgen.Emit(OpCodes.Ret)
ev.AddEventHandler(obj, handler.CreateDelegate(ev.EventHandlerType))
Next
End Sub
And yes, I know this is not a good solution when you actually want to do real stuff that triggers off the events. There are good reasons for the 1 method - 1 event approach, but this is still useful when trying to figure out which of the methods you want to add your handlers to.
The only way that I can think of is to use Reflection to enumerate all of the events and wire up a generic handler which would be a PITA.
Is the problem with Framework events? If so, Microsoft does a pretty good job of giving event life-cycle/call order.
Edit
So here's a global event capture routine:
Private Sub WireAllEvents(ByVal obj As Object)
'Grab all of the events for the supplied object
Dim Events = obj.GetType().GetEvents()
'This points to the method that we want to invoke each time
Dim HandlerMethod = Me.GetType().GetMethod("GlobalHandler")
'Loop through all of the events
For Each ev In Events
'Wire in a handler for the event
ev.AddEventHandler(obj, [Delegate].CreateDelegate(ev.EventHandlerType, Me, HandlerMethod))
Next
End Sub
Public Sub GlobalHandler(ByVal sender As Object, ByVal e As EventArgs)
'Probably want to do something more meaningful here than just tracing
Trace.WriteLine(e)
End Sub
To wire it in just call WireAllEvents(Me.DataGridView1) supplying your object. Almost all MS events use sender/e (including DataGridView) format but if for some reason it doesn't I think this code will error out. But I just tested it with both a DataGridView and Form and it worked as expected.