Python的Socket编程过程中实现UDP端口复用的实例分享
关于端口复用
一个套接字不能同时绑定多个端口,如果客户端想绑定端口号,一定要调用发送信息函数之前绑定( bind )端口,因为在发送信息函数( sendto, 或 write ),系统会自动给当前网络程序分配一个随机端口号,这相当于随机绑定了一个端口号,这里只会分配一次,以后通信就以这个随机端口通信,我们再绑定端口号的话,就会绑定失败。如果我们放在发送信息函数( sendto, 或 write )之前绑定,那样程序将以我们绑定的端口号发送信息,不会再随机分配一个端口号。实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口,这时候,别的套接字就无法使用这个端口。那如何让两个套接字都能成功绑定一个端口呢"htmlcode">
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)
但是仍然报错:
复制代码 代码如下:
error: [Errno 10013] An attempt was made to access a socket in a way forbidden by its access permissions
(顺便一提,还有另一个参数叫SO_REUSEPORT,即复用端口,另外有一个叫SO_EXCLUSIVEADDRUSE,即不准复用该端口,其他socket的参数还有很多,可以参考winsockhttp://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()
感触良多,底层的知识比较重要,浮沙筑高台果然危险。
下一篇:python编写简单爬虫资料汇总