美文网首页
网络编程-socket

网络编程-socket

作者: Snackk | 来源:发表于2018-09-19 15:26 被阅读0次

    网络开发的框架

    • C/S
    • B/S 统一了程序的入口 (浏览器)
    osi七层模型

    TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

    UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

    tcp协议的socket进行通信
    • 对于TCP协议的socket server来说

    server端
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
    sk.listen() #监听链接
    conn,addr = sk.accept() #接受客户端链接
    ret = conn.recv(1024) #接收客户端信息
    print(ret) #打印客户端信息
    conn.send(b'hi') #向客户端发送信息
    conn.close() #关闭客户端套接字
    sk.close() #关闭服务器套接字(可选)

    client端
    import socket
    sk = socket.socket() # 创建客户套接字
    sk.connect(('127.0.0.1',8898)) # 尝试连接服务器
    sk.send(b'hello!')
    ret = sk.recv(1024) # 对话(发送/接收)
    print(ret)
    sk.close() # 关闭客户套接字

    • 不能同时接受多个client端的连接
    server端                                           client端
    import socket                                      import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    sk = socket.socket() #实例化一个对象(买手机)
    sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#在bind前加(复用端口,测试用)
    sk.bind(('ip地址',端口号)) #(买卡)
    sk.listen(数字(1024)) #(开机)以上为启动一个socket服务
    
    conn,addr = sk.accept()  # 三次握手                 sk.connect((服务端ip,服务端口号))
    conn.send(bytes类型的内容) #发送消息                  sk.recv(数字)
    msg = conn.recv(数字)    #接受消息                   sk.send(bytes的消息)
    ...打印 操作
    conn.close()     #断开与client端的链接(4次挥手)        sk.close()
    
    sk.close()   #关闭服务器端服务
    
    
    
    #  server服务器端
    
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(('127.0.0.1',10000))
    sk.listen()
    while True:
        try:
            conn,addr = sk.accept()
            while True:
                msg = input(':')
                conn.send(msg.encode('utf-8'))
                if msg == 'exit':break
                msg = conn.recv(1024)
                if msg == b'exit':break
                print(msg.decode('utf-8'))
            conn.close()
        except UnicodeDecodeError:
            pass
    sk.close()
    
    #  client端
    
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',10000))
    while True:
        mag = sk.recv(1024)
        if mag == b'exit':break
        print(mag.decode('utf-8'))
        mag = input(':')
        sk.send(mag.encode('utf-8'))
        if mag == 'exit':break
    sk.close()
    
    

    文件的一次性传送

    #client端
    import time
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1',10000))
    sk.send('文件名'.encode('utf-8'))
    time.sleep(0.1)
    with open('文件绝对路径','rb') as f:
        sk.send(f.read())
    sk.close()
    
    #server服务器端
    import socket
    sk = socket.socket()
    sk.bind(('127.0.0.1',9999))
    sk.listen()
    conn,addr = sk.accept()
    filename = conn.recv(1024)
    with open(filename,'wb') as f:
        while True:
            try:
                f1 = conn.recv(1024)
                f.write(f1)
            except Exception:
                break
        conn.close()
    sk.close()
    
    
    • 文件的分次传送
    # 服务器端
    import socket
    def sky(ip,port):
        i = 0
        count = 1
        sk = socket.socket()
        sk.bind((ip,port))
        sk.listen()
        conn,addr = sk.accept()
        filename = conn.recv(1024)
        butter = int(conn.recv(1024))
        with open(filename,'wb') as f:
            while count:
                try:
                    count = conn.recv(butter)
                    f.write(count)
                    i += 1
                    print('\r%s' % i,end = '')
                except Exception:
                    break
            conn.close()
        sk.close()
    
    sky('127.0.0.1',9999)
    
    # client 端
    import os
    import time
    import socket
    def use(filename,ip,port,butter = 1024):
        sk = socket.socket()
        sk.connect((ip,port))
        sk.send(os.path.basename(filename).encode('utf-8'))
        time.sleep(0.01)
        sk.send(str(butter).encode('utf-8'))
        time.sleep(0.01)
        file_size = os.path.getsize(filename)
        with open(filename,'rb') as f:
            while file_size:
                count = f.read(butter)
                sk.send(count)
                time.sleep(0.0000001)
                file_size -= len(count)
        sk.close()
    use(r'D:\BaiduNetdiskDownload\day17\项目架构讲解.mp4','127.0.0.1',9999,102400)
    
    udp协议的socket进行通信
    • 基本结构

    server端
    import socket
    udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字
    udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
    msg,addr = udp_sk.recvfrom(1024)
    print(msg)
    udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
    udp_sk.close() # 关闭服务器套接字

    client端
    import socket
    ip_port=('127.0.0.1',9000)
    udp_sk=socket.socket(type=socket.SOCK_DGRAM)
    udp_sk.sendto(b'hello',ip_port)
    back_msg,addr=udp_sk.recvfrom(1024)
    print(back_msg.decode('utf-8'),addr)

    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.bind(('ip地址',端口号))
    对方发给你的消息,对方的地址 = udp_sk.recvfrom(接受的字节数)
    sk.sendto(b'要发给对方的消息',对方的地址)
    sk.close()
    
    • low版
    # 服务端
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9999))
    mag,addr = sk.recvfrom(1024)
    print(mag.decode('utf-8'),end = '')
    print(addr)
    sk.sendto('你好'.encode('utf-8'),addr)
    sk.close()
    
    # 客户端
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.sendto('你好'.encode('utf-8'),('127.0.0.1',9999))
    mag,addr = sk.recvfrom(1024)
    print(mag.decode('utf-8'),end='')
    print(addr)
    sk.close()
    
    • 还是low版
    # 服务端
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    sk.bind(('127.0.0.1',9999))
    
    while True:
        mag,addr = sk.recvfrom(1024)
        print(mag.decode('utf-8'))
        mag = input('>>:')
        sk.sendto(mag.encode('utf-8'),addr)
    sk.close()
    
    # 客户端
    import socket
    sk = socket.socket(type = socket.SOCK_DGRAM)
    while True:
        mag = input('>>:')
        sk.sendto(mag.encode('utf-8'),('127.0.0.1',9999))
        mag,addr = sk.recvfrom(1024)
        print(mag.decode('utf-8'))
    sk.close()
    
    • 粘包现象 -->优化方法(Nagle算法)
    • 只有TCP有粘包现象,UDP永远不会粘包

    由于流式传输的特点 产生了数据连续发送的粘包现象
    在一个conn建立起来的连接上传输的多条数据是没有边界的
    数据的发送和接收实际上不是在执行send/recv的时候就立刻被发送或者接收
    而是需要经过操作系统内核

    tcp: 面向流的通信是无消息保护边界的,对于空消息:tcp是基于数据流的,于是收发的消息不能为空

    Nagle算法 能够将发送间隔时间得很近的短数据合成一个包发送到接收端

    拆包机制 ,当要发送的数据超过了网络上能传输的最大长度,就会被tcp协议强制拆包

    udp:是无连接的,面向消息,基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y)

    如果是短数据 :只需要告诉对方边界就可以了
    如果是长数据 :不仅要告诉对方边界,还要保证对面完整的接受了

    • udp协议中是不会发生粘包现象的
      适合短数据的发送
      不建议你发送过长的数据
      会增大你数据丢失的几率

    • 会发生黏包的两种情况

      • 1,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
    # 服务端
    import socket
    ip_port=('127.0.0.1',8081)
    sk = socket.socket()
    sk.bind(ip_port)
    sk.listen()
    conn,addr = sk.accept()
    data1=conn.recv(10)
    data2=conn.recv(10)
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    conn.close()
    # 客户端
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8081)
    sk = socket.socket()
    sk.connect(ip_port)
    sk.send('hello'.encode('utf-8'))
    sk.send('egg'.encode('utf-8'))
    
    • 2 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
    # 客户端
    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(ip_port)
    s.send('hello egg'.encode('utf-8'))
    # 服务端
    from socket import *
    ip_port=('127.0.0.1',8080)
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)  #最多接受5个客户端的链接
    conn,addr=tcp_socket_server.accept()
    data1=conn.recv(2) #一次没有收完整
    data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))
    conn.close()
    
    • 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
    • 使用struct解决黏包
    • 该模块可以把一个类型,如数字,转成固定长度的bytes
    • 借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

    struct.pack('i',1111111111111)
    struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
    i int 型
    l long 型
    f folat型

    • struct实现结构
    import struct
    ret = struct.pack('i',1023800976)
    print(ret,len(ret))
    num = struct.unpack('i',ret)
    print(num[0])# num是一个元祖,应该取缔一个元素
    
    • 实现基本结构
    # 客户端
    import socket
    import struct
    sk = socket.socket()
    sk.connect(('127.0.0.1',9999))
    mag = '借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。'.encode('utf-8')
    num = struct.pack('i',len(mag))
    sk.send(num)
    sk.send(mag)
    sk.close()
    
    # 服务端
    import socket
    import struct
    sk = socket.socket()
    sk.bind(('127.0.0.1',9999))
    sk.listen(3)
    conn,addr = sk.accept()
    num = conn.recv(4)
    num = struct.unpack('i',num)[0]
    mag = conn.recv(num).decode('utf-8')
    print(mag)
    conn.close()
    sk.close()
    

    服务端套接字函数
    s.bind() 绑定(主机,端口号)到套接字
    s.listen() 开始TCP监听
    s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

    客户端套接字函数
    s.connect() 主动初始化TCP服务器连接
    s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

    公共用途的套接字函数
    s.recv() 接收TCP数据
    s.send() 发送TCP数据
    s.sendall() 发送TCP数据
    s.recvfrom() 接收UDP数据
    s.sendto() 发送UDP数据
    s.getpeername() 连接到当前套接字的远端的地址
    s.getsockname() 当前套接字的地址
    s.getsockopt() 返回指定套接字的参数
    s.setsockopt() 设置指定套接字的参数
    s.close() 关闭套接字

    面向锁的套接字方法
    s.setblocking() 设置套接字的阻塞与非阻塞模式 ,默认True(阻塞) 设置成False就不阻塞了,没人连就会报错(用try-excpet解决)
    s.settimeout() 设置阻塞套接字操作的超时时间
    s.gettimeout() 得到阻塞套接字操作的超时时间

    面向文件的套接字的函数
    s.fileno() 套接字的文件描述符
    s.makefile() 创建一个与该套接字相关的文件

    验证客户端链接的合法性
    # 服务端
    import os
    import hmac
    import socket
    def auth(conn):
        msg = os.urandom(32)  # 生成一个随机的32位字节字符串
        conn.send(msg)  # # 发送到client端
        result = hmac.new(secret_key, msg)  # 处理这个随机字符串,得到一个结果
        client_digest = conn.recv(1024)  # 接收client端处理的结果
        if result.hexdigest() == client_digest.decode('utf-8'):
            print('是合法的连接')  # 对比成功可以继续通信
            return True
        else:
            print('不合法的连接')  # 不成功 close
            return False
    
    secret_key = b'alex_sb'
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()
    conn,addr = sk.accept()
    if auth(conn):
        print(conn.recv(1024))
        # 正常的和client端进行沟通了
        conn.close()
    else:
        conn.close()
    sk.close()
    
    # 客户端
    import hmac
    import socket
    def auth(sk):
        msg = sk.recv(32)
        result = hmac.new(key, msg)
        res = result.hexdigest()
        sk.send(res.encode('utf-8'))
    
    key = b'alex_s'
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    auth(sk)
    sk.send(b'upload')
    # 进行其他正常的和server端的沟通
    sk.close()
    
    
    • socketserver模块
    import socketserver
    # tcp协议的server端就不需要导入socket,socketserver中导入了socket模块
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            conn = self.request   #self.data = self.request.recv(1024).strip()
            while True:
                conn.send(b'hello')
                print(conn.recv(1024))
    
    server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
    server.serve_forever()
    
    # 这个类不用实例化也不用调用类中的handle方法
    

    相关文章

      网友评论

          本文标题:网络编程-socket

          本文链接:https://www.haomeiwen.com/subject/lpyinftx.html