Click here to Skip to main content
15,892,059 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

 
GeneralProblems in ReadSocket() method Pin
eyasso19-May-03 3:52
eyasso19-May-03 3:52 
Generalhi&#65292;It is a bug Pin
JiangHaiLong6-Mar-03 19:13
JiangHaiLong6-Mar-03 19:13 
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 
I agree that on just one of the points I was wrong, but on all the others I stand by my opinion that this is a bad example. It has several programming errors, some unnecessarily complex idioms, and, crucially, fails to meet the basic requirements for an application; it functions as a toy application but it is fundamentally flawed as production code.


I concede that I failed to take into account that everything is funnelled through accept, so in fact the WaitingForClient() method doesn't *need* to be thread-safe. (However your claim that it is thread-safe remains untrue; it just doesn't need to be thread-safe when it comes to multiple incoming requests, so that particular issue isn't a problem.)

But I stand by my claim that the use of interlocked increment in this function is pointless.

--I use Interlocked.Increment(ref connectId) to increase it

Why? What's wrong with ++connectId; ? (Actually a better way of phrasing that would be: what problems with ++connectId; does Interlocked.Increment solve here? There are actually problems with ++connectId;, but using Interlocked.Increment doesn't solve them in this particular case.) Not only is Interlocked.Increment unnecessary here, it's positively misleading, since it implies that this code is trying to be thread-safe.

If the reason that you're using Interlocked.Increment here is because you are *reading* from the connectId field in another thread, I'm afraid this doesn't cut it. Just because thread A uses Interlocked.Increment, this doesn't alter the fact that if thread B reads the field, that read will be non-atomic: on a 32 bit processor, reading connectId will require 2 bus cycles, and the operation will be non-atomic. If you don't believe me go and read the part of the CLR specs that talk about which memory accesses are atomic and which aren't. The first bus cycle could occur before the Interlocked.Increment, the second could occur after the Interlocked.Increment. The only thing that Interlocked.Increment guarantees is that the read can't occur half way through the increment; it doesn't stop the opposite though - the Increment *can* happen half way through the read. Both the increment AND the read would need to be atomic for your code to be correct. (AFAIK there is no way to do an Interlocked.Read - if you could do an interlocked read on a long, you would be able to avoid this problem.)

If connectId were an int, not a long, it would be different. Reading from an int *is* guaranteed to be atomic. So that would fix the problem your code has wherebye the threads you kick off could actually read a bad value by getting rid of the potential for a read from a long being split by the increment. And since you are restricting the value of connectId to always be less than 10,000 I can see absolutely no point in making it a long. (You are aware that long is a 64 bit type aren't you?) Make it an int, and this gets rid of this particular set of problems.

However, that's not all... You completely fail to address the points in the next section:

>>However, the place where the value of connectId is then used
>>- the ReadSocket method on another thread - is a hostage to
>>fortune - what if the WaitingForClient() method manages to
>>accept a second incoming request before the second one gets
>>as far as reading connectId? (Also, ReadSocket implies
>> connectId is a 'long' - why? Its value is constrained to be
>> 10000 or lower, so having a 64 bit value is just pointless.
>> And copying one long into another like that is not guaranteed
>> to be thread-safe on 32 bit systems.)

-- The connectId is increased by each connection. How long is the
-- server running and how many connections will have during the
-- server is up? The connectId will be a huge number eventually.
-- Even it defined as long, it can’t guarantee it is fine. So I
-- set it to be 10000, or any big number. When the connectId
-- reach 10000, then try to start it from 1 again. You can find a
-- value that is not used by previous close thread.

This doesn't address either of the issues I raised, so I'll state them again:

(1) If the main thread manages to accept an incoming connection, launch a new thread, and then accept ANOTHER incoming connection before the 1st thread that it launched gets as far as reading the connectId, the connectId will already have been incremented a 2nd time. (This is possible despite the serialization that going through accept enforces: there is nothing in your main code that will wait until a newly-launched thread has actually read the value of connectId.) In this case both of the threads will read the same value from connectId. You will now have two threads that think they have the same ID. This is a bug.

(2) Why have you made connectId a long? long is a 64 bit type. You don't need a 64 bit type to hold a number that never goes over 10,000.


>> The server fires up one thread per connection. This is
>> widely recognised to be an unscaleable solution.
-- It depends on how does the server is used and how many client
-- does it have. It has its advantage.

Document this fact then. You don't make it clear that this code is only appropriate for lightweight usage, and that it will fall over under any kind of load. The system thread pool limits the maximum number of threads that it creates to 25 on a single processor system. It does this for a very good reason. Your system is OK for very small numbers of clients, but for 50 clients or more, it's not good. For 100s of clients it will have big performance problems.

The advantage your approach has is simplicity of implementation. The disadvantage is that this is no good for large (or even moderate) numbers of clients. At least mention this in your article, or people will think this is a good technique. I've seen servers that have floundered using this technique for fewer than 50 clients!


>>The ReadSocket method updates a ListView directly. This
>>is illegal - you are not supposed to call methods on a
>>Control on any thread other than the one that created it.
>>These calls should be done via the Control.Invoke method
>>or Control.BeginInvoke. Interestingly LoadThread gets
>>this right when updating the ListView.)
-- You are wrong completely and should learn something from here.
-- This is the best solution to update the control from the
-- background thread!

Well I can back my statement up with authoritative sources: I quote from Microsoft's documentation:

"Windows Forms uses the single-threaded apartment (STA) model because Windows Forms is based on native Win32 windows that are inherently apartment threaded. The STA model implies that a window can be created on any thread, but it cannot switch threads once created, and all function calls to it must occur on its creation thread."

and

"The STA model requires that any methods on a control that need to be called from outside the control's creation thread must be marshaled to (executed on) the control's creation thread. "


I also refer you to Mark Boulter's comment on this subject. Mark Boulter works for Microsoft. In fact he works for the part of Microsoft responsible for producing the Windows Forms framework. When someone did exactly what you suggest (updating a control from a background thread) here is what he said:

"DON'T DO THIS. YOU GOT LUCKY. YOU MUST USE Control.Invoke etc."

The original message is here. For a more full explanation see this message he wrote here, and to quote a small section from it, he says:

"if you are using multiple threads of execution you must be careful to marshal any calls to Win Forms controls back to the creating thread"

So the very thing you recommend (updating controls from background threads) is explicitly not allowed, according to Mark Boulter. Since he is a developer working for the part of Microsoft who produced this technology, I would very much like to know why it is that you claim to know more than him about how to use Windows Forms Controls.


--Otherwise you should get the instances of
-- the form and the instance of the control from the background
-- thread. It is not that easy! Please read the example in how to
-- do ... that demonstrates how to create a background thread
-- that uses a MethodInvoker to update a ProgressBar control at
-- regular intervals, which come from Microsoft within the Beta2
-- DVD.

I'm not sure from this whether you're having trouble with expressing the ideas in English (I'm guessing English is not your first language) or whether you genuinely don't understand the issues.

First of all this is *not* optional. The fact that it is inconvenient doesn't alter the fact that you have to do it. It's not just me who says you have to do it - the documentation, and Mark Boulter say that too.

Secondly, it's unclear what you mean by getting "the instance of the control from the background thread." This is not really the issue - there is only one instance of a given control, it's easy enough to get. The important thing is that you have to marshal calls onto the right thread if you want to use if from a background thread.

I'm well aware of the example in the documentation. It's really not *that* hard, and the whole point is that it's NOT OPTIONAL! If you find it difficult, then I'm very sorry to hear that, but that doesn't excuse you. You still have to do it. You are absolutely required to use Control.Invoke (or BeginInvoke). (You don't actually have to use MethodInvoker by the way - you can use any delegate.) It's the use of the [Begin]Invoke method that's significant.

Your code doesn't use this technique. It should. It's not just me who says it, but the documentation and the guys at Microsoft who design this stuff. So don't go telling me that I'm the one who could learn here. Poke tongue | ;-P


>>The CloseTheThread mysteriously aborts the thread.
>> This is completely pointless, since the thread was
>> about return out of its starting function anyway,
>> at which point the system would have cleaned it up.
-- execute the Abort thread function when you don’t need the
-- thread. It is better than you depend on system to do it for
-- you. If the system does not do it for you, what happen …?

