UDP端口复用问题

    一直觉得UDP协议很简单,但是今天问题让我感觉到网络的基础真是博大精深。

    废话少说,来看问题吧。由于协议的需要,我得实现一个UDP的客户端和服务器端,并且从同一个端口读写数据。

    最初不以为然,无非就是用两个socket,一个监听并从这个端口读取数据(服务器端采用了twisted),另一个向这个端口写入数据,用python实现只要10行左右的代码。

def startServer(queue, port):
    reactor.listenUDP(port, DhtResponseHandler(queue))
    reactor.run()
def sendUdpMsg(self, addr, msg):
    socketHandler = socket.socket(socket.AF_INET,  socket.SOCK_DGRAM)
    socketHandler.bind(("", self.port))
    socketHandler.sendto(msg, addr)
    socketHandler.close()
     由于要向同一个端口写数据,于是client必须有bind,但是运行后发现server先bind了这个端口,client运行时会报错

error: [Errno 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted
     一般这种错误时因为多个socket不能同时bind同一个地址
     由于基础不够扎实,我开始疯狂的搜索,发现有人说端口复用的问题,所谓的端口复用,是指一个套接字释放掉一个端口后有一个wait_time,另一个套接字如果接着bind就会报错。虽然我的问题不完全一样,但是我欣喜若狂的使用了。即在client bind前加上如下一句

socketHandler.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

     原因如下:一旦应用程序认为某个连接关闭了,操作系统的网络栈实际上会在一个等待状态(CLOSE_WAIT, TIME_WAIT)中将该连接的记录最多保存4分钟。也就是试图声明某个几分钟前在用的端口时,可能只是试图声明一个仍在使用的端口。

     但是仍然报错:

error: [Errno 10013] An attempt was made to access a socket in a way forbidden by its access permissions
    (顺便一提,还有另一个参数叫SO_REUSEPORT,即复用端口,另外有一个叫SO_EXCLUSIVEADDRUSE,即不准复用该端口,其他socket的参数还有很多,可以参考winsock http://msdn.microsoft.com/en-us/library/aa924071.aspx或者unix下的socket)

     这个10013错误让我百思不得其解,搜索一下,主要有两种解释,有人说是需要提升应用程序的权限为管理员,我用的是eclipse+pydev,提升完eclipse权限没用,实际上还要修改python.exe的权限,方法是在这个程序上右键,兼容性一栏中勾上以系统管理员身份运行;有人说是跟其他程序地址或者端口冲突。但是我测试过发现都不行。

     另外,运行的时候发现,twisted的服务器端一定是要在主线程中,否则会报signal一定要在主线程才能接受的错误,但是twisted的reactor一运行起来就阻塞了。

     在twisted文档中翻到,原来还有一种UDP叫做connected UDP,变态吧,所谓connected UDP,就是只能向一个地址收发数据,看起来貌似可以,但是不符合可以向多个地址接收数据。

     最后在一篇文章中翻到说需要两个端口都设置重用,于是我试着重新写一个服务器,与之前的客户端配合,运行良好,完全无错

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(("", port))
    data, address = sock.recvfrom(4096)
     好吧,看来问题在调用twisted了,不知道他是否有这样的设置,进去将这部分代码翻了一下,找不到这样设置的参数。
class Port(abstract.FileHandle):
    def __init__(self, port, proto, interface='', maxPacketSize=8192,
                 reactor=None):
        """
        Initialize with a numeric port to listen on.
        """
        self.port = port
        self.protocol = proto
        self.readBufferSize = maxPacketSize
        self.interface = interface
        self.setLogStr()
        self._connectedAddr = None

        abstract.FileHandle.__init__(self, reactor)

        skt = socket.socket(self.addressFamily, self.socketType)
        addrLen = _iocp.maxAddrLen(skt.fileno())
        self.addressBuffer = _iocp.AllocateReadBuffer(addrLen)
        # WSARecvFrom takes an int
        self.addressLengthBuffer = _iocp.AllocateReadBuffer(
                struct.calcsize('i'))

    def startListening(self):
        """
        Create and bind my socket, and begin listening on it.

        This is called on unserialization, and must be called after creating a
        server to begin listening on the specified port.
        """
        self._bindSocket()
        self._connectToProtocol()


    def createSocket(self):
        return self.reactor.createSocket(self.addressFamily, self.socketType)


    def _bindSocket(self):
        try:
            skt = self.createSocket()
            skt.bind((self.interface, self.port))
        except socket.error, le:
            raise error.CannotListenError, (self.interface, self.port, le)

        # Make sure that if we listened on port 0, we update that to
        # reflect what the OS actually assigned us.
        self._realPortNumber = skt.getsockname()[1]

        log.msg("%s starting on %s" % (
                self._getLogPrefix(self.protocol), self._realPortNumber))

        self.connected = True
        self.socket = skt
        self.getFileHandle = self.socket.fileno

    难道说twisted就完全不提供这样的功能?最终在multicast中翻到这样一段,也就是,多播的情况是支持地址复用的,动手测起来。

class MulticastPort(MulticastMixin, Port):
    """
    UDP Port that supports multicasting.
    """

    implements(interfaces.IMulticastTransport)


    def __init__(self, port, proto, interface='', maxPacketSize=8192,
                 reactor=None, listenMultiple=False):
        Port.__init__(self, port, proto, interface, maxPacketSize, reactor)
        self.listenMultiple = listenMultiple


    def createSocket(self):
        skt = Port.createSocket(self)
        if self.listenMultiple:
            skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            if hasattr(socket, "SO_REUSEPORT"):
                skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        return skt
     将server端改成如下代码,运行通过!
reactor.listenMulticast(port, DhtResponseHandler(queue), listenMultiple=True)
reactor.run()
     感触良多,底层的知识比较重要,浮沙筑高台果然危险。
   



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值