45网络编程_UDP

 

成都创新互联专注于企业成都营销网站建设、网站重做改版、宕昌网站定制设计、自适应品牌网站建设、H5高端网站建设成都商城网站开发、集团公司官网建设、外贸网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为宕昌等各大城市提供网站开发制作服务。

 

 

UDP编程:

 

>netstat -anp tcp | find "9998"   #win下

#echo "123abc" | nc -u 127.0.0.1 9998

 

应用:

无连接协议,基于如下假设:网络足够好、消息不会丢包、包不会乱序;

1、音频、视频传输,一般丢些包,问题不大,最多丢些图像、听不清说话,再次说话即可;

2、海量采集数据,如传感器发来的数据,丢几十、几百条数据也没关系;

3、DNS协议,数据内容小,一个包就能查询到结果,不存在乱序、丢包,重新请求解析;

 

注:

即使在LAN,也不能保证不丢包,且包的到达不一定有序;

一般来说,UDP性能优于TCP,但可靠性要求高的场合还是选择用TCP;

QUIC,quick udp internet connection,google,是谷歌制定的一种基于UDP的低时延的互联网传输层协议。在2016年11月国际互联网工程任务组(IETF)召开了第一次QUIC工作组会议,受到了业界的广泛关注。这也意味着QUIC开始了它的标准化过程,成为新一代传输层协议;

 

 

UDP服务端编程步骤:

创建socket对象,sock=socket.socket(type=socket.SOCK_DGRAM);

绑定ip和port,bind()方法;

传输数据:

         recvfrom(bufsize[,flags]),接收数据,获取一个二元组(string,address);

         sendto(string,address),发送数据,发送某信息给某地址;

释放资源;

 

例:

import socket

 

sock = socket.socket(type=socket.SOCK_DGRAM)

 

addr = ('127.0.0.1', 9998)

sock.bind(addr)

 

data, clientaddr = sock.recvfrom(1024)

print(clientaddr)

 

msg = 'ack: {}'.format(data.decode())

sock.sendto(msg.encode(), clientaddr)

输出:

('127.0.0.1', 9999)

45网络编程_UDP

45网络编程_UDP

 

例,ChatServerUdp:

ver1:

class ChatServerUdp:

    def __init__(self, ip='127.0.0.1', port=9998):

        self.sock = socket.socket(type=socket.SOCK_DGRAM)

        self.addr = (ip, port)

        self.event = threading.Event()

        self.clients = set()   #集合,去重,client主动退出后要清此数据结构

 

    def start(self):

        self.sock.bind(self.addr)

        threading.Thread(target=self._recv, name='recv').start()

 

    def stop(self):

        for c in self.clients:   #业务中udp的server关闭时不会通知client

            self.sock.sendto(b'end', c)

        self.sock.close()   #udp的socket关闭很快,不会有很多垃圾

        self.event.set()

 

    def _recv(self):   #_recv中使用多线程场景,在一对多情况下,server发送消息和接收消息出现不匹配时,用另一线程单独处理发送数据,否则接收和发送是同步,只有等发送完才能继续再次接收

        while not self.event.is_set():

            data, client = self.sock.recvfrom(1024)

            data = data.strip().decode()

 

            if data == 'quit':

                self.clients.remove(client)

                continue   #关键,接收下个client的消息

            self.clients.add(client)

            print(self.clients)

 

            msg = 'ack: {}'.format(data)

            for c in self.clients:

                self.sock.sendto(msg.encode(), c)

 

if __name__ == '__main__':

    cs = ChatServerUdp()

    cs.start()

    myutils.show_threads()

 

例:

ver2:

增加ack和heartbeat机制;

心跳即一端定时发往另一端信息,一般每次发的数据越少越好,心跳时间间隔约定好就行,ack响应,一端收到另一端的消息后返回的信息;

心跳包设计:

c主动,一般由client发hb-->server,server并不需要发ack-->client,只需要记录client还活着就行;

s主动,server发hb扫一遍client,一般需要client发ack响应来表示活着,server没收到ack就断开与client连接,server移除其信息,这种实现较为复杂,用的少;

c-s双向,用的更少;

