Link to home
Start Free TrialLog in
Avatar of revo1059
revo1059Flag for United States of America

asked on

how to use sockets and VB.net to send commands from a client to a server

I have a server that we runs a program.  What I want to do is restart that program if it has crashed, but I need to do it remotely.  I have searched all over and found a couple of examples.  I even got it working to the point where I have 2 programs.. a server and a client.  The server listens and the client sends a message.  When the server gets that message,the server puts a message up saying data received etc.  Part of the problem I am having is the server only received one time, then freezes and I have to end it using visual studio.

In the end what I want is to have A button on the client that says "restart".  That send the command to the server, the server uses shell("mybatchfile.bat") or whatever... and then continues to listen for the next "restart" command

I am using Visual studio 2008 in Visual basic.NET
'-------------------- server code ------------------------------
Imports System.Net.Sockets
Imports System.Text
Class TCPSrv
 
    Function Main()
        ' Must listen on correct port- must be same as port client wants to connect on.
 
        Const portNumber As Integer = 8000
        Dim tcpListener As New TcpListener(portNumber)
 
 
        tcpListener.Start()
        Debug.WriteLine("Waiting for connection...")
        Try
            'Accept the pending client connection and return 
            'a TcpClient initialized for communication. 
            Dim tcpClient As TcpClient = tcpListener.AcceptTcpClient()
            Debug.WriteLine("command sent.")
            ' Get the stream
            Dim networkStream As NetworkStream = tcpClient.GetStream()
            ' Read the stream into a byte array
            Dim bytes(tcpClient.ReceiveBufferSize) As Byte
            networkStream.Read(bytes, 0, CInt(tcpClient.ReceiveBufferSize))
            ' Return the data received from the client to the console.
            Dim clientdata As String = Encoding.ASCII.GetString(bytes)
            MsgBox(clientdata)
            If clientdata.CompareTo("notepad") Then
 
                Shell("c:\windows\notepad.exe")
            End If
            Dim responseString As String = "Notepad started."
            Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(responseString)
            networkStream.Write(sendBytes, 0, sendBytes.Length)
 
            'MsgBox("Message Sent /> : " + responseString)
            'Any communication with the remote client using the TcpClient can go here.
            'Close TcpListener and TcpClient.
            tcpClient.Close()
            tcpListener.Stop()
 
        Catch e As Exception
            MsgBox(e.ToString())
 
        End Try
 
    End Function
 
 
    Private Sub TCPSrv_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Shell("c:\windows\notepad.exe")
        Me.Show()
    End Sub
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bnStartListen.Click
        My.Settings.StopClicked = False
        bnStartListen.Text = "Started"
        MsgBox("Server Started")
        Main()
    End Sub
 
    Private Sub bnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bnStop.Click
        My.Settings.StopClicked = True
        Main()
    End Sub
End Class
 
'-----------------------------CLIENT CODE ----------------------------
Imports System.Net.Sockets
Imports System.Text
 
Public Class frmServerClient
 
    Private Sub frmServerClient_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.Show()
        'MsgBox("sending data")
    End Sub
 
    Function Main()
 
        Dim tcpClient As New System.Net.Sockets.TcpClient()
        tcpClient.Connect("127.0.0.1", 8000)
        Dim networkStream As NetworkStream = tcpClient.GetStream()
        If networkStream.CanWrite And networkStream.CanRead Then
            ' Do a simple write.
            Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes("notepad")
            networkStream.Write(sendBytes, 0, sendBytes.Length)
            ' Read the NetworkStream into a byte buffer.
            Dim bytes(tcpClient.ReceiveBufferSize) As Byte
            networkStream.Read(bytes, 0, CInt(tcpClient.ReceiveBufferSize))
            ' Output the data received from the host to the console.
            Dim returndata As String = Encoding.ASCII.GetString(bytes)
            MsgBox("Host returned: " + returndata)
        Else
            If Not networkStream.CanRead Then
                MsgBox("cannot not write data to this stream")
                tcpClient.Close()
 
 
            Else
                If Not networkStream.CanWrite Then
                    MsgBox("cannot read data from this stream")
                    tcpClient.Close()
 
 
                End If
            End If
        End If
 
    End Function
 
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        MsgBox("sending data")
        Main()
    End Sub
End Class

Open in new window

Avatar of SStory
SStory
Flag of United States of America image

It looks like to me that you are reading one time and then closing.
ASKER CERTIFIED SOLUTION
Avatar of SStory
SStory
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of revo1059

ASKER

I have been playing with it for a few days now.  I have gotten it to do what I want, however, after the first command is sent, the program uses 100% of the cpu.  I have not been able to find out why
Here is the new code I am using


I separated the listener so it starts the command, and then listens again.  like I said, the problem I think the problem I am having is that it is not closing its thread.

anyway, any code examples would be very helpful
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
 
