简体   繁体   English

Python-连接套接字,与客户端/服务器无关

[英]Python - connecting sockets, client/server agnostic

I want 2 processes to communicate on a given port without either one having a defined client or server role. 我希望2个进程在给定端口上进行通信,而没有一个进程具有定义的客户端或服务器角色。 Either of the processes may be running alone. 这两个进程都可能单独运行。 Either may stop and restart at any time, in any order. 可以随时以任何顺序停止并重新启动。 When they are both running they need to communicate (when only one is running, communication is just discarded). 当它们都在运行时,它们需要进行通信(当仅一个正在运行时,通信将被丢弃)。

I want non-blocking sockets and Windows/Linux support. 我想要非阻塞套接字和Windows / Linux支持。

Here's a rather crude class that actually works to some extent, which might get you started. 这是一个相当粗糙的类,实际上在某种程度上有效,这可能会让您入门。

The main trick here is not to bother with listen at all: these are pure peer to peer connections, fully specified by the <local-addr, remote-addr> pair. 这里的主要技巧是根本不用去理会listen :它们是纯对等连接,完全由<local-addr,remote-addr>对指定。

Note that the sockets are left in non-blocking mode. 请注意,套接字处于非阻塞模式。 I caught the recv exception but there can be a send one as well (plus, you get broken-pipe errors when sending to a dead peer, etc). 我捕获了recv异常,但也可能有一个send异常(另外,在发送给失效的对等节点时,您会遇到管道破裂错误等)。 You'll also need to handle EOF-from-terminated-peer (when recv returns '' instead of failing with EAGAIN ). 您还需要处理EOF-from-terminated-peer(当recv返回''而不是EAGAIN失败时)。

import errno
import os
import select
import socket

class Peer(object):
    def __init__(self, local_addr, peer_addr):
        self._local_addr = local_addr
        self._peer_addr = peer_addr
        self._renew()
        self.reopen()

    def _renew(self):
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.bind(self._local_addr)
        self._sock.setblocking(False)
        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._state = 'bound'

    def is_open(self):
        return self._state == 'open'

    def is_opening(self):
        return self._state == 'opening'

    def reopen(self):
        if self._state == 'open':
            raise ValueError('already open')
        if self._state == 'opening':
            raise ValueError('open in progress')
        print 'try connect to:', self._peer_addr
        error = self._sock.connect_ex(self._peer_addr)
        print 'result:', error
        if error == 0:
            self._state = 'open'
            print 'connected immediately'
        elif error in (errno.EINPROGRESS, errno.EINTR):
            self._state = 'opening'
            print 'connection in progress'
        else:
            raise socket.error(error, os.strerror(error))

    def _check_open(self):
        if self._state != 'opening':
            raise ValueError('improper call to _check_open')
        print 'check connect to:', self._peer_addr
        _, wfds, _ = select.select([], [self._sock], [])
        if len(wfds) == 0:
            # connection still in progress
            return
        # we have a result: fail or succeed, either way a result
        try:
            peer = self._sock.getpeername()
        except socket.error as err:
            print 'caught err:', err
            if err.errno == errno.ENOTCONN:
                print 'connection failed, no peer available'
                self.close()
                return
            raise
        print 'got a peer:', peer
        self._state = 'open'
        print 'connection finished'

    def close(self):
        if self._state in ('open', 'opening'):
            self._sock.close()
            self._renew()
            # self.reopen() - or leave to caller

    def send_if_connected(self, data):
        # to do: add check for send to dead peer, and if so, _renew etc
        if self._state == 'bound':
            self.reopen()
        if self._state == 'opening':
            self._check_open()
        if self._state == 'open':
            self._sock.send(data)

    def recv_if_connected(self):
        # to do: add check for send to dead peer, and if so, _renew etc
        if self._state == 'bound':
            self.reopen()
        if self._state == 'opening':
            self._check_open()
        if self._state == 'open':
            try:
                return self._sock.recv(1024)
            except socket.error as err:
                # still connected but no data avail
                if err.errno == errno.EAGAIN:
                    return ''
                raise
        else:
            return None

if __name__ == '__main__':
    import argparse
    import time

    parser = argparse.ArgumentParser(description='test Peer()')
    parser.add_argument('-l', '--localhost', default='')
    parser.add_argument('-p', '--port', type=int, default=9001)
    parser.add_argument('-R', '--remote-host', default='')
    parser.add_argument('-r', '--remote-port', type=int, default=9002)
    args = parser.parse_args()

    x = Peer((args.localhost, args.port), (args.remote_host, args.remote_port))
    for i in range(1, 10):
        print 'attempt to send %d' % i
        x.send_if_connected('send %d' % i)
        got = x.recv_if_connected()
        if got is not None:
            print 'got: "%s"' % got
        time.sleep(1)

Run with: $ python peerish.py -p 9001 -r 9002 & python peerish.py -p 9002 -r 9001 & for instance. 例如: $ python peerish.py -p 9001 -r 9002 & python peerish.py -p 9002 -r 9001 &

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM