温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

45网络编程_UDP

发布时间:2020-07-17 11:43:05 来源:网络 阅读:199 作者:chaijowin 栏目:编程语言

 

 

 

UDP编程:

 

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

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

 

应用:

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

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

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

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

 

注:

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

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

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

 

 

UDP服务端编程步骤:

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

绑定ipportbind()方法;

传输数据:

         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:   #业务中udpserver关闭时不会通知client

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

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

        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

增加ackheartbeat机制;

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

心跳包设计:

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

s主动,serverhb扫一遍client,一般需要clientack响应来表示活着,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-->clientserver上连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')

 


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI