Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DatagramServer can be started multiple time on same port without Exception #932

Closed
bobatsar opened this issue Jan 20, 2017 · 2 comments
Closed
Labels
Type: Question User support and/or waiting for responses

Comments

@bobatsar
Copy link

bobatsar commented Jan 20, 2017

Description:

It seems it is possible to start two Datagram servers in the same script on the same port. I thought that should not be possible. If I do the same for a StreamServer I get an error as expected (socket.error: [Errno 98] Address already in use: ('127.0.0.1', 8000)).

The second started server can receive data, the first does not get anything. After stopping the second, no data is received at all.

If a normal UDP Server is already started, I get an exception (as expected).

What I've run:

from gevent.server import DatagramServer
from gevent import sleep, spawn, Timeout
import random
import argparse
import socket
import sys


class UDPServer(object):

    def __init__(self, host):
        self.udp_server = DatagramServer(host, handle=self.handler)
        self.udp_server.start()

    def __repr__(self):
        return "<UDPServer id={} started: {}>".format(id(self),
                                                      self.udp_server.started)

    def handler(self, data, address):
        print "{}: got data {} from {}".format(self, data, address)

    def stop(self):
        self.udp_server.close()


def sender(host, timeout=1):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    print "start sending data to", host
    with Timeout(timeout, exception=False):
        while(True):
            rand = random.random()
            print "send", rand
            sock.sendto(str(rand), host)
            sleep(.1)
    print "sender finished"

def plain_datagram_server(host, port, time):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Bind the socket to the port
    print "start plain UDP server for {}s".format(time)
    server_address = (host, port)
    print 'starting up on %s port %s' % server_address
    sock.bind(server_address)
    print "started plain datagram server"
    sleep(time)
    print "stop plain datagram server"


def test_datagram_server(host):
    print "DatagramServer test on", host
    print "Start udp1 on {} and send data".format(host)
    udp1 = UDPServer(host)
    udp2 = None
    print "udp1", udp1
    print "udp2", udp2
    sender_greenlet = spawn(sender, host)
    sender_greenlet.join()

    print "\nStart udp2 on {} and send data".format(host)
    udp2 = UDPServer(host)
    print "udp1", udp1
    print "udp2", udp2
    sender_greenlet = spawn(sender, host)
    sender_greenlet.join()

    print "\nStop udp2 and send data"
    udp2.stop()
    print "udp1", udp1
    print "udp2", udp2
    sender_greenlet = spawn(sender, host)
    sender_greenlet.join()


if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument("mode", choices=["send", "test", "test_plain",
                                         "receive_plain"])
    parser.add_argument("--time", default=10)
    parser.add_argument("--port", default=8000)
    parser.add_argument("--host", default="localhost")

    args = parser.parse_args()
    if args.mode == "send":
        sender((args.host, args.port), args.time)
    elif args.mode == "test":
        test_datagram_server((args.host, args.port))
    elif args.mode == "test_plain":
        spawn(plain_datagram_server, args.host, args.port, args.time)
        sleep()
        test_datagram_server((args.host, args.port))
    elif args.mode == "receive_plain":
        plain_datagram_server(args.host, args.port, args.time)

If you run my script with mode "test", it shows the described behaviour. If you run it with mode "test_plain", a normal UDP Server is started on a socket and then the DatagramServer fails.

@jamadden
Copy link
Member

I believe you're seeing the effects of SO_REUSEADDR, which is set to True by default. Its effects are highly system dependent (and maybe even family---INET vs DGRAM) dependent. It seems to make no difference at all on OS X (I can't "double bind" ever), while on Linux 4.4.21 I can unless I set reuse_addr to False.

This is poorly documented, perhaps because the effects vary so widely. But you can change it for your particular system:

class MyDS(DatagramServer):
  reuse_addr = 0

@jamadden jamadden added the Type: Question User support and/or waiting for responses label Jan 20, 2017
@jamadden jamadden closed this as completed Mar 9, 2017
@bobatsar
Copy link
Author

bobatsar commented Mar 10, 2017

sorry that I didn't respond until now.
I changed my code to the following but the behaviour didn't change.

I can work around this problem but I was wondering if this is a bug.

class UDPServer(object):

    def __init__(self, host):
        self.udp_server = DatagramServer(host, handle=self.handler)
        self.udp_server.reuse_addr = 0
        self.udp_server.start()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Question User support and/or waiting for responses
Projects
None yet
Development

No branches or pull requests

2 participants