Click here to Skip to main content
15,892,005 members
Articles / Programming Languages / C#
Article

Real Time TCP/IP using C#

Rate me:
Please Sign up or sign in to vote.
3.09/5 (20 votes)
12 Jan 20022 min read 579.5K   14.5K   108   42
This sample shows the communication techniques between a client and a server application using a Socket class on each side.

Introduction

The Real time Application is a sample that shows the communication techniques between a client (TcpClient) and a server (TcpServer) application using Socket class on each side. The project also demonstrates how to using listview control in the real time project.

     

  • TcpServer.exe showing the use of TCP socket communication in a separate thread. Multiple instances of TcpClient can talk to the same instance of TcpServer.
  • TcpClient.exe also uses a separate thread to read data from Socket then update the listview control in a form.

The flow of logic

  1. TcpServer listens on port 8002 and spawns a thread to waiting clients to connect.
    Hashtable socketHolder = new Hashtable();      
    Hashtable threadHolder = new Hashtable();      
    
    public Form1()   
    { 
        // Required for Windows Form Designer support           
        //         
        InitializeComponent();    
    
        tcpLsn = new TcpListener(8002);          
        tcpLsn.Start();           
        // tcpLsn.LocalEndpoint may have a bug, it only show 0.0.0.0:8002      
        stpanel.Text = "Listen at: " + tcpLsn.LocalEndpoint.ToString();        
        Thread tcpThd = new Thread(new ThreadStart(WaitingForClient));         
        threadHolder.Add(connectId, tcpThd);     
        tcpThd.Start() ;          
    
        ...
    } 
  2. TcpClient connect to TcpSrv and sends Client information data packet to TcpServer then spawns a thread, which waits to receive data through the Socket.
    private void menuConn_Click(object sender, System.EventArgs e)
    { 
        ConnectDlg myDlg = new ConnectDlg();     
        myDlg.ShowDialog(this);   
        if( myDlg.DialogResult==DialogResult.OK) 
        {          
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream,    
                ProtocolType.Tcp );          
    
            IPAddress hostadd = IPAddress.Parse(myDlg.IpAdd); 
            int port=Int32.Parse(myDlg.PortNum);              
            IPEndPoint EPhost = new IPEndPoint(hostadd, port);
    
            Try  
            {    
                s.Connect(EPhost);           
    
                if (s.Connected)             
                {             
                    Byte[] bBuf;           
                    string buf;            
                    buf = String.Format("{0}:{1}", myDlg.UserName,       
                        myDlg.PassWord);       
                    bBuf=ASCII.GetBytes(buf);             
                    s.Send(bBuf, 0 , bBuf.Length,0);      
                    t = new Thread(new ThreadStart(StartRecieve));       
                    t.Start();             
                    sbar.Text="Ready to recieve data";    
                }             
            }    
            catch (Exception e1)
            {    
                MessageBox.Show(e1.ToString());             
            }    
        }          
    } 
    private void StartRecieve()     
    { 
        MethodInvoker miv = new MethodInvoker(this.UpdateListView);           
        while (true)              
        {          
            Byte[] receive = new Byte[38] ;    
            Try  
            {    
                string tmp=null;             
                // Receive will block until data coming     
                // ret is 0 or Exception happen when Socket connection is  
                // broken     
                int ret = s.Receive(receive, receive.Length, 0);           
                if (ret>0)    
                {             
                    tmp = System.Text.Encoding.ASCII.GetString(receive); 
                    if(tmp.Length > 0)     
                    {       
                        isu.symbol= Mid(tmp, 0, 4);     
                        isu.bid = Mid(tmp, 4, 5);       
                        isu.offer = Mid(tmp, 9, 5);     
                        isu.volume = Mid(tmp, 16, tmp.Length-16);      
    
                        this.BeginInvoke(miv);          
                        Thread.Sleep(300);              
                        // block until finish the
                        // UpdateListview’s job JobDone.WaitOne(); 
                    }       
                }             
            }    
            catch (Exception e) 
            {    
                if( !s.Connected )           
                {             
                    break;  
                }             
            }    
        }          
        t.Abort(); 
    } 
  3. TcpServer accepts the connection and saves the socket instance into a Hashtable instance then spawns a thread to handle the socket communication and show the client information in the top listview control.
    public void WaitingForClient()                                                
    {                                                                             
          while(true)                                                             
          {                                                                       
                // Accept will block until someone connects                       
                Socket sckt = tcpLsn.AcceptSocket();                              
                if (connectId < 10000)                                            
                      Interlocked.Increment(ref connectId);                       
                Else                                                              
                      connectId = 1;                                              
                if (socketHolder.Count < MaxConnected )                           
                {                                                                 
                      while (socketHolder.Contains(connectId) )                   
                      {                                                           
                            Interlocked.Increment(ref connectId);                 
                      }                                                           
                      // it is used to keep connected Sockets                     
                      socketHolder.Add(connectId, sckt);                          
                      Thread td = new Thread(new ThreadStart(ReadSocket));        
                      // it is used to keep the active thread                     
                      threadHolder.Add(connectId, td);                            
                      td.Start();                                                 
                }                                                                 
          }                                                                       
    }                                                                             
    // follow function handle the communication from the clients and close the    
    // socket and the thread when the socket connection is down                   
    public void ReadSocket()                                                      
    {                                                                             
          // the connectId is keeping changed with new connection added. it can't 
          // be used to keep the real connectId, the local variable realId will   
          // keep the value when the thread started.                              
          long realId = connectId;                                                
          int ind=-1;                                                             
          Socket s = (Socket)socketHolder[realId];                                
          while (true)                                                            
          {                                                                       
                if(s.Connected)                                                   
                {                                                                 
                      Byte[] receive = new Byte[37] ;                             
                      Try                                                         
                      {                                                           
                            // Receive will block until data coming               
                            // ret is 0 or Exception happen when Socket connection
                            // is broken                                          
                            int ret=s.Receive(receive,receive.Length,0);          
                            if (ret>0)                                            
                            {                                                     
                                  string tmp = null;                              
                                tmp=System.Text.Encoding.ASCII.GetString(receive);
                                  if(tmp.Length > 0)                              
                                  {                                               
                                        DateTime now1=DateTime.Now;               
                                        String strDate;                           
                                        strDate = now1.ToShortDateString() + " "  
                                                    + now1.ToLongTimeString();    
                                                                                  
                                        ListViewItem newItem = new ListViewItem();
                                        string[] strArry=tmp.Split(':');          
                                        int code = checkUserInfo(strArry[0]);     
                                        if(code==2)                               
                                        {                                         
                                              userHolder.Add(realId, strArry[0]); 
                                              newItem.SubItems.Add(strArry[0]);   
                                              newItem.ImageIndex = 0;             
                                              newItem.SubItems.Add(strDate);      
                                              this.listView2.Items.Add(newItem);  
                                        ind=this.listView2.Items.IndexOf(newItem);
                                        }                                         
                                        else if( code==1)                         
                                                                                  
                                              ……………                               
                                  }                                               
                            }                                                     
                            else                                                  
                            {                                                     
                                  this.listView2.Items[ind].ImageIndex=1;         
                                  keepUser=false;                                 
                                  break;                                          
                            }                                                     
                      }                                                           
                      catch (Exception e)                                         
                      {                                                           
                            if( !s.Connected )                                    
                            {                                                     
                                  this.listView2.Items[ind].ImageIndex=1;         
                                  keepUser=false;                                 
                                  break;                                          
                            }                                                     
                      }                                                           
                }                                                                 
          }                                                                       
          CloseTheThread(realId);                                                 
    }                                                                             
    private void CloseTheThread(long realId)                                      
    {                                                                             
          socketHolder.Remove(realId);                                            
          if(!keepUser) userHolder.Remove(realId);                                
          Thread thd = (Thread)threadHolder[realId];                              
          threadHolder.Remove(realId);                                            
          thd.Abort();                                                            
    } 
  4. Click Load Data Menu to spawns a thread to load the information from a file then sends the information to all the clients that were connected to the TcpServer and update its own listview.

    In both TcpServer and TcpClient, they get the data from a working thread, and then update the Listview control in the Main thread. Here use the MethodInvoker to work it out.

    public void LoadThread()        
    { 
        MethodInvoker mi = new MethodInvoker(this.UpdateListView);             
        string tmp = null;        
        StreamReader sr = File.OpenText("Issue.txt");           
        while((tmp = sr.ReadLine()) !=null )     
        {          
            if (tmp =="")       
                break;        
            SendDataToAllClient(tmp);          
    
            isu.symbol= Mid(tmp, 0, 4);        
            isu.bid = Mid(tmp, 4, 5);          
            isu.offer = Mid(tmp, 9, 5);        
            isu.volume = Mid(tmp, 16, tmp.Length-16);         
    
            this.BeginInvoke(mi);              
            Thread.Sleep(200);  
    
            JobDone.WaitOne();  
        }          
        sr.Close();
        fThd.Abort();             
    } 
    private void SendDataToAllClient(string str)   
    { 
        foreach (Socket s in socketHolder.Values)
        {          
            if(s.Connected)     
            {    
                Byte[] byteDateLine=ASCII.GetBytes(str.ToCharArray());     
                s.Send(byteDateLine, byteDateLine.Length, 0);              
            }    
        }          
    }   

    Following function demonstrate how to dynamically set BackColor and Forecolor properties of the Listview in TcpClient.

    private void UpdateListView()    
    { 
        int ind=-1;
        for (int i=0; i<this.listView1.Items.Count;i++)         
        {          
            if (this.listView1.Items[i].Text == isu.symbol.ToString())       
            {    
                ind=i;        
                break;        
            }    
        }          
        if (ind == -1)            
        {          
            ListViewItem newItem new ListViewItem(isu.symbol.ToString());    
            newItem.SubItems.Add(isu.bid);     
            newItem.SubItems.Add(isu.offer);   
            newItem.SubItems.Add(isu.volume);  
    
            this.listView1.Items.Add(newItem); 
            int i=this.listView1.Items.IndexOf(newItem);      
            setRowColor(i, System.Drawing.Color.FromArgb(255, 255, 175));    
            setColColorHL(i, 0, System.Drawing.Color.FromArgb(128,0,0));     
            setColColorHL(i, 1, System.Drawing.Color.FromArgb(128,0,0));     
            this.listView1.Update();           
            Thread.Sleep(300);  
            setColColor(i, 0, System.Drawing.Color.FromArgb(255, 255,175));  
            setColColor(i, 1, System.Drawing.Color.FromArgb(255, 255, 175));  
        }          
        else       
        {          
            this.listView1.Items[ind].Text = isu.symbol.ToString();          
            this.listView1.Items[ind].SubItems[1].Text = (isu.bid);          
            this.listView1.Items[ind].SubItems[2].Text = (isu.offer);        
            this.listView1.Items[ind].SubItems[3].Text = (isu.volume);       
            setColColorHL(ind, 0, System.Drawing.Color.FromArgb(128,0,0));   
            setColColorHL(ind, 1, System.Drawing.Color.FromArgb(128,0,0));   
            this.listView1.Update();           
            Thread.Sleep(300);  
            setColColor(ind, 0, System.Drawing.Color.FromArgb(255,255,175)); 
            setColColor(ind, 1, System.Drawing.Color.FromArgb(255,255,175)); 
        }          
        JobDone.Set();            
    } 
    
    private void setRowColor(int rowNum, Color colr )             
    { 
        for (int i=0; i<this.listView1.Items[rowNum].SubItems.Count;i++)       
            if (rowNum%2 !=0)   
                this.listView1.Items[rowNum].SubItems[i].BackColor = colr;     
    } 
    
    private void setColColor(int rowNum, int colNum, Color colr ) 
    { 
        if (rowNum%2 !=0)         
            this.listView1.Items[rowNum].SubItems[colNum].BackColor=colr;     
        else       
            this.listView1.Items[rowNum].SubItems[colNum].BackColor =        
            System.Drawing.Color.FromArgb(248, 248,248); 
        if (colNum==0)            
        {          
            this.listView1.Items[rowNum].SubItems[colNum].ForeColor =        
                System.Drawing.Color.FromArgb(128, 0, 64);  
            this.listView1.Items[rowNum].SubItems[colNum].BackColor =        
                System.Drawing.Color.FromArgb(197, 197, 182);
        }                                                                      
        else                                                                   
            this.listView1.Items[rowNum].SubItems[colNum].ForeColor =        
            System.Drawing.Color.FromArgb(20, 20,20);    
    }                                                                            
    
    private void setColColorHL(int rowNum, int colNum, Color colr )              
    {                                                                            
        this.listView1.Items[rowNum].SubItems[colNum].BackColor = colr;        
        this.listView1.Items[rowNum].SubItems[colNum].ForeColor =              
            System.Drawing.Color.FromArgb(255,255,255);  
    } 