If the system does not do it for you then there is a bug in the system. So you are writing code that presumes that at some point Microsoft will release a version of the CLR which has a bug wherebye threads don't terminate when they should. Don't you think that this is a little paranoid?

As soon as the main function in a thread exits, the thread will terminate. It's not like it needs to hang around and wait for garbage collection. Aborting the thread will, if anything, slow things down, since forcibly aborting a thread is more expensive than letting it terminate naturally.

Normally you would only abort a thread from another thread. I.e. the purpose of the Abort method is to allow one thread to kill off another one. There is rarely any need for a thread to kill itself off like this. (In your case there is definitely no need, since it is easy for the threads to simply exit.)



>>LoadThread uses asynchronous invocation - it calls
>>Control.BeginInvoke, and then mysteriously calls
>>JobDone.WaitOne() to wait for this asynchronous
>>work to complete [...]

You have completely missed the point I was making here in your answer:

-- you are wrong at here. The BeginInvoke Executes the given
-- delegate on the thread that owns this Control's underlying
-- window handle. The delegate is called asynchronously,

I know. That's what I said. That's what I meant when I said that "LoadThread uses asynchronous invocation - it calls Control.BeginInvoke". I have no idea why you think that your reply contradicts what I said, so I'm not sure what you're disagreeing with.

My point is that having used asynchronous invocation, you then have a load of extremely complex code that recreates the exact same semantics as you would have had if you had used synchronous invocation in the first place. So my question is: why didn't you use synchronous invocation in the first place?



-- but this method returns immediately.

Well...yes. That's exactly what asynchronous invocation is about. If it waited until the method returned then it would be synchronous invocation. (The fact that the method runs on a different thread is orthogonal to this. A call can be synchronous despite crossing threads - that's precisely what Control.Invoke does!)


-- it doesn't wait the called function to finished.
-- You have to use JobDone.WaitOne() at
-- here to receive the signal from the end of the called
-- function,

Exactly! That's precisely my point. (Again, I'm guessing that this is a language barrier problem - you seem to be in violent agreement with me, and yet you don't agree that the code is wrong.) If you wanted to wait for the function to finish, why didn't you use the method that DOES THAT FOR YOU?!!! (Control.Invoke)

Just in case you've misunderstood the documentation, here's what Control.Invoke does: it runs the method on the Control's thread, but it doesn't return until that method has completed. These are exactly the semantics you have recreated here. But the advantage of using Control.Invoke is that it's just one line of code, rather than the convoluted solution you have presented.



>> The SendDataToAllClient method is fine so
>> long as we ignore the title of the article.
>> If this is supposed to be supporting real-time
>> updates, then this is a particularly bad idea -
>> these Send calls could block. So all it takes
>> is one dead client, and everyone else is stuffed.
-- I can fix it easily by using try – catch to handle exception,
-- when you got the exception remove all the connected from all
-- the Hashtable.

That would be better, but it's only a partial solution. It takes a long time for the TCP/IP layer to detect that a connection is dead - far too long for the demanding requirements of real time stock price information. Worse than that, if you have a connection that is flakey but not dead (such as you get on the internet all the time - when you get heavy but not complete packet loss, connections grind to a near-halt but never actually die), then send operations can block for minutes at a time. Worse than that, it's possible for a client process to be locked up but the network to be healthy, in which case the call will block indefinitely, and there will be no errors. Ever.

So even with error handling built in, this code is entirely inadequate for the problem at hand. There is absolutely no sense in which it is real time - it is limited by the slowness of the slowest client, and there is no bound on how slow that might be. The only way to deal with this is to use a multi-threaded send of some kind. I would fire up a watchdog thread which checks to make sure the sending thread hasn't blocked for more than, say, a second, and to spin up a new thread if it has. Or it might be possible to build a satisfactory solution around the system thread pool. A third approach might be to use non-blocking IO, which would probably allow the most efficient solution, but would be the most complex approach.


I remain convinced that this is an example of how not to write such an application. Sorry.

--
Ian Griffiths
DevelopMentor
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 
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.