LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

TCP/IP feature or bug?

I have been using the basic TCP VIs (Data Communication palette) to implement multiple independent TCP streams with a real-time system.  In doing some tests, I found a strange behavior that I think may be a "bug".

 

To form a connection, you need to both run both TCP Listen and TCP Open Connection with the same Service Name.  I assumed that when you closed these two TCP IDs (TCP Close Connection), a subsequent Listen alone, or Open Connection alone, would fail (because both need to be present).  However, I found that while I could not open the Listener a second time without it timing out, I could open the "Open Connection" (without re-opening the Listener) and no error would result.  This should not work (because you could, for example, "Send" via the "Open Connection" stream but not have any process "listening", and capable of receiving, the data).

 

I've attached a VI that runs the following 6 tests:  1, open/close ("run") server; 2, run client; 3, run server + client; 4, run server+client, then run server+client again; 5, run server+client, then run just server again; 6, run server+client, then run just client again.  According to the principle that both a server and client need to be running at the same time, only tests 3 and 4 should succeed without error (the server is configured with a 2 second timeout, which generates an error if there's no client), but test 6 also succeeds!

 

Note that the "Is a RefNum" VI on the Boolean palette correctly indicates "Not a RefNum" after the Close Connection VI runs.

 

P.S. -- this VI was run and tested on LabVIEW 8.5.1.  I just ran it on LabVIEW 8.6, and it behaves the same way (tests 3, 4, and 6 run without error).

 

Bob Schor

0 Kudos
Message 1 of 13
(7,060 Views)

I'm personally having trouble with understanding your code, but I think I know what you're talking about and I ran into this myself. I wrote a VI to demonstrate this, but never had a chance to upload it, so here it is now (7.0).

 

Essentially, the problem is that the TCP Open primitive works if there's a listener on the other side, even if the Wait On Listener primitive is not called. I don't know whether this behavior is correct or not, since the term "listener" isn't really explained in the help. Personally, I think it should probably not behave this way and only work if the Wait On Listener primitive is running.

 

The second problem is that the TCP Listen VI creates a listener without destroying it and it looks like closing the connection does not destroy the listener either. My example doesn't show this, but if you add a close at the end and then open again, the open still succeeds since the listener is still active.

 

When combining this with the previous behavior, you get the result that if you call TCP listen N times (e.g. in a loop), you have N listeners. I feel fairly certain that at least this is a bug and the listener should be destroyed.


___________________
Try to take over the world!
Message 2 of 13
(7,035 Views)

I'm not a TCP/IP expert, but I think this is correct behavior.  I assume you've opened the TCP Listen.vi to see that inside of it is the "Internecine Avoider" (which contains Create Listener) and a Wait On Listener primitive.  Wait On Listener has an unwired "listener ID out" output terminal.  So long as that listener is active it will continue to accept, and queue, incoming TCP/IP connections.  You can close the listener after accepting a connection by connecting the listener ID to TCP Close Connection.  If you don't close the listener and you don't handle additional incoming connections, you can fill up the incoming queue to the point where no more TCP/IP connections are accepted (at least, that's what appears to be happening - I've made this error at least twice and couldn't figure out the problem).

 

Let's say you do want to allow multiple connections (a single-server/multiple client situation).  Then, you need the listener to continue to accept connections even if the code isn't waiting on the listener at the moment of connection, so that it can establish the connection as soon as it is ready to wait again.  For example, your server waits for a connection, exchanges some data to confirm the client's identity, then passes off the validated connection to some other loop and goes back to waiting for additional incoming connections.  If you attempt a second connection while the initial data exchange is in progress, the listener accepts the incoming connection so that you can deal with it as soon as the first connection is established, rather than rejecting the second connection.  If the listener didn't act this way there would be no way to reliably allow a server to accept connections from multiple clients, since it would be unable to handle the case of nearly-simultaneous connections.  So, I think this behavior is correct, although I do think that TCP Listen.vi should have a listener ID out terminal.

Message 3 of 13
(7,024 Views)

The 4 Case structures contain code for either a "listener" open/close sequence (top row) or a "shouter" open/close sequence (bottom row), the latter having a half-second delay to let the listener go first.  The Knob control selects which Case statements to run in order to implement Tests 1-6 (for example, Test 1 does a single Listener open/close).  Within each Case, I display the numeric value of the TCP ID before and after the Close, as well as the boolean "Is Not RefNum", one set for each Case.  All of the indicators are initialized in a Sequence structure first.

 

The Open/Close doesn't "do" anything except to establish a TCP/IP connection between Listener and Shouter, then discard it.  [Or so I thought].


BS

0 Kudos
Message 4 of 13
(7,012 Views)

I still find the code in the original post too complicated. It's just too much. I believe the VI I posted essentially demonstrates the same issue.

 


nathand wrote:

If the listener didn't act this way there would be no way to reliably allow a server to accept connections from multiple clients, since it would be unable to handle the case of nearly-simultaneous connections.


Isn't that why both the Wait and the Open have a timeout? That way, they don't have to be perfectly sync'ed, as one can wait on the other.

In any case, your code won't be simultaneous either, since even if the open succeeds, the server will still need to finish running its current validation code before it can run it again for the new connection.


___________________
Try to take over the world!
Message 5 of 13
(7,008 Views)

Bob Schor wrote:

 

The Open/Close doesn't "do" anything except to establish a TCP/IP connection between Listener and Shouter, then discard it.  [Or so I thought].


Your code never actually closes the listener, it simply closes the established TCP/IP connection.  Those are two separate items, which is why I noted that TCP Listen.vi should have a "listener id out" terminal.  Take a look at the "Multiple Connections - Server.vi" example to see how you can have one listener create multiple connections.

 

Here's an analogy - picture you're trying to get into some party and I'm standing by the door, handing you a ticket (which you'll then need to present later to get drinks or something).  The door is acting like the listener, and your ticket is an individual TCP/IP connection.  You could rip up your ticket or leave the party (close the connection) but other people will still be able to walk in the door and enter.  On the other hand, if you close the door (close the listener), no one else will be able to enter the party (establish a connection).  If I'm busy talking to you when I hand you your ticket - I'm waiting for you to pay, perhaps - more people can still wait in line behind you because the door is still open.  That's what's happening in your code - the first person into the party leaves (closes the connection), but the second person in line can still get in because the door is still open (the listener is still available).

Message Edited by nathand on 01-08-2009 04:01 PM
Message 6 of 13
(6,999 Views)

tst wrote:

I still find the code in the original post too complicated. It's just too much. I believe the VI I posted essentially demonstrates the same issue.

 


nathand wrote:

If the listener didn't act this way there would be no way to reliably allow a server to accept connections from multiple clients, since it would be unable to handle the case of nearly-simultaneous connections.


Isn't that why both the Wait and the Open have a timeout? That way, they don't have to be perfectly sync'ed, as one can wait on the other.

In any case, your code won't be simultaneous either, since even if the open succeeds, the server will still need to finish running its current validation code before it can run it again for the new connection.


 

I wasn't suggesting that the two connections would both execute simultaneously - what I was trying to say is that both clients attempt to establish them simultaneously.  Open only sends one packet of data, then waits the length of the timeout period for a response.  It does not continue to attempt to establish a connection during that period.  If, as you suggested, a listener only accepted connections while Wait is active, then in my example of two nearly-simultaneous connections, the second connection would not be opened because it would never see the attempt to establish the connection.  It would be analogous to the situation in which you first open a connection, then create the listener within the timeout period - the connection won't be established.  But this is not how it works.  Instead, by creating a listener you are telling the operating system to accept and establish connections on a specific port.  The operating system will go ahead and do that for you even if LabVIEW isn't the active application at that instant and even if Wait On Listener isn't currently executing because the code is busy doing something else.  It will queue those connections and the next time Wait On Listener is called it will return the first connection in the queue.  If there are no queued connections, then Wait On Listener waits for its timeout to expire.
Message 7 of 13
(6,989 Views)

I don't know all the internal TCP details (e.g. SYN and ACK) and who's supposed to send what and when. I'm assuming that the LabVIEW primitives follow it correctly.

 

I should also point out that my claim earlier about the listen VI creating multiple listeners was wrong. I simply hadn't looked inside the IA VI, although I have done it in the past. Looking at it shows clearly that it has its own buffer for the listener references and only holds one for each port (makes sense, since you can't have two connections on the same port). Assuming that the primitives work correctly (which they probably do, or there would probably be a single primitive for listening instead of two), then I guess that NI does need to output the listener ref as you suggested, so that the user can disable the listener if they so choose.

 

By the way, this was simply a pet annoyance of mine, since I was asked to debug a piece of code which was affected by this issue and it was annoying. Essentially, the system had a single client with multiple servers and each server would only allow one connection. The server code looked something like this:

 

 

 

The client code had a similar timer for handling the errors, but it had a much shorter timeout, so what was happening was that if the user closed the client and reopened it while the server was still in the right loop, they would get stuck in a situation where the client opened the connection successfully but didn't get a response so it kept opening more connections. The server, on the other hand, got a connection each time (since the listener was always active) and a single message which was very stale. Then, it had to count all the errors again.

 

Of course, once I realized this was the issue, the fix was simple. I created and destroyed the listener myself and that solved that. I could also probably have used an infinite timeout instead of the loop, but fixing it was enough. I didn't need to make it any better.

 


___________________
Try to take over the world!
0 Kudos
Message 8 of 13
(6,936 Views)

Nathand's explanation is correct. When using 'TCP Listen' there is no way to close the listener so it will continue to accept connections until the OS's connection queue is full. I agree that this behavior of 'TCP Listen' is odd. I will look into adding an output terminal for the listener. If this is a problem you can, as tst did, use the 'Create Listener' and 'Wait on Listener' instead of 'TCP Listen'.

 

On a side note, tst wrote:

"I don't know all the internal TCP details (e.g. SYN and ACK) and who's supposed to send what and when. I'm assuming that the LabVIEW primitives follow it correctly."

 

LabVIEW's networking primitives do not operate at that level (SYN and ACK). The networking primitives are a thin wrapper around the OS's networking stack to keep them simple (fewer bugs) and so that developers familiar with network programming can use them easily.

Message 9 of 13
(6,859 Views)

NathanK wrote:

I will look into adding an output terminal for the listener.


I would say that the better alternative would be to destroy the listener internally, because when a user (including me) sees "TCP Listen", they figure the VI will listen as long as it runs. I definitely did not expect the device (a FieldPoint, in that case) to continue listening after the VI stopped running.

 

Of course, that would cause issues with backwards compatibility, so it probably can't be done and returning the listener ref along with a prominent piece of text in the VI description is probably the only viable option. Maybe another alternative would be to add a Destroy Listener? input to the VI. This would at least make it clearer to the unsuspecting user. You could then make the default T and use mutation code for backward compatibility.


___________________
Try to take over the world!
0 Kudos
Message 10 of 13
(6,833 Views)