I'd like a TCP socket server example. I need it, and I haven't been able to find it anywhere. Only a few useless samples.
Printable View
I'd like a TCP socket server example. I need it, and I haven't been able to find it anywhere. Only a few useless samples.
The MSDN 101 Samples has a pretty good example.
Anyways, heres one Ive made for you:
VB Code:
Dim listener As Net.Sockets.TcpListener Dim listenThread As Threading.Thread Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load listener = New Net.Sockets.TcpListener(Net.IPAddress.Any, 32111) listener.Start() listenThread = New Threading.Thread(AddressOf DoListen) listenThread.IsBackground = True listenThread.Start() End Sub Private Sub DoListen() 'This is the sub that does all the actual "listening". Its an endless loop that calls the AcceptTcpClient method over and over. 'If there is no connecting TcpClient, it will raise an exception but we will ignore this by catching it in a try statement and doing nothing with it. 'On the other hand, If a client has connected, it proceeds to the next line and a streamreader reads the incoming stream and shows it in a messagebox. Dim sr As IO.StreamReader Do Try Dim client As Net.Sockets.TcpClient = listener.AcceptTcpClient sr = New IO.StreamReader(client.GetStream) MessageBox.Show(sr.ReadToEnd) sr.Close() Catch End Try Loop End Sub
Ive just put some badly written explanations in the DoListen sub, I think you can figure the rest out:)
Uuuuh! Good job! However, how to make it multi client compatible?Quote:
Originally Posted by Atheist
It is multiclient compatible.
Alot of clients can send data to the server at the same time.
Yes, but what I mean, is that how to make the server be able to reply to a specific client of all the clients connected?Quote:
Originally Posted by Atheist
Aha. Wait 1 sec rewriting example...
Nice. Thanks.Quote:
Originally Posted by Atheist
VB Code:
Dim listener As Net.Sockets.TcpListener Dim listenThread As Threading.Thread Dim Clients As New Dictionary(Of String, Net.Sockets.TcpClient) Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load listener = New Net.Sockets.TcpListener(Net.IPAddress.Any, 32111) listener.Start() listenThread = New Threading.Thread(AddressOf DoListen) listenThread.IsBackground = True listenThread.Start() End Sub Private Sub DoListen() 'This is the sub that does all the actual "listening". Its an endless loop that calls the AcceptTcpClient method over and over. 'If there is no connecting TcpClient, it will raise an exception but we will ignore this by catching it in a try statement and doing nothing with it. 'If a client has connected it proceeds to the next line and a streamreader reads the incoming stream and shows it in a messagebox. Dim sr As IO.StreamReader Do Try Dim client As Net.Sockets.TcpClient = listener.AcceptTcpClient sr = New IO.StreamReader(client.GetStream) If sr.ReadToEnd = "Register" Then Clients.Add("Mr Bean", client) End If sr.Close() Catch End Try Loop End Sub Private Function SendToUser(ByVal user As String, ByVal data As String) As Boolean If Clients.ContainsKey(user) Then Dim sw As New IO.StreamWriter(Clients.Item(user).GetStream) sw.Write(data) sw.Flush() sw.Close() Return True Else Return False End If End Function
In this example, if a client sends "Register" to the server, the server adds the client to a Dictionary along with a unique string that identifies it (altough in this case, the string will not be unique, but you will need to change that). And the SendToUser function pretty much speaks for itself.;)
Very impressive! You keep surprising me! :)Quote:
Originally Posted by Atheist
Now, what if I want to send data to all clients connected? :)
You would do something like this:
VB Code:
Private Sub SendToAll(ByVal data As String) Dim sw As IO.StreamWriter For Each c As Net.Sockets.TcpClient In Clients.Values sw = New IO.StreamWriter(c.GetStream) sw.Write(data) sw.Flush() sw.Close() Next End Sub
That's great! I'll test that tomorrow, and reply with my rating from 1 to 10, among with any problems I might experience. Thanks for it! It seems suitable! +REP!Quote:
Originally Posted by Atheist
So what if I want the code to have some kind of event that triggers when one of the clients receive data?Quote:
Originally Posted by Mathiaslylo
Each client is using a System.Net.Sockets.TcpClient, you will need to read its stream continouesly to find whenever something is incoming.
VB Code:
Private client As Net.Sockets.TcpClient Private readBuffer(255) As Byte Private Sub Button1_Click(...) client = New Net.Sockets.TcpClient("somewhere", 0) client.GetStream.BeginRead(readBuffer, 0, 255, AddressOf DoRead, Nothing) End Sub
And then you will need a subroutine just like on the server:
VB Code:
Private Sub DoRead(ByVal ar As IAsyncResult) Try 'EndRead returns the number of bytes that were recieved, so we will need to check so that something was recieved Dim TotalBytes As Integer = client.GetStream.EndRead(ar) If TotalBytes > 0 Dim strMessage As String = System.Text.Encoding.ASCII.GetString(readBuffer, 0, TotalBytes) 'Here is the place to do whatever you want with the incoming message: strMessage 'Calling this method again is important, it begins the read again. client.GetStream.BeginRead(readBuffer, 0, 255, AddressOf DoRead, Nothing) Catch e As Exception MessageBox.Show(e.Message) End Try End Sub
Okay, so how do I know which client reads the data and receives it?Quote:
Originally Posted by Atheist
I dont really understand what you mean. All the code I posted in my last post should go in the client application. You would know who recieves the data because you would know who you sent the data too ;)
I'm sorry I was misunderstood. I'll try to explain in another way. In the DoRead sub, I would like it from there to also return the actual name of the client you add when you useQuote:
Originally Posted by Atheist
Clients.Add("Mr Bean", client)
:D
Aha, you would require the client to send his username along with the "Register" keyword.
I know that this technique was used in the MSDN example, lets say the client wants to send two things at once, both the "Register" keyword and his name, he would then send this:
"Register|Mr Bean"
to the server. When the server recieves this string, it splits it on each | character, and evaluates the first part (Which should always be the 'keyword'). Heres how the DoListen sub would look:
VB Code:
Private Sub DoListen() 'This is the sub that does all the actual "listening". Its an endless loop that calls the AcceptTcpClient method over and over. 'If there is no connecting TcpClient, it will raise an exception but we will ignore this by catching it in a try statement and doing nothing with it. 'If a client has connected it proceeds to the next line and a streamreader reads the incoming stream and shows it in a messagebox. Dim sr As IO.StreamReader Do Try Dim client As Net.Sockets.TcpClient = listener.AcceptTcpClient sr = New IO.StreamReader(client.GetStream) 'We split the incoming string by each | character, giving us an array of string elements where the first element is the 'Keyword' Dim Data() As String = sr.ReadToEnd.Split("|"c) sr.Close() Select Case Data(0) Case "Register" 'Data(0) was Register, so we know that the next element will be the name of the client If Not Clients.ContainsKey(Data(1)) Clients.Add(Data(1), client) End If End Select Catch End Try Loop End Sub
Good job. Now, back to my question. In DoRead, how can I identify the name of the client I am reading from?Quote:
Originally Posted by Atheist
I dont understand the question:o . DoRead is the subroutine in the client application, it reads its own stream for incoming data. Any incoming data will be from the server.
The clients will not be sending data between eachother, everything goes through the server.
Well, I'll try to modify your code to explain.Quote:
Originally Posted by Atheist
First, let's say 2 clients are connected. "John", and "Mathy". This sub then occurs, since one of those 2 clients receives data, which causes the "DoRead" sub to be called. However, inside the DoRead sub, how do I know if it was "John" or "Mathy" that received the data?
I take it that John and Mathy are both on their own pc's, then they will be running their own instances of the client application. Two clients will not be sharing the same application, and therefor not the same DoRead subroutine :)Quote:
Originally Posted by Mathiaslylo
I hope that answer was clear :o
Well, it wasn't :) You see, I still can't determine who is who.Quote:
Originally Posted by Atheist
I'd like the function to be modified something like:
Private Sub DoRead(ByVal ar As IAsyncResult, byval User as string)
Where User is the user receiving data?
So you're saying that there will only be 1 client application that will be used by many users? If so:
Every message that the server sends should consist of atleast two parts: The first part is the 'Keyword' (defines what the message actually is), and the second part should be to which user the message is addressed. Something like this:
VB Code:
"Message|Mr Bean|Go to bed!"
Could be sent from the server, and then evaluated on the client.
Ah! Thank you so much :D!Quote:
Originally Posted by Atheist
So what about a TCP client then?Quote:
Originally Posted by Mathiaslylo
What about it?:)
Well, I actually need an example of that as well :)Quote:
Originally Posted by Atheist
Post #13 is all about the clients communication:)
The only thing I havent shown is how to send data from the client to the server, but it is done exactly like on the server side. Write to the stream using a streamwriter:
VB Code:
Public Sub SendData(ByVal data As String) Try Dim writer As New IO.StreamWriter(client.GetStream) writer.Write(data) writer.Flush() Catch ex As Exception MessageBox.Show("An error occured, are you sure the server is running?.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub
You're right, but post #13 doesn't show how to connect with a client ;)Quote:
Originally Posted by Atheist
ah.
You connect the client to the server in one of two ways, either you use the Connect method, or you specify a host and a port when you instanciate it:
orVB Code:
Dim Client As New System.Net.Sockets.TcpClient Client.Connect("127.0.0.1", 30320)
VB Code:
Dim Client As New System.Net.Sockets.TcpClient("127.0.0.1", 30320)
Now, I bet this is not ascynchronous? There will be a complete freeze, until it connects. Right?Quote:
Originally Posted by Atheist
It will pause the application for a very short while, and if no connection could be established, it will throw an exception.
One problem. The codes you sent for sending data from the server to a client, does not work.Quote:
Originally Posted by Atheist
Whats happening? Have you tried stepping through the code?
Well, I re-modified your code a bit, and here's what I've got, which doesn't work. It does not arrive at the client.Quote:
Originally Posted by Atheist
Private Function Send(ByVal user As Long, ByVal data As String) As Boolean
Debug.Print("Sending")
If Clients.ContainsKey(user) = True Then
Debug.Print("User has been found")
Dim sw As System.Net.Sockets.NetworkStream = Clients.Item(user).GetStream
Debug.Print("Stream writer created and write property is " & sw.CanWrite.ToString.ToLower)
Dim d() As Byte
d = System.Text.Encoding.ASCII.GetBytes(data)
sw.Write(d, 0, d.Length)
Debug.Print("Sent")
Return True
Else
Return False
End If
End Function
Alright, I fixed that. Now, the error is inside my DoListen sub that I also modified from your own. Look at this.
Code:Public Function FindUsedIndexes() As String()
Dim Index1 As Long
Dim Temp As String = "0"
Debug.Print("Used indexes from 1 to " & Clients.Values.Count)
For Index1 = 1 To Clients.Values.Count
On Error Resume Next
If Not Clients.Item(Index1) Is Nothing Then
Temp = Temp & "#" & Index1
End If
Next
Debug.Print("Used indexes was " & Temp & "#0")
Return Split(Temp & "#0", "#")
End Function
Public Function FindFreeIndex() As Long
Dim Index1 As Long
For Index1 = 1 To 1000
GoTo StartLoop
NextIndex:
Return Index1
StartLoop:
On Error GoTo NextIndex
If Clients.Item(Index1) Is Nothing Then
FindFreeIndex = Index1
End If
Next
End Function
Private Sub DoListen()
Dim sr As IO.StreamReader
Debug.Print("Going to listen!")
Do
Try
Dim client As Net.Sockets.TcpClient = listener.AcceptTcpClient
If client.Connected = True Then
Debug.Print("A connection has just been accepted")
Ccount = FindFreeIndex()
Debug.Print("Free index found " & Ccount)
Clients.Add(Ccount, client)
Debug.Print("Accepted client added")
Dim UserList As String = "-None"
Dim ChoosenIndex As Integer = -1
Dim Index As Long
Debug.Print("Entering data collection loop")
For Index = 1 To 100
If Index = Ccount Then
User(Index).Available = 1
User(Index).RequestID = Ccount
Exit For
Else
UserList = UserList & "-" & User(Index).Name
End If
Next
Send(Ccount, "A" & Ccount & "-" & Mid(UserList, 2))
End If
For Each Cc As String In FindUsedIndexes()
If Not CLng(Cc) = 0 Then
Debug.Print("Creating stream reader " & Cc)
sr = New IO.StreamReader(Clients.Item(CLng(Cc)).GetStream) 'This line returns a System.InvalidOperationException
Debug.Print("Reading to end")
Dim dat As String = sr.ReadToEnd
Debug.Print("Splitting data")
Dim Data() As String = Split(dat, "|")
Debug.Print("It sent the data " & dat)
sr.Close()
Received(Data(1), Data(0))
End If
Next
Catch
End Try
Loop
End Sub
Private Function Send(ByVal user As Long, ByVal data As String) As Boolean
Debug.Print("Sending")
If Clients.ContainsKey(user) = True Then
Debug.Print("User has been found")
Dim sw As New IO.StreamWriter(Clients.Item(user).GetStream)
sw.Write(data)
sw.Flush()
sw.Close()
Debug.Print("Sent")
Return True
Else
Return False
End If
End Function
First off, before I even look at the code more. I just want to say that GoTo and On Error statements should not be used. If you ever end up "needing" to use them, you know youve got a bad code design. And On Error Resume Next is defenitly not recommended as it does not "handle" any errors, it just ignores them.
I found the solution for that problem, and I am now trying to create a UDP client as well, by the codes you made for the TCP client. However, it can't receive data. Please let me know what I am doing wrong here:
Code:Public Sub Connect(ByVal HostName As String, ByVal Port As Integer)
If Port = 0 Then
Err.Raise(44, "MathyProductions.Sockets.TCP.Client", "The port to connect to was not set")
End If
If HostName = vbNullString Then
Err.Raise(45, "MathyProductions.Sockets.TCP.Client", "The hostname to connect to was not set")
End If
Client.Connect(HostName, Port)
Client.BeginReceive(AddressOf DoRead, Nothing)
End Sub
Private Sub DoRead(ByVal ar As IAsyncResult)
Try
MsgBox("Read something?")
Dim EndPoint As System.Net.IPEndPoint = Nothing
Dim Bytes As [Byte]()
Bytes = Client.EndReceive(ar, EndPoint)
If Not Bytes Is Nothing Then
Dim Data As String = System.Text.Encoding.ASCII.GetString(Bytes)
If Mid(Data, 1, 2) = "ID" Then
ClientIndex = CLng(Mid(Data, 3, 1))
If Len(Mid(Data, 4)) > 0 Then
RaiseEvent ClientReceived(Mid(Data, 4))
Else
RaiseEvent ServerConnected()
End If
Else
RaiseEvent ClientReceived(Data)
End If
Else
Debug.Print("MathyProductions.Sockets.UDP.Client: Could not download data")
End If
Client.BeginReceive(AddressOf DoRead, Nothing)
Catch e As Exception
RaiseEvent ClientError(Err.Number, e.Message)
End Try
End Sub
Anyone?
You have mixed the code with alot of old VB6 code. You shouldnt do that in VB .Net.
Besides, why have you changed so much of the original code?
Well, I modified it to suit my needs better, and the TCP version of it works. Can you try building a UDP client for me as well so I could modify it too? I'll give you credits :)Quote:
Originally Posted by Atheist
Hey, i'm working on a project using a TCP server and used your code. I was looking at the client side of things and no matter how much i try, it won't send text to the server. It pops up with that error message asking if i'm sure it is connected to the server, but it is. What could possibly be wrong?
Well have you also created a server application and if so, is it running? What IP does it have? Are you sure you've set the same port numbers on both server and client?
I made a separate server app from your code and a separate client from your code. I cross tested it with a delphi server and client i made a while back. With both the delphi client and vb client it connected to the vb server fine. I also tried connecting the delphi client and vb client to the delphi server and that checked out fine too, but as soon as i use the SendData() method you supplied for the vb client it keeps returning the same error message.
For some reason the data won't go through the stream and was just wondering if you might have some ideas. I'll tinker with it more later on and see if i'm doing something wrong.
Thanks.
Atheist,Quote:
Originally Posted by Atheist
I wanted to thank you for such a great tutorial on the use of sockets.
I have a minor questions about the "register" feature.
Would it be possible to have multiple users with the same username - so I could in theory send a bulk update message to a group of people with only the updated information that affects that group of users?
As far as I know that is possible (i haven't tried it but think it happened a few times in my experience) but would be a bad idea if you want to later reference single messages and deal with disconnects of single users.
I would recommend either when the user connects they send through a message saying which group they want to join or something like that. I also have an example of a server which makes each connection from a client an instance of a ClientConnection class which could be quite useful depending on what you want to do aswell as if you want to store lots of information for each client. Let me know and i'll give you a tutorial in it if you want.
basically what I am attempting to do is push updated xml from the server to several groups of clients.there is only going to be 8 different groups and i would like to fire off XML data to each group of users by sending out 8 different messages in sequence. I am trying to limit the hammering that happens on the server when 300 people ask for the same data at the same time. I thought it would be better to have 1 server applet request the data then push said data out. I looked at xml webservices but I didn't see a way to push data to the app without continually polling the service.
I am not planning on referencing the data after its used 1 time. I would enjoy looking at the examples you have.
I tried the whole logging of more than one user with the same username using a hashtable and it doesn't seem to like it. I've provided a project called tcpServer that shows you how to deal with each connection as an object of a ClientConnection class. Let me know if you need anything explained. I guess you could add a property to the ClientConnection class as trying to group the connections. If I come up with any other ideas to solve the problem i'll let you know.
code is in vb2005.
I appreciate the help on this and I will look at the example in the morning. have a kinda silly question about all this - if I send a group based message will it send them all at once or 1 at a time till everyone is updated.
trying to achieve a burst update
Well the way the server works is it cycles through all the connected users and will send them each a message one at a time. If you put the users into groups then with a few minor changes to the server code you can cycle through just those select connected users.
Unless you are sending a huge amount of data to each client it should be quite quick. I'm not really sure if there is any other way of sending data quicker and more efficiently to the clients though . I'll have a look around and see if there are other ways of doing this later today.
the more I think about it the better the idea sounds of sending messages individually in order instead of sending a massive bulk message - that way you limit the spikes the server outbound bandwidth would see every time it sends out an update. In the code you provided on the client login
in the dataArray's that your sending to the server could you add what group you need to be in there? if a user moved to another group would it be better to remove them from the session then log them back in under the new group?Code:Private Sub OnLineReceived(ByVal client As ClientConnection, ByVal data As String)
'
' NOTE: Messages received from client in following form:
' COMMAND|PARAMETER1|PARAMETER2 etc...
' dataArray(0) = COMMAND
' dataArray(1) = PARAMETER1
' dataArray(2) = PARAMETER2
'
data = data.Substring(0, data.Length - 1)
Dim dataArray() As String
dataArray = data.Split("|")
Select Case (dataArray(0))
Case "LOGIN"
' Logs in the client - Message received from clients in following form:
' LOGIN|Username
LoginClient(client, dataArray(1))
End Select
End Sub
Didn't even think about the bandwidth spikes but yeah not bad thinking at all.
For your first question - are you talking about putting the client into a group during the login process? If so then you need to add an extra property to the ClientConnection class for a group so you can later reference that connection to that group and then simply send the group name through on the login message. Something like LOGIN|GROUPNAME|USERNAME. It seems complicated but if you need an example to show you how it would probably make it a lot easier.
For you second question - if you deal with the connections like I've described above then it would be simply a matter of sending a different kind of message to the server to change your current group. No logging off and on would be necessary at all.
I have the adding of users to groups handled already.
There are only 3 real questions left I haven't asked yet.
since the nature of this service will be the updating of client and I wont be sending much except the login information to the server.
would you recommend going with the RDM type of socket as mentioned on MSDN
http://msdn2.microsoft.com/en-us/lib...pe(VS.80).aspx
"Rdm -
Supports connectionless, message-oriented, reliably delivered messages, and preserves message boundaries in data. Rdm (Reliably Delivered Messages) messages arrive unduplicated and in order. Furthermore, the sender is notified if messages are lost. If you initialize a Socket using Rdm, you do not require a remote host connection before sending and receiving data. With Rdm, you can communicate with multiple peers. "
The second question is about the sending of the xml update message - should I pack it up into a file and send it or should I just send it as a message?
do you have an example for sending a file in sockets
3rd question is - if a client needed to send a message out to all the other clients attached to the server how would this be handled?
I've never tried RDM but it seems pretty much like the UDP protocol which even if it does notify you about missing packets will still require you to resend the updates which i'm guessing is more hassle than it's worth.
If you sending updates as a whole file to replace a previous one then i recommend sending the file instead of text - search the forum i remember there being a thread about it somewhere here on vbforums.
For broadcasting messages make the client send: "BROADCAST|Message" and handle it like so in the OnlineReceived event:
Code:Private Sub OnLineReceived(ByVal client As ClientConnection, ByVal data As String)
'
' CODE
'
Select Case (dataArray(0))
Case "LOGIN"
' Logs in the client - Message received from clients in following form:
' LOGIN|Username|Groupname
LoginClient(client, dataArray(1), dataArray(2))
Case "BROADCAST"
' Broadcast message to all clients - Message received from clients
' in following form: BROADCAST|MESSAGE
Broadcast(dataArray(1))
End Select
'
' CODE
'
End Sub
very nice, thank you for the help on this.
have a good one and enjoy the holidays.
Glad I could help. Feel free to ask any other questions you have.
Thanks and have a great holiday too.
How can i let the server broadcast listboxs, my clients can t chat or anything but they can jump to 4 listboxes, A B C D, so if a person that is in room(listbox) A and moves to roomD (listbox) then i want the other clients to see him olso in room D and not more in A. Ty :).
^^vampire^^
on the program load I initially pull the data needed from the database - when you mentioned the broadcast command I started rethinking the idea of sending an update xml file and started thinking about just sending the updated data across as a line of text in a broadcast. have you ever taken a line of text from a broadcast and used it to update an array?
next how could you make the updated information conditional on a parameter inside the line of text.
say the group number sent isnt the group number your associated with, could you set it so it would ignore any info thats not needed?
think if I handle things this way it should be much more efficient and update the program with only the info needed in almost real-time
1 - Can't say i really have but what info is going to be in that array and what is the array for exactly?
2 - For your second question do you mean that you're trying to separate which information goes to which group [eg] if you receive a message from a client BROADCAST|GROUP|MESSAGE that the message is only broadcast to the members of that group?
Maxim -
What kind of program are you trying to make?
Can the client only be in one listbox at a time? If so then are you only using the 4 listboxes on the server form to keep track of the clients?
If so then it's just a case of adding a property to the ClientConnection class for the group they will be in and when changing groups receiving a message from the client saying they want to change group [eg] CHANGEGROUP|NEWGROUP. When this happens before changing the clients group property you check what the current group is, remove them from that listbox, send a message to the other clients letting them know he left that group, change the clients group property, and then let the clients know that he has joined the new group.
If you need a coding example let me know although it might be somewhat lengthy.