Public Class MainClass
    Shared Listener As New TcpListener(11000)
    Shared Stream As NetworkStream
    Dim listnerStarted As Boolean
    Public Shared Sub AcceptData()
        Try
            Console.WriteLine("TCP Server Waiting for a connection...")
            Dim Client As TcpClient = Listener.AcceptTcpClient()
            Console.WriteLine("Connection accepted.")
            Console.WriteLine(New String("-", 40))
            Console.WriteLine()
            Stream = Client.GetStream()
            Dim ReceiveThread As New Thread(AddressOf ReceiveData)
            ReceiveThread.IsBackground = True
            ReceiveThread.Start()
            Dim w As New BinaryWriter(Stream)
 
            Dim Text As String
 
            Text = Console.ReadLine()
            w.Write(Text)
 
            Client.Close()
 
        Catch Err As Exception
            Debug.WriteLine(Err.ToString())
        End Try
    End Sub
    Public Shared Sub Main()
        'Dim listnerStarted As Boolean
 
        Listener.Start()
 
        AcceptData()
 
    End Sub
 
    Private Shared Sub ReceiveData()
        Dim r As New BinaryReader(Stream)
 
        Do
            If Stream.DataAvailable Then
                If r.ReadString = "notepad" Then
                    Shell("c:\windows\notepad.exe")
                    'ProcessThread.Abort()
                    AcceptData()
                End If
                Debug.WriteLine("RECEIVED: " + r.ReadString())
            End If
        Loop
 
 
    End Sub
 
    Private Sub bnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bnStart.Click
        Main()
 
    End Sub
 
    Private Sub MainClass_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 
    End Sub
End Class

Open in new window

After telling you about the demo, I downloaded both and watched them. It an indepth how to on Client Server sockets using TCP/IP.  They even build a chat client.  You should watch it and learn.  You may see a lot of things you haven't considered.   Sounds like a deadlock or threading issue of some sort.

I'd just check out this tutorial.  The guy doing it has been famous in the VB community for years...from the VBPJ Magazine days. It is well done.
The problem is, I am not trying to build a chat program.  I want to be able to launch a batch file on a remote server from a vb application that I am writing. I will watch the videos when I have time.
It doesn't matter what you want to do.  The basic operation is the same.  You will be sending bytes from point A to point B.  The first part of the video is about that...in building a simple telnet client.  You can use this with some special bytes to say--this is a command--followed by the text for the batch to be launched. The listener could look for that and react and actually run the batch file.

Does this make sense?
Yes it does,  I will download the source and watch the videos when I get home from work today.  Thank you for all your help.  If I get stuck, would you be willing to help me with the code?
I will help you with what I can. That's what EE is about.  None of us know it all. My point is that I started doing something similar to what you ware wanting to do.  You can pass anything you want to...but in the end it is just bytes.  If you wanted to make a class that stored item1, item2,item3
item1 being MessageType which is an integer or byte.  You could output a delimited string that had parts on one end (converting from string to bytes). Then on the other end do the opposite.. This means you can send anything you can come up with as Text if you can encode it to bytes and decode it on the other end.  In that manner it is all basically a telnet type deal.  Send stuff, receive stuff...do something with the stuff.

POP3 has a protocol (a  language)
SMTP has another protocol (a language)  
You transmit
HELO SOMEMACHINE.SOMEWHERE.COM to the server (just bytes)
it comes back with a response.

Everything works this way.  The protocol just tells you what is expected and how you have to talk to the server and what responses to expect.  You can make your own for sure.  Underlying it all...is transferring and receiving  a series of bytes.

Example:

if you defined messages types as
typTEXT = 0
typCMD=1
.
.
.
typN=N

and you wanted to send a command to be executed on the server.

then the client could take it and put it in a delimited string
1|mybatch.bat

Call an encoder to encode that to bytes, then transmit it.
On the other end when you get messages, see what that first field is, by getting all bytes,  converting to a string and then splitting to field1 and seeing if it is a 0, 1, 2, .... or N
React accordingly.

If it is a 1, we know a command follows so we take field2 and execute it on the machine.
system.diagnostic.process.start(whatevercommand)

Make sense?
I am trying the source code from the video.  I can start the server, and start the client, when I put my computers IP address and click connect, I get an error:

System.Net.Sockets.SocketException was unhandled
  ErrorCode=11001
  Message="No such host is known"
  NativeErrorCode=11001
  Source="System"
  StackTrace:
       at System.Net.Dns.GetAddrInfo(String name)
       at System.Net.Dns.InternalGetHostByAddress(IPAddress address, Boolean includeIPv6, Boolean throwOnFailure)
       at System.Net.Dns.GetHostEntry(String hostNameOrAddress)
       at SocketsClient.Form1.btnConnect_Click(Object sender, EventArgs e) in D:\downloads\SocketsClient\SocketsClient\Form1.vb:line 23
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()
       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()
       at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)
       at SocketsClient.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:line 81
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you, that did work.  This server is very cool, but it is above and beyond what I need.  I have tried very hard to look through the code and find where the text comes in so that I can put an if/then statement on it.  I have not been able to find that code.  If i find that, I think I can make the rest work.  
Ok, I searched harder and found what I was looking for.  I tried to put a simple command in there to get cmd to run (just for testing) but it didnt work.  The mesgbox displays test, but cmd does not run. If you can help me with this last question, I will be very grateful