Steps to run the sample:

  1. Run TcpServer.exe on machine A.

  2. Run TcpClient.exe once or more either on machine A or machine B.

  3. On the TcpClient side, Click Menu connect; enter the server machine name where TcpServer is running. Enter user name and password in the edit box. Click Ok.

  4. When you see the client in the TcpServer top listview, click Load Data Menu on the TcpServer, and then you will see the real time data in TcpServer and TcpClient.

    Note:  Make sure that the Data file, Issue.txt, is in the same directory as TcpSvr.exe.

If you have any comments, I would love to hear about it. You can reach me at Jibin Pan.

Jibin Pan is VC++, C programmer at Interactive Edge Corp. Xtend Communications Corp. MoneyLine Corp in New York City since 1994 and has Master degree at computer science.

History

13 Jan 2002 - updated source.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: hi&#65292;It is a bug Pin
Anonymous10-Mar-03 5:56
Anonymous10-Mar-03 5:56 
GeneralHi, I have read your thread, and....a prb Pin
steve_cluj20-Jun-02 19:57
steve_cluj20-Jun-02 19:57 
GeneralRe: Hi, I have read your thread, and....a prb Pin
Alex Korchemniy23-Dec-02 13:56
Alex Korchemniy23-Dec-02 13:56 
GeneralTerrible! Pin
Ian Griffiths14-Jan-02 10:32
Ian Griffiths14-Jan-02 10:32 
GeneralRe: Terrible! Pin
22-Jan-02 3:02
suss22-Jan-02 3:02 
GeneralRe: Terrible! Pin
Ian Griffiths22-Jan-02 5:26
Ian Griffiths22-Jan-02 5:26 
GeneralRe: Terrible! Pin
22-Jan-02 9:43
suss22-Jan-02 9:43 
GeneralRe: Terrible! Pin
Ian Griffiths22-Jan-02 23:27
Ian Griffiths22-Jan-02 23:27 
-- Beacause WaitingClient using while-loop to handle the new
-- connection, After accepted new connection
-- and processed, then can accept another new coming connection.
-- The ReadSocket thread only using conectId when it started.
-- After the thread is started, then WaitingClient can start to
-- accept new connection. The satuation what you concern will be
-- not happened.

Here is a sequence of events that will cause exactly the problem I describe to occur:

(1) A client connection request comes in
(2) In WaitingForClient, tcpLsn.AcceptSocket returns.
(3) WaitingForClient increments connectId, to (say) 2
(4) Another client connection request comes in. (This won't be handled yet, because - it will only get handled when the main thread loops back round. However, the request will sit in the OS's queue.)
(5) WaitingForClient checks ID 2 is not in use right now
(6) WaitingForClient calls Thread.Start to run the new thread
(7) The OS decides that although it could start running the new thread, it realises that the main thread hasn't used up its current quanta and so it allows the main thread to carry on running.
(8) The main thread loops round, calls tcpLsn.AcceptSocket which returns immediately because of the second client connection request that came in earlier.
(9) The main thread increments connectId to 3.
(10) The main thread kicks off another thread
(11) The main thread loops round again, and this time it blocks in AcceptSocket
(12) The OS decides to give the next thread a go. It starts up, reads connectId and gets the value 3. So it sets its realId value to 3.
(13) The OS scheduler switches to the other thread that got created. It starts up, reads connectId and also gets the value 3.


So there are now two threads running, both of which think their ID is 3. Surely this is a bug?

Do you agree with this analysis? As far as I can tell, this is not only possible, it's positively likely to happen. It's not unusual for a single listening socket to have multiple connection requests queue up faster than they are handled. This is likely to happen when everyone connects to your server first thing in the morning. And the scheduler will very likely behave in the manner I describe - it tries to minimise the number of thread switches, so the main thread will almost certainly carry on running after it has called Thread.Start, and the threads thus launched won't actually begin their work until the main thread runs out of things to do.

There are variations on the theme. If everything happens the same as before up to 8, consider the following alternative sequence after that point:

(9) The OS switches to the other thread.
(10) The other thread starts to read connectId. Remember that reading from a long is not an atomic operation.
(11) The OS decides to switch back to the main thread. The main thread calls Interlocked.Increment to increment connectId. This will succeed - it has no way of knowing that there was another thread trying to read from connectId at the time.
(12) The OS switches back to the other thread, which carries on doing its read.


So the launched thread's value of realId will be corrupt. It has been interrupted by a thread switch. (That can definitely happen - read the CLR spec.) A 64 bit read operation which has been split in this way has an undefined value, so realId could be absolutely anything.

In practice this second scenario is not going to happen very often. But that's no excuse - threading hazards that don't happen often still happen, they just happen once every 6 months or so. In practice I would be surprised if it ever caused a real problem, simply because your value never goes above 10,000 so the top half of the long is always 0, so on current architectures you will happen to get the right value anyway. So you are relying on undocumented behaviour. But what I still don't understand is why on earth you chose to make this a long! Just make it an int, and this particular problem goes away.


However, the other problem - the problem of 2 different threads both getting the same ID won't be solved by changing to an int. That's a more fundamental problem with your design, and require more radical changes.



-- Please goto How Do I ... Common tasks, Windows Forms: and
-- Manipulate a control from a background thread?
-- The article: Making procedure calls across thread boundaries
-- and sample code that "demonstrates how to create a background
-- thread that uses a MethodInvoker to update a ProgressBar
-- control at regular intervals:"

I think we're talking about different parts of your code here. You're talking about StartReceive, where it passes a MethodInvoker to this.BeginInvoke, yes? That code is correct, although I think it would be better to call this.Invoke. (That will still marshal the call to the correct thread, as required. It will also block until UpdateListView has finished, which is what your comments say you want.)

That's not the code that I'm talking about. I'm talking about the code in ReadSocket.

In ReadSocket, you have the following lines of code:

this.listView2.Items.Add(newItem);
ind=this.listView2.Items.IndexOf(newItem);

and also

this.listView2.Items[ind].ImageIndex=1;

The ReadSocket method is not running on the UI thread. The reason I say that is that you also have this line of code:

Thread td = new Thread(new ThreadStart(ReadSocket));

So, the ReadSocket method doesn't run on the main thread, but it does call methods and use properties on listView2. This is wrong. You should be using this.Invoke or this.BeginInvoke here just like you do in your StartReceive method.


So, I wasn't complaining about StartReceive, or UpdateListView. These use the correct technique. I was complaining about the ReadSocket method. This is why I said in my original message "The ReadSocket method updates a ListView directly. This is illegal"



-- I like this comments. But This program just a small demo, you
-- can say it is a toy. You can't expect it to solve all the
-- problems in the TCP/IP world.

No, but I think it's somewhat misleading. You have given this article the title "Real Time TCP/IP using C#", and your example is all based around stock prices. There is nothing about this code that can be described as "Real Time" - it makes no guarantees about timing as it can be brought down by a single client. It is very definitely not a sound basis for a real time stock price update system.

So not only does it not solve all the problems in the TCP/IP world, I don't believe it even solves the problem that the title suggests it solves.

--
Ian Griffiths
DevelopMentor
GeneralRe: Terrible! Pin
25-Jan-02 6:45
suss25-Jan-02 6:45 
GeneralRe: Terrible! Pin
Ian Griffiths28-Jan-02 4:59
Ian Griffiths28-Jan-02 4:59 
GeneralRe: Terrible! Pin
titusb#10-Dec-05 8:59
titusb#10-Dec-05 8:59 
GeneralRe: Terrible! Pin
Odis Wooten20-Mar-02 18:15
Odis Wooten20-Mar-02 18:15 
GeneralRe: Building sockets apps (was Terrible!) Pin
Ian Griffiths20-Mar-02 23:31
Ian Griffiths20-Mar-02 23:31 
GeneralRe: Building sockets apps (was Terrible!) Pin
Christian Uhlig7-Apr-04 22:00
Christian Uhlig7-Apr-04 22:00 
GeneralNot a bug Pin
Matthias Mann28-Oct-01 7:18
Matthias Mann28-Oct-01 7:18 
QuestionThread safe? Pin
Nemanja Trifunovic5-Oct-01 8:32
Nemanja Trifunovic5-Oct-01 8:32 
GeneralPart of Source code is missing Pin
5-Oct-01 4:37
suss5-Oct-01 4:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.