• python 全栈开发,Day34(基于UDP协议的socket)


    昨日内容回顾

    网络的基础概念
    arp协议 :通过ip地址找到mac地址
    五层模型 : 应用层 传输层 网络层 数据链路层 物理层
    tcp协议 : 可靠的 面向连接 全双工
      三次握手
      四次挥手
    udp协议 : 不可靠的 面向数据包的 高效的
    socket :
      是模块 是和应用层直接交互,
        向下封装了,应用层之下的相关协议的一组网络通信的接口

    全双工,表示双向连接。
    为什么四次挥手的2次连接,不可以合并?
    第一次断开,不会立即断开,如果还有数据,可以发送。所以不可以合并。

    python代码,属于应用层

    socket网络通信

    新建文件server.py,内容如下:

    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()  # 参数n表示同一时间可以有n个链接等待与server段通信
    conn,addr = sk.accept()
    ret = conn.recv(1024).decode('utf-8')
    print(ret)
    conn.send(b'hello')
    conn.close()
    sk.close()

    新建文件client.py,内容如下:

    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    sk.send(b'hello')
    print(sk.recv(1024).decode('utf-8'))
    sk.close()

    先执行server.py,再执行client.py,输出:

    hello

    但这是一次性的,能不能server与client交互呢?

    修改server.py,内容如下:

    import socket
     
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(('127.0.0.1',9000))
    sk.listen()   # 参数n表示同一时间可以有n个链接等待与server端通信
     
    while True:
        conn,addr = sk.accept()
        while True:
            ret = conn.recv(1024).decode('utf-8')
            if ret == 'q':break
            print(ret)
            inp = input('>>>')
            conn.send(inp.encode('utf-8'))
            if inp == 'q':break
        conn.close()
     
    sk.close()

    修改client.py,内容如下:

    import socket
     
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    while True:
        inp = input('>>>')
        sk.send(inp.encode('utf-8'))
        if inp == 'q':break
        ret = sk.recv(1024).decode('utf-8')
        if ret == 'q':break
        print(ret)
    sk.close()

    先执行server.py,再执行client.py,效果如下:

    server可以主动关闭client,如果client再次开启,还可以继续聊。

    如果client和server已经建立连接了。这个时候,再来一个客户端,新来的就不能用了。为啥呢?

    对于一个tcp连接,客户端和server是一直占用的。其他所有客户端,全都不能占用。

    因为连接是阻塞的。

    如果一个server没有执行sk.colse(),再开一个进程时,就会出现

    解决方案:

    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 表示允许端口复用
    sk.bind(('127.0.0.1',9000))

    总结:

    tcp协议适用于 文件的上传和下载 发送邮件 发送重要的文件
    每和一个客户端建立链接 都会在自己的操作系统上占用一个资源
    同一时间 只能 和一个客户端通信

    数据交互是从上至下,然后再从下至上

    下图,表示2台机器

    基于UDP协议的socket

    udp是无链接的,启动服务之后可以直接接受消息不需要提前建立链接

    简单使用

    server端

    import socket
     
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9000))
    msg,client_addr = sk.recvfrom(1024)  # udp协议不同建立链接
    print(msg)
    sk.sendto(b'world',client_addr)
    sk.close()

    client端

    import socket
     
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.sendto(b'hello',('127.0.0.1',9000))
    ret = sk.recvfrom(1024)
    print(ret)
    sk.close()

    先执行server.py,再执行client.py,执行输出:

    (b'world', ('127.0.0.1', 9000))

    server.py,输出: b'hello'

    上面的代码,执行一次就结束了,改成多次执行的。

    server.py内容如下:

    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9090))
    while True:
        msg,client_addr = sk.recvfrom(1024)
        print(msg.decode('utf-8'))
        inp = input('>>>')
        sk.sendto(inp.encode('utf-8'),client_addr)
    sk.close()

    client.py内容如下:

    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    while True:
        inp = input('>>>').strip()
        sk.sendto(inp.encode('utf-8'),('127.0.0.1',9090))
        msg,addr = sk.recvfrom(1024)
        print(msg.decode('utf-8'))
    sk.close()

    在udp中,数据必须是server端先接收

    先执行server.py,再执行client.py,效果如下:

    client和server可以相互通信。如果再新开一个client,也是可以和server通信的。

    那么问题来了,如果2个client,同时发给server,server先不回复。

    最后再回复时,server会回复给谁呢?

    在网络中,是有先后顺序的。即是是0.0001秒的差距,谁最后给server,那么server就会回复给它。

    再增加一个功能,显示人名。

    修改client.py,内容如下:

    import socket
     
    sk = socket.socket(type=socket.SOCK_DGRAM)
    name = input('请输入名字: ')
    while True:
        inp = input('请输入发送内容: ')
        sk.sendto(('%s : %s'%(name,inp)).encode('utf-8'),('127.0.0.1',9090))
        msg,addr = sk.recvfrom(1024)
        print(msg.decode('utf-8'))
     
    sk.close()

    server.py不需要修改。先执行server.py,再执行client.py,效果如下:

    再增加一个功能,人名显示不同的颜色。

    修改server.py,内容如下:

    import socket
    lst = {'egon':'33[1;31m','yuan':'33[1;34m'}
    sk = socket.socket(type=socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9090))
    while True:
        msg,client_addr= sk.recvfrom(1024)   # udp协议不用建立链接
        name,mesg = msg.decode('utf-8').split(':')
        color = lst.get(name.strip(),'')
        print('%s%s33[0m'%(color,msg.decode('utf-8')))
        inp = input('>>>')
        sk.sendto(inp.encode('utf-8'),client_addr)
     
    sk.close()

    client.py不需要修改,先执行server.py,再执行client.py,效果如下:

    切换到eva和yuan,就会有不同的颜色显示。

    现在有一个需求,utf-8转码很麻烦,能不能自动转换呢?

    是可以的,写一个类,这个类继承了socket,它能满足定制需求。

    新建一个文件mysocket.py,内容如下:

    from socket import *  # 导入socket模块
    class Mysocket(socket):  # 继承socket
        def __init__(self,coding='utf-8'):  # 默认编码为utf-8
            self.coding = coding
            super().__init__(type=SOCK_DGRAM)  # 设定为udp协议
        def my_recv(self,num):  # num表示最大字节,比如1024
            msg,addr = self.recvfrom(num)
            return msg.decode(self.coding),addr  # 返回解码后的接收信息
        def my_send(self,msg,addr):  # msg和addr分别表示发送信息和连接ip:端口
            return self.sendto(msg.encode(self.coding),addr)  # 发送编码后的信息

    修改server.py,内容如下:

    from mysocket import Mysocket
    sk = Mysocket()  # 可以指定编码,默认为utf-8
    lst = {'eva':'33[1;31m','yuan':'33[1;34m'}
    sk.bind(('127.0.0.1',9090))
    while True:
        msg,client_addr= sk.my_recv(1024)   # udp协议不用建立链接
        name,mesg = msg.split(':')
        color = lst.get(name.strip(),'')
        print('%s%s33[0m'%(color,msg))
        inp = input('>>>')
        sk.my_send(inp,client_addr)
     
    sk.close()

    client.py,内容如下:

    from mysocket import Mysocket
    sk = Mysocket()
    name = input('请输入名字: ')
    while True:
        inp = input('请输入发送内容: ')
        sk.my_send(('%s : %s'%(name,inp)),('127.0.0.1',9090))
        msg,addr = sk.my_recv(1024)
        print(msg)
     
    sk.close()

    先执行server.py,再执行client,py。执行效果同上!

    时间同步服务                                                                                                                    
    基于udp协议完成的
    公司有4,5台服务器,00:00 执行任务,从一个文件里面 读取一些数据,作为明天的搜索关键字。
    假如有一台服务器,慢了1小时,那么就会影响客户端访问。
    现在机房里面,有一台标准时间的服务器。
    机房里所有的机器,每隔一段时间,就去请求这台服务器,来获取一个标准时间

    比如一个有标准时间的server,client可以从server获取标准时间,需要使用udp协议。
    server不能关,它必须先接收客户端的ip,端口,信息,它才能将新消息,发给指定的人。

    修改server.py,内容如下:

    import time
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9090))
    while True:
        msg,addr = sk.recvfrom(1024)
        sk.sendto(time.strftime(msg.decode('utf-8')).encode('utf-8'),addr)
    sk.close()

    修改client.py,内容如下:

    import time
    import socket
     
    sk = socket.socket(type=socket.SOCK_DGRAM)
    while True:
        sk.sendto('%Y/%m/%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9090))  # 执行时间格式
        ret,addr = sk.recvfrom(1024)
        print(ret.decode('utf-8'))
        time.sleep(1)  # 暂停1秒执行
    sk.close()

    先执行server.py,再执行client,py,执行输出:

    2018/05/04 16:33:08
    2018/05/04 16:33:09
    2018/05/04 16:33:10

    ...

    在真正的工作中,不会让client 一直whilte true的
    是用linux任务计划执行的

    socket参数的详解

    socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)

    创建socket对象的参数说明:

    family 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
    (AF_UNIX 域实际上是使用本地 socket 文件来通信)
    type 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。
    SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 
    SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
    proto 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。
    fileno 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。
    与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。
    这可能有助于使用socket.close()关闭一个独立的插座。

    最主要用到的参数是type

    看下图:

    左边是TCP,右边是UDP

    对比可以看出,UDP的步骤比较少一点。

    熟练掌握以下3个例子
    时间同步
    tcp协议的聊天
    udp协议的聊天

    周一默写 重写socket类的那一套,有3个文件。

    server.py,内容如下:

    from mysocket import Mysocket
     
    sk = Mysocket()
    sk.bind(('127.0.0.1',9090))
    while True:
        msg,client_addr= sk.my_recv(1024)   # udp协议不用建立链接
        print(msg)
        if msg == 'q':sk.close()
        inp = input('>>>')
        sk.my_send(inp,client_addr)
        if inp == 'q':sk.close()
    sk.close()

    mysocket.py,内容如下:

    from socket import *
    class Mysocket(socket):
        def __init__(self,coding='utf-8'):
            self.coding = coding
            super().__init__(type=SOCK_DGRAM)
     
        def my_recv(self,num):
            msg,addr = self.recvfrom(num)
            return msg.decode(self.coding),addr
     
        def my_send(self,msg,addr):
            return self.sendto(msg.encode(self.coding),addr)

    client.py,内容如下:

    from mysocket import Mysocket
     
    sk = Mysocket()
    while True:
        inp = input('>>>')
        sk.my_send(inp,('127.0.0.1',9090))
        msg,addr = sk.my_recv(1024)
        print(msg)
     
    sk.close()
    

    周末作业:

    1.继续完成聊天  使用udp协议
        加上重写socket类 + 名字 + 颜色
        进阶功能:
            1.添加好友,删除好友。
            2.client简单登录,用来表明身份,以登录用户来聊天。
     
    2.cs架构的程序的登陆
        ftp做准备
        hashilib密文   client端简单加密一次 + server端加密
        使用tcp协议
        进阶功能:
            1.远程创建文件夹

    第一个作业,请参考文章

    http://www.py3study.com/Article/details/id/237.html

     第二个作业,请参考文章

    http://www.py3study.com/Article/details/id/238.html

  • 相关阅读:
    PHPUnit断言详情
    dev tools杂谈
    sublime text3 + python3.5 ide
    文件传输接收sz、rz命令用法
    Linux编辑器vi使用方法详细介绍
    vi编辑字符串替换及sed用法
    Selenium实战(一)——浏览器实例
    Python的数据库连接
    Cypress测试框架——一个简单的测试用例
    Cypress测试框架——入门1
  • 原文地址:https://www.cnblogs.com/tu240302975/p/12695201.html
Copyright © 2020-2023  润新知