if you have the source, this is in chatserver.vb under the Private Members Region

thank you so much for all your help so far.
Private Sub _broadcast(ByVal buffer As Byte(), ByVal sender As ClientContext, ByVal Echo As Boolean)
        '-- By locking the buffer, we make sure nobody can read it until we've put it in all the queues
        Dim thecommand As String
        theCommand = System.Text.ASCIIEncoding.ASCII.GetString(buffer)
        MsgBox(thecommand)
        If thecommand.Equals("test") Then
            Shell("c:\windows\system32\cmd.exe")
        End If
        '-- everything that was here before
        'SyncLock buffer
        '-- Add this data to each client's queue. This will be read and sent in _processQueues()
        'For Each client As ClientContext In clients
        'If client IsNot sender Then
        'client.SendQueue.Enqueue(buffer)
        'End If
        'Next
        'If Echo Then
        ' sender.SendQueue.Enqueue(buffer)
        ' End If
        ' End SyncLock
    End Sub

Open in new window

OK. Wrong place. Broadcast is sending the message out.  You should put it back the way that it was.

The source uses multithreading and async programming.  In the client source from the demo.  Line 66 (form1.vb) is where data is received into a data variable.

You should put all other code back like it was.

To test, using Carl's example, do the above and then inside of btnSend_Click
Change SendText value to some fake command like "DOTHIS"

Back after Line 66, process the incoming string.  It is bit more complicated as if you had two clients and a server running you'd want to make sure it didn't process this on the sending client as well.

The rest is up to your imagination in packing your string with useful information that you can separate back out into parts that are logical....a delimited string works well.

HTH
I made lots of changes.  I took out all those boxes on the main form, and made them into settings instead (like my.setting.[settingname] ) I am going to use a xml file to let the user select the server at the start.  Each server will have a different port, and a password at some point.  So that being the case, do I need to broadcast the message sent because there is only one person connected to each server.  
Let me explain a little more.  There is a potential for there to be more than one of these servers that I want to control. They are game servers, and you an run more than one on a given computer.   When the server (the program I am writing ) starts up, they are going to choose from a dropdown list, which game server they want to monitor.  I set the max connections to 1 in the code, that is why I do not need to broadcast.  Above my previous post, you said that in Form1.vb I should set the text of the command.  That is fine... I am going to have a Button that sais "Restart" or something like that and maybe a few others.  if not in that sub Broadcast, where on the server side do I read the command "restart" and then launch my file?
Just look for the place where it is decoded from bytes to text. At that point it is text.  Do whatever you want to with it then.

You could have multiples servers, or you could have one server that determined from a game identifier, which game the message was for and handled all games. This is all up to you.  The point is the basic idea is to pass a delimited text string on one end and get it back out as the same on the other end. string.Split("|") to get the parts back if "|" is your delimiter and then use it to do whatever you want to.

You are always sending and receiving.  Broadcast on a chat app is to everyone connected to the server.  I would assume you would broadcast from your server to all clients listening to it.  The clients can ignore the message if you add some ID to it.
remember I am going to have one of these listening servers for each one of my game servers.  The client will connect to the server listener and push a button that says restart.  That sends the word "restart" to the server and it launches whatever command the user has setup.

I have still not found anywhere but inside that _broadcast sub where I can read what command has been sent from the client
I originally mentioned this tutorial because I thought it would give you the techniques to do whatever you wanted to.  I think it does.  In any game some machine has to be the "controller" or "server" and the other(s) is/are the client(s).  No matter what it is still just sending and receiving, and responding.  The techniques for sending data and receiving asynchronously as well as the server piece to receive  are there.  
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Like I said above, I have put the code for executing the file that I want in the _broadcast function

        Dim str As String
        Dim enc As New System.Text.ASCIIEncoding()
        str = enc.GetString(buffer)

        MsgBox(str)
        If (str.Equals("restart")) Then
            Try
                Shell("c:\windows\notepad.exe")
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        End If

No matter what, the command does not start if I do it this way.  If i put 'Shell("c:\windows\notepad.exe")' outside the if, it does work.  I have read so many articles about comparing strings, but nothing I do works.

I am not a programmer by nature.  The examples you gave me do have parts of what I need, but to me they are to complicated and I don't understand the different parts well enough to be able to pull out just the parts I need.  I do appreciate all your help, and just need this one last part to work the way I want.

Any more help you can give would be appreciated
I have an update.  I don't know why this works but it does.  Instead of using if str = "restart" .... I used

if str.contains("restart") then ....

No clue why it works but it does.  

Once again,  thank you SStory for all your help and extra effort
Probably there are some extraneous chars behind or in front of the word.

Probably str.trim="restart" would also work