Re: tcp server (bidirectional)
by BrowserUk (Patriarch) on Dec 06, 2008 at 17:33 UTC
|
Reading between the lines of the OP and 728564, I think you want something like this:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
use threads;
use Thread::Queue;
use Mysql;
#ignore child processes to prevent zombies
$SIG{CHLD} = 'IGNORE';
#get the port to bind to or default to 8000
my $port = $ARGV[0] || 8000;
## Queue for commands input from the keyboard
## That will be sent to the currrently connected device
my $qin = Thread::Queue -> new;
## Queue for the data inbound from the device
my $qout = Thread::Queue -> new;
my $listen_socket = IO::Socket::INET->new(
LocalPort => $port, Listen => 1,
Proto => 'tcp', Reuse => 1
) or die "Cant't create a listening socket: $@";
warn "Server ready. Waiting for connections on $port ... \n";
async {
## Monitor the keyboard and forward any input
## to the currently connected client GPS device
while( <STDIN> ) {
chomp;
$qin->enqueue( $_ );
}
};
async{
## Monitor the queue and do whatever with the inbound data
while( my $data = $qout->dequeue ) {
## Do something with the gps data (like write it to a database
+?)
}
};
## Spawn a new connection if the device reattaches.
## Old connections clean themselves up automatically
while (my $connection = $listen_socket->accept) {
threads->create ("read_data", $qin, $qout, $connection)->detach;
print $connection;
}
sub read_data {
# accept data from the socket and put it on the queue
my( $qin, $qout, $socket ) = @_;
while (<$socket>) {
print "$_";
## Process data from connected devices
$qout -> enqueue(time." $_");
## if there are any commands pending, send them to the GPS dev
+ice
while( $qin->pending ) {
my $cmd = $qin->dequeue;
print $socket $cmd;
}
}
close $socket;
}
Based on your original above, this is light on error handling. The main changes are:
- It starts two extra threads (using async()).
- One monitors the keyboard and forwards any input (commands) via a second queue, to the current client thread which will in turn forward those to the device.
This could be an additional listening socket so the command input could be initiated via another script, or even remotely.
- The second reads your original queue (which you were doing nothing with), allowing you to process the data asynchronously from it's arrival.
Going by the use MySql; in the OP, you intend to put it into a DB?
- Starts a second queue used to pass keyboard input commands to the currently connected device.
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
|
|
Hello BrowserUk,
Thanks for your help!
Yes, this script will update MySQL database with gps data.
This is great with additional listening socket, but server still needs to receive some data from the client to respond him with a command, right? I need to be able to send command to connected client without waiting his data.
With your version, old connections still remains opened and does not clean themselves. When gps device reconnects to the server, it uses a different IP and port No., maybe is this the cause !? Thanks again! Igor V.
| [reply] |
|
|
With your version, old connections still remains opened and does not clean themselves.
I cannot argue authoratatively with you as I do not have the device is question (nor possibly your OS), but on the basis of logic, that doesn't make much sense.
Starting from the point where there is an established connection between the GPS device and the server, under what circumstances would it decide to reconnect? I see two possibilities:
- It attempts to write its regular data packet to the socket and gets an error indicating that the connection has 'gone away', and so it decides to establish a new connection.
Under these circumstances, the readline at the server end will fail returning undef, the while loop will end.
The server will close the socket, and fall off the end of the (detached) thread proc and the thread will self-terminate.
It is possible that the tcpip stack will hold the socket open (in a SYN_WAIT state) for a while longer, subject the the system default timeout (900 seconds?), but it will eventually timeout and be shut down.
You might cause it to be shutdown earlier by preceding the close with an explicit shutdown $socket, 2; before the thread terminates, but it will make little or no difference to the operation of the application.
- The device could decide to reboot as a result of (say) a hardware induced reset, and come up unaware of the existing established connection and make a new connection (to the server's main threads accept loop).
Under these circumstances, the previous thread may persist in the readline (recv) state for a while, seconds or minutes (again depending upon the systems default timeout values), but eventually, the readline will timeout and the (detached) thread will close the client socket and self-terminate.
As this is a threaded server, pending and orphaned connections will not inhibit the new connection from being established and operating.
When gps device reconnects to the server, it uses a different IP and port No., maybe is this the cause !?
Again, I cannot argue with what you are seeing, but logic says this doesn't make sense. If the device connected with a different IP, it would be connecting to a different machine!
Unless the server is multi-homed (of which I have no experience), but even so, it would still have to connect to a different (copy of the) server process. And how would it know what other IP to use?
I suspect what you are seeing is that when the device reconnects, that the socket handle reported by the print $connection; line in the main accept loop, is different. That's normal!
When a new client connects, (even if it is the same client reconnecting), it will get a new IO::Socket::INET instance which will have a different handle to that used previously.
Equally, if you are monitoring the port numbers used by the client connections, the server port allocated to the new connection will be different to that used by the listening socket (that's the way tcp works!), and it will often be different to the port number used by the previous client connection, even if it is inbound from the same device and connectiing to the same listening port.
Unless you've called shutdown (at both ends, which is unlikely given the possible scenarios ), the previous port number may not get reused (even with Reuse => 1) until both ends of the old connection have either shutdown or timed out. That can take several minutes. This is normal and of no great consequence unless you are re-establishing the same connections 100s of times per second.
This is great with additional listening socket, but server still needs to receive some data from the client to respond him with a command, right?
Yes. As posted, outbound (server to device) transmissions will not be sent until after an inbound data message (device to server) is received. You cannot (on my system using perl and IO::Socket, at least) transmit to a socket whilst it is currently in a read state. It may be possible on other systems, but no one has ever answered my frequent questions on this to either confirm or deny that possibility. I therefore assume that it is not possible,
My assumption (given an absence of information to the contrary), was that the device uploads data packets at regular intervals, and so the longest delay between initiating a command input and it being transmitted, is the length of time between those regular data packets.
If this would present an unacceptable delay, there are two possibilities:
- Use a non-blocking socket and a select loop to ensure that you only enter a read state when there is something to be read.
This would allow you to transmit the command immediately from any thread that has access to the client socket. It would also considerably complicate the architecture of the application.
- Use a timer approach to decide when to enter the read state to fetch inbound data packets.
This would allow you to continue to monitor the command input queue and transmit commands in a timely fashion. The downside is that it requires you to know the frequency with which the device produces data packets--which must be at regular intervals.
So, assuming that the device uploads data packets (say) every 10 seconds, and you wish to not have to wait for that duration before transmitting a command, you might modify the client thread proc along these lines:
sub read_data {
# accept data from the socket and put it on the queue
my( $qin, $qout, $socket ) = @_;
while ( 1 ) {
## Don't enter a read state until your pretty sure there will b
+e something
## to fetch. The loop count (10) x the sleep value (1) define t
+he device
## data frequency
for( 1 .. 10 ) {
## if there are any commands pending, send them to the GP
+S device
while( my $cmd = $qin->pending ) {
print $socket $cmd;
}
sleep 1; ## or usleep 0.1 or 0.01 etc.
## depending upon the urgency of your cmd requirements
}
my $data = <$socket>;
last unless defined $data;
print "$data";
## Process data from connected devices
$qout->enqueue( time . ' ' . $data );
}
shutdown $socket, 2;
close $socket;
}
In the absence of clear information about the nature of the device and communications involved, this is an imperfect formulation, but gives an idea of one way to approach the situation.
There is a third possibility. That of setting the socket non-blocking and not using select, but I'm uncomfortable to suggest this as my own experience of trying this have had mixed results.
(I make no claims to any great expertise in tcp. I tend to just stick to whatever works for me on my platform. YMMV.)
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
Re: tcp server (bidirectional)
by jethro (Monsignor) on Dec 06, 2008 at 14:45 UTC
|
Usually a server is a server because it does not act, it reacts i.e. serves. If you keep your communication that way, you have a lot less problems (especially if you are a novice in that area)
For example your while loop in the read_data thread is endless if your client just breaks the connection (which I assume might be your problem, you don't show the client code so I have to guess). Your client usually has to tell the server "I'm done". You either have to send an EOF or some command which signals the end.
Your client and your server have to know at what times each one has to send anything. Such a proper handshake makes sure that client and server don't wait both for a communication from the other side. Usually this is done by defining commands that the client sends to the server and the server has to answer (naturally to make this safe from network problems you still have to have some timeout which breaks the connection after waiting too long, but that can be ignored for a first simple solution).
So the client might have a command "send gps" and the server answers either with the gps data or an error code which might signify that something is wrong or that it just needs more time to get gps data. In the second case the clients waits a second (or whatever time seems sensible) and then sends the command "send gps" again. Notice this makes it possible for the client to wait for an answer of the server, it is called "polling". Or instead of polling the client simply has to wait until the server has calculated the answer.
Now it is entirely possible and no problem for the client and the server to change roles (even in the middle of the connection). Then the server initiates new communications, sends the commands and tells the client when the communication has finished or when the roles reverse again. But this makes your script more and more complex and you need a lot more than your simple while loop to get this working
| [reply] |
|
|
Hello Jethro,
Thanks for your comment on this.
Gps devices are constantly connected to the server, which waits for devices data. Devices can not send the EOF command, because they only disconnect when there is a problem with gsm network or some other unpredicted outage. When this occur, device reconnect automatically to the server and opens a new socket, but the old one remains open on the server side. (This happens about once a day) I need to close those sockets which are not receiving data for some time. Second thing, I need to occasionally send some data to selected devices (Instruct the device to reboot, change some configuration, etc.) I have to do all of this on a server side, because I have no control over devices software.
Any help would be really appreciated Igor V.
| [reply] |
Re: tcp server (bidirectional)
by zentara (Cardinal) on Dec 06, 2008 at 17:34 UTC
|
See Simple threaded chat server and the multi-echo section. What you need to do is keep an array of active clients, or you can use the socket method "connected", to test each connection before sending to it.
| [reply] |
|
|
Hello BrowserUk and Zentara
Really thanks for your information's.-
You were right about closing connections
I probably didn't wait enough and when checking connections and port numbers they were still open.
In previously message I meant different ip's from clients, not that they are connecting to different server ip - Sorry to misslead you.
I resolved the problems for sending a command to selected client, without waiting him to respond. I've adopted a chat script that suggested beneath - Works perfectly! Thanks again to both of you for helping me! Regards, Igor V.
| [reply] |
Re: tcp server (bidirectional)
by Anonymous Monk on Dec 06, 2008 at 08:38 UTC
|
How can I send a text string from tcp server to the open socket of gps device.
print SOCKET "string";
| [reply] [d/l] |
|
|
| [reply] |
|
|
Look into using exec or system or some other format. Or something like named pipes (a simple block of text for example):
my $string = `date`;
print SOCKET "$string";
Though there are other considerations.
Someting that a lot of people need to realise is that sockets and files are no different (except for initialisation). You can read and write from a socket in the same way that you can from a file. If you understand files, treat them the same way.
Gits.
| [reply] |