Can't get UDP example to work

P

Paul Barry

I'm trying to get one of the examples from Foundation of Python
Network Programming to work. Specifically this is the UDP example
from Ch 3. First there is the server:

#!/usr/bin/env python
# UDP Echo Server - Chapter 3 - udpechoserver.py
import socket, traceback, time

host = '127.0.0.1' # Bind to all
interfaces
port = 51423

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))

while 1:
try:
message, address = s.recvfrom(8192)
print "Got message '%s' from %s" % (message, address)

# Echo it back
s.sendto(message, address)
print "Sent response to '%s'" % (address,)

except (KeyboardInterrupt, SystemExit):
raise
except:
traceback.print_exc()

Next I have a client written in Ruby, which works. I am posting thing
not to start a Ruby/Python flame war, but to simply prove that the
server works and there are no weird networking issues that would
prevent the Python client from working. The Ruby code is:

#!/usr/bin/env ruby
require 'socket'

socket = UDPSocket.new
socket.connect ARGV[0], ARGV[1]

puts "Enter data to transmit: "
data = STDIN.gets.chomp

socket.send data, 0
puts "Looking for replies; press Ctrl-C or Ctrl-Break to stop."

loop do
buf = socket.recvfrom(2048)
puts buf.first
end

When I start the server and run that, the output looks like this:

$ ch02/udp.rb 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
foobar

Now, when I try the python example:

#!/usr/bin/env python
# UDP Example - Chapter 2 - udp.py

import socket, sys

host = sys.argv[1]
textport = sys.argv[2]

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
port = int(textport)
except ValueError:
# That didn't work. Look it up instead.
port = socket.getservbyname(textport, 'udp')

s.connect((host, port))
print "Enter data to transmit: "
data = sys.stdin.readline().strip()
s.sendall(data)

print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
buf = s.recvfrom(2048)
sys.stdout.write(buf[0])

I don't ever get a response:

$ ch02/udp.py 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.

The server sees the message and says it has sent a reply:

Got message 'foobar' from ('127.0.0.1', 49623)
Sent response to '('127.0.0.1', 49623)'

Any ideas as to why this doesn't work?
 
M

MRAB

Paul said:
I'm trying to get one of the examples from Foundation of Python
Network Programming to work. Specifically this is the UDP example
from Ch 3. First there is the server:

#!/usr/bin/env python
# UDP Echo Server - Chapter 3 - udpechoserver.py
import socket, traceback, time

host = '127.0.0.1' # Bind to all
interfaces
port = 51423

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))

while 1:
try:
message, address = s.recvfrom(8192)
print "Got message '%s' from %s" % (message, address)

# Echo it back
s.sendto(message, address)
print "Sent response to '%s'" % (address,)

except (KeyboardInterrupt, SystemExit):
raise
except:
traceback.print_exc()

Next I have a client written in Ruby, which works. I am posting thing
not to start a Ruby/Python flame war, but to simply prove that the
server works and there are no weird networking issues that would
prevent the Python client from working. The Ruby code is:

#!/usr/bin/env ruby
require 'socket'

socket = UDPSocket.new
socket.connect ARGV[0], ARGV[1]

puts "Enter data to transmit: "
data = STDIN.gets.chomp

socket.send data, 0
puts "Looking for replies; press Ctrl-C or Ctrl-Break to stop."

loop do
buf = socket.recvfrom(2048)
puts buf.first
end

When I start the server and run that, the output looks like this:

$ ch02/udp.rb 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
foobar

Now, when I try the python example:

#!/usr/bin/env python
# UDP Example - Chapter 2 - udp.py

import socket, sys

host = sys.argv[1]
textport = sys.argv[2]

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
port = int(textport)
except ValueError:
# That didn't work. Look it up instead.
port = socket.getservbyname(textport, 'udp')

s.connect((host, port))
print "Enter data to transmit: "
data = sys.stdin.readline().strip()
s.sendall(data)

print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
buf = s.recvfrom(2048)
sys.stdout.write(buf[0])

I don't ever get a response:

$ ch02/udp.py 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.

The server sees the message and says it has sent a reply:

Got message 'foobar' from ('127.0.0.1', 49623)
Sent response to '('127.0.0.1', 49623)'

Any ideas as to why this doesn't work?
It works on my PC (Python 2.6.2, Windows XP Pro, service pack 3).
 
R

Roy Smith

Paul Barry said:
host = '127.0.0.1' # Bind to all interfaces

This threw me off the track for a little while. The comment is wrong!
You're not binding to all interfaces, you're binding specifically to the
loopback interface. For what you're doing, it's fine, but the comment is
still wrong.

It's also a little weird that the example uses connect() and sendall().
Those are TCP-ish type constructs. They work with UDP sockets, but using
them sort of obscures the fundamental datagram nature of UDP.

Well, enough of that. The real problem seems to be right here:
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
buf = s.recvfrom(2048)
sys.stdout.write(buf[0])

Try adding sys.stdout.flush(), and it should work. Or change it to print.

I've never seen this book, but if this code really is verbatim from the
book, I'm not very impressed.
 
P

Paul Barry

This threw me off the track for a little while.  The comment is wrong!  
You're not binding to all interfaces, you're binding specifically to the
loopback interface.  For what you're doing, it's fine, but the comment is
still wrong.

You are right. The example in the book actually has

host = ''

So the comment is correct. In trying to debug what was going on, I
put 127.0.0.1 to see if that had something to do with it, which it
didn't.
It's also a little weird that the example uses connect() and sendall().  
Those are TCP-ish type constructs.  They work with UDP sockets, but using
them sort of obscures the fundamental datagram nature of UDP.

Well, enough of that.  The real problem seems to be right here:
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    buf = s.recvfrom(2048)
    sys.stdout.write(buf[0])

Try adding sys.stdout.flush(), and it should work.  Or change it to print.

Yes, that did the trick, thanks. I should have thought of that, if I
would have just printed something after the recvfrom line I would have
realized it was no longer blocking and the problem was with getting
the client to print the data to stdout, not with getting a response
from the server.
I've never seen this book, but if this code really is verbatim from the
book, I'm not very impressed.

I like the book so far. It's a little verbose, but I think that's
because he's trying to teach the concepts, rather than the write it
how you actually would in production. In this case, I think he's
trying to illustrate how the UDP example compares to the TCP example
from the previous section, which is why he choose to keep the methods
the same.

But why not use

print buf[0]

instead of

sys.stdin.write(buf[0])

that much, I don't know.
 
P

Paul Barry

Paul said:
I'm trying to get one of the examples from Foundation of Python
Network Programming to work.  Specifically this is the UDP example
from Ch 3.  First there is the server:
#!/usr/bin/env python
# UDP Echo Server - Chapter 3 - udpechoserver.py
import socket, traceback, time
host = '127.0.0.1'                               # Bind to all
interfaces
port = 51423
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
while 1:
    try:
        message, address = s.recvfrom(8192)
        print "Got message '%s' from %s" % (message, address)
        # Echo it back
        s.sendto(message, address)
        print "Sent response to '%s'" % (address,)
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        traceback.print_exc()
Next I have a client written in Ruby, which works.  I am posting thing
not to start a Ruby/Python flame war, but to simply prove that the
server works and there are no weird networking issues that would
prevent the Python client from working.  The Ruby code is:
#!/usr/bin/env ruby
require 'socket'
socket = UDPSocket.new
socket.connect ARGV[0], ARGV[1]
puts "Enter data to transmit: "
data = STDIN.gets.chomp
socket.send data, 0
puts "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
loop do
  buf = socket.recvfrom(2048)
  puts buf.first
end
When I start the server and run that, the output looks like this:
$ ch02/udp.rb 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
foobar
Now, when I try the python example:
#!/usr/bin/env python
# UDP Example - Chapter 2 - udp.py
import socket, sys
host = sys.argv[1]
textport = sys.argv[2]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
    port = int(textport)
except ValueError:
    # That didn't work.  Look it up instead.
    port = socket.getservbyname(textport, 'udp')
s.connect((host, port))
print "Enter data to transmit: "
data = sys.stdin.readline().strip()
s.sendall(data)
print "Looking for replies; press Ctrl-C or Ctrl-Break to stop."
while 1:
    buf = s.recvfrom(2048)
    sys.stdout.write(buf[0])
I don't ever get a response:
$ ch02/udp.py 127.0.0.1 51423
Enter data to transmit:
foobar
Looking for replies; press Ctrl-C or Ctrl-Break to stop.
The server sees the message and says it has sent a reply:
Got message 'foobar' from ('127.0.0.1', 49623)
Sent response to '('127.0.0.1', 49623)'
Any ideas as to why this doesn't work?

It works on my PC (Python 2.6.2, Windows XP Pro, service pack 3).

Doesn't work on a Mac with Python 2.5.1 or 2.6.2 unless you flush
stdout or change it to print. Not sure why it would work on one
platform and not the other.
 
R

Roy Smith

Paul Barry said:
In this case, I think he's trying to illustrate how the UDP example
compares to the TCP example from the previous section, which is why he
choose to keep the methods the same.


I suppose, but I still think that's a confusing way to teach the
differences between UDP and TCP. The fundamental differences are:

1) TCP makes sure what you send gets there (for reasonably large values of
"makes sure"). UDP just tosses stuff out onto the network and hopes for
the best.

2) TCP is connection-oriented. Connections must be explicitly set up
before any data can flow, and once two sockets are connected, they can
never be disconnected (short of tearing down the connection and starting
again with two new sockets). With UPD, every time you send data, you have
to specify where it's going, and a given socket can be talking to many
different remote endpoints on a packet-by-packet basis.

3) TCP is a stream protocol, which hides record boundaries; it is free to
combine and/or split up your data into whatever packet boundaries it wants,
and there is no way for the receiving end to discover where the boundaries
are. UDP, on the other hand, honors record boundaries; there is a
one-to-one correspondence between each write (send, sendto) call at one and
each read (recv, recvfrom) call at the other.

It's difficult to demonstrate #1 in a simple example, so I'll skip that.
But, #2 and #3 are easy.

The example you gave uses connect() on a UDP socket. That's not pointing
out the differences between UDP and TCP, that's hiding them behind a
strange corner of the API. There really is no connection set up when you
do connect() on a UDP socket; it's just a way of saying, "From now on, I'm
going to skip telling you the destination of each individual datagram.
Just use this destination from now on". It saves moving a bit of data
around, but it's not fundamentally a connection.

As for the stream/datagram distinction, sendall() is very much a stream
creature. It says, "Just keep banging out packets until you've managed to
transmit the whole buffer". That only makes sense in an environment where
packet boundaries are meaningless. Using it with UDP is sort of like
saying, "Use as many packets as it takes, as long as that number is one".
It's not not a concept that makes sense in a datagram world. That it works
at all on UDP is (IHMO) a mis-guided attempt at interface uniformity.

If I were writing an example on how to use UDP, I would leave out the
connect() call, and use sendto() instead of sendall(). Once the student
has those down, that's the time to talk about weird corner cases like how
connect() and sendall() can be twisted to work with UDP sockets. Or maybe
not even bother.
 
S

Steven D'Aprano

You are right. The example in the book actually has

host = ''

So the comment is correct. In trying to debug what was going on, I put
127.0.0.1 to see if that had something to do with it, which it didn't.

Now I have to quote Aahz's email sig:

"At Resolver we've found it useful to short-circuit any doubt and just
refer to comments in code as 'lies'. "
--Michael Foord paraphrases Christian Muirhead on python-dev, 2009-03-22

But why not use

print buf[0]

instead of

sys.stdin.write(buf[0])

that much, I don't know.

print adds a trailing newline to the output, so the two lines aren't
quite equivalent.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top