class ChatServerUdp:

    def __init__(self, ip='127.0.0.1', port=9998, interval=10):

        self.sock = socket.socket(type=socket.SOCK_DGRAM)

        self.addr = (ip, port)

        self.event = threading.Event()

        self.clients = {}

        self.interval = interval

 

    def start(self):

        self.sock.bind(self.addr)

        threading.Thread(target=self._recv, name='recv').start()

 

    def stop(self):

        for c in self.clients:

            self.sock.sendto(b'end', c)

        self.sock.close()

        self.event.set()

 

    def _recv(self):

        while not self.event.is_set():

            lostset = set()

            data, client = self.sock.recvfrom(1024)

            data = data.strip().decode()

 

            current = datetime.datetime.now().timestamp()

            if data == '^hb^' or data == 'reg':

                print('hb')

                self.clients[client] = current

                continue

            elif data == 'quit':

                self.clients.pop(client, None)

                logging.info('{} leaving'.format(client))

                continue

 

            self.clients[client] = current

            print(self.clients)

 

            msg = 'ack: {} {}\n{}\n'.format(*client, data)

            logging.info(msg)

            for c, stamp in self.clients.items():

                if current - stamp > self.interval:

                    lostset.add(c)

                else:

                    self.sock.sendto(msg.encode(), c)

 

            for c in lostset:

                self.clients.pop(c)

 

if __name__ == '__main__':

    cs = ChatServerUdp()

    cs.start()

    myutils.show_threads()

 

UDP客户端编程步骤:

创建socket对象,socket.socket(type=socket.SOCK_DGRAM);

发送数据:sendto(string,address),发送某信息给某地址;

释放资源;

 

udp客户端编程中,只能在sendto()后,才能recvfrom(),否则OSError;

 

例:

sock = socket.socket(type=socket.SOCK_DGRAM)

 

addr = ('127.0.0.1', 9998)

data = 'test_data'.encode()

 

sock.sendto(data, addr)   #方式1,使用sendto()和recvfrom(),建议用此种方式

data, saddr = sock.recvfrom(1024)   #也可用recv(),只不过不知道谁发的消息了

print(data, saddr)

sock.close()

 

# sock.connect(addr)   #方式2,用connect()连接后才能用send()或sendto(),没有connect()连接只能用sendto();此方式可能会有问题,client-->server正常,server-->client,server上连client的端口不对了

# sock.send(data)

# data, saddr = sock.recvfrom(1024)

# print(data, saddr)

# sock.close()

 

例:

addr = ('127.0.0.1', 9998)

event = threading.Event()

 

def recv1(sock:socket.socket, event:threading.Event):

    while not event.is_set():

        data, saddr = sock.recvfrom(1024)

        logging.info('recv: {} ; from: {}'.format(data, saddr))

 

sock1 = socket.socket(type=socket.SOCK_DGRAM)

sock1.sendto('udp client1 send'.encode(), addr)

threading.Thread(target=recv1, args=(sock1, event)).start()   #recvfrom()必须在sendto()或connect()之后,否则OSError,即recvfrom()操作之前应该先sendto()或connect();如果用connect(),则远端必须有服务

 

def recv2(sock:socket.socket, event:threading.Event):

    while not event.is_set():

        data, saddr2 = sock.recvfrom(1024)

        logging.info('recv: {} ; from: {}'.format(data, saddr2))

 

sock2 = socket.socket(type=socket.SOCK_DGRAM)

sock2.connect(addr)

threading.Thread(target=recv2, args=(sock2, event)).start()

threading.Event().wait(5)

sock2.sendto('udp client2 send'.encode(), addr)

event.wait(2)

sock2.send('udp client2.1 send'.encode())

 

while True:

    if input('>>> ').strip() == 'quit':

        sock1.close()

        sock2.close()

        event.wait(3)

        break

logging.info('end')

 

例,ChatClientUdp:

注:此代码有问题,在发送hb后一直阻塞

class ChatClientUdp:

    def __init__(self, ip='127.0.0.1', port=9998, interval=5):

        self.sock = socket.socket(type=socket.SOCK_DGRAM)

        self.addr = (ip, port)

        self.event = threading.Event()

        self.interval = interval

        self.sock.connect(self.addr)

        self._sendhb()

 

    def start(self):

        # self.sock.send(b'reg')

        threading.Thread(target=self._sendhb, name='hb', daemon=True).start()

        # threading.Thread(target=self._recv, name='recv').start()

        # self._recv()

 

    def stop(self):

        self.send()

        self.sock.close()

        self.event.wait(2)

        self.event.set()

 

    def _sendhb(self):

        while not self.event.wait(5):

            self.sock.sendto(b'^hb^', self.addr)

 

    def send(self, msg:str='quit'):

        self.sock.sendto(msg.encode(), self.addr)

 

    def _recv(self):

        while not self.event.is_set():

            data, addr = self.sock.recvfrom(1024)

            logging.info('recv {} from {}'.format(data, addr))

 

cc = ChatClientUdp()

cc.start()

while True:

    data = input('plz input string>>> ')

    if data == 'quit':

        cc.stop()

        break

    else:

        cc.send(data)

logging.info('end')

 


当前文章:45网络编程_UDP
当前路径:http://ybzwz.com/article/jojdgc.html