zoukankan      html  css  js  c++  java
  • 网络编程之socket

    基于TCP的套接字

    tcp服务端

    ss = socket()                #创建服务器套接字
    ss.bind()                    #把地址绑定到套接字
    ss.listen()                  #监听链接
    inf_loop:                    #服务器无限循环
       cs = ss.accept()         #接受客户端链接
       comm_loop:             #通讯循环
           cs.recv()/cs.send() #对话(接收与发送)
       cs.close()               #关闭客户端套接字
    ss.close()                   #关闭服务器套接字(可选)

    tcp客户端

    cs = socket()                # 创建客户套接字
    cs.connect()           # 尝试连接服务器
    comm_loop:           # 通讯循环
    cs.send()/cs.recv()     # 对话(发送/接收)
    cs.close()                 # 关闭客户套接字

    TCP解决粘包

    TCP粘包原因:接收方不知道到底要收多少数据

    import socket
    import subprocess
    import struct
    import json
    
    server = socket.socket()
    server.bind(('127.0.0.1',8081))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        print('连接成功')
        while True:
            try:
                cmd = conn.recv(1024)
                print('接收成功')
                # tcp客户端发空,会导致服务端夯住(udp不存在此问题,因为自带报头,为地址和端口的信息)
                if len(cmd) == 0:break
                cmd = cmd.decode('utf-8')
                #利用subprocess开启新进程,可接收命令,并调用shell去执行这个字符串
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                #res接收subprocess的shell标准化输出和标准化报误信息
                res = obj.stdout.read() + obj.stderr.read()
                #将模块返回的信息的长度,和其他需要提供的信息,做出字典
                d = {'file_size':len(res),'info':'xxxxxxx'}
                #字典序列化
                json_d = json.dumps(d)
                # 1.先制作一个提示客户端,将要发送的字典长度的报头
                # (struct模块,将int类型的长度,转化为二进制字符串,只占4字节)
                header = struct.pack('i',len(json_d))
                # header2=struct.pack('i',len(json_d.encode('utf8'))) 与上句效果相同,算的都是bytes长度
    
                # 2.发送字典报头
                conn.send(header)
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))
                # 4.再发真实数据
                conn.send(res)
                # conn.send(obj.stdout.read())
                # conn.send(obj.stderr.read())
            except ConnectionResetError:
                break
        conn.close()
    服务端
    import socket
    import struct
    import json
    
    client = socket.socket()
    client.connect(('127.0.0.1',8081))
    print('连接成功')
    while True:
        msg = input('>>>:').encode('utf-8')
        # tcp客户端发空,会导致服务端夯住(udp不存在此问题,因为自带报头,为地址和端口的信息)
        if len(msg) == 0:continue
        client.send(msg)
        # 1.先接收字典报头
        header_dict = client.recv(4)
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
        # 4.从字典中获取信息
        recv_size = 0
        real_data = b''
        # 必须是 < ,最后一次若本来可以刚好发完 即 recv_size = dict_json,大不了再接收一次
        # 若改为 = ,最后一次本来收完,却还满足判断条件,收到的就是空了,会夯住
        while recv_size < dict_json.get('file_size'):
            data = client.recv(1024)
            real_data += data
            recv_size += len(data)
        print(real_data.decode('gbk'))
    
    
    """
    如何将对方发送的数据收干净
    """
    客户端

     发送大文件

    import socket
    import os
    import json
    import struct
    
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    while True:
        conn,addr = server.accept()
        while True:
            try:
                header_len = conn.recv(4)
                # 解析字典报头
                header_len = struct.unpack('i',header_len)[0]
                # 再接收字典数据
                header_dic = conn.recv(header_len)
                real_dic = json.loads(header_dic.decode('utf-8'))
                # 获取数据长度
                total_size = real_dic.get('file_size')
                # 循环接收并写入文件
                recv_size = 0
                with open(real_dic.get('file_name'),'wb') as f:
                    while recv_size < total_size:
                        data = conn.recv(1024)
                        f.write(data)
                        recv_size += len(data)
                    print('上传成功')
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()
    服务端
    import socket
    import json
    import os
    import struct
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    
    while True:
        # 获取电影列表 循环展示
        MOVIE_DIR = r'D:python脱产10期视频day25视频'
        movie_list = os.listdir(MOVIE_DIR)
        # print(movie_list)
        for i,movie in enumerate(movie_list,1):
            print(i,movie)
        # 用户选择
        choice = input('please choice movie to upload>>>:')
        # 判断是否是数字
        if choice.isdigit():
            # 将字符串数字转为int
            choice = int(choice) - 1
            # 判断用户选择在不在列表范围内
            if choice in range(0,len(movie_list)):
                # 获取到用户想上传的文件路径
                path = movie_list[choice]
                # 拼接文件的绝对路径
                file_path = os.path.join(MOVIE_DIR,path)
                # 获取文件大小
                file_size = os.path.getsize(file_path)
                # 定义一个字典
                res_d = {
                    'file_name':'性感荷官在线发牌.mp4',
                    'file_size':file_size,
                    'msg':'注意身体,多喝营养快线'
                }
                # 序列化字典
                json_d = json.dumps(res_d)
                json_bytes = json_d.encode('utf-8')
    
                # 1.先制作字典格式的报头
                header = struct.pack('i',len(json_bytes))
                # 2.发送字典的报头
                client.send(header)
                # 3.再发字典
                client.send(json_bytes)
                # 4.再发文件数据(打开文件循环发送)
                with open(file_path,'rb') as f:
                    for line in f:
                        client.send(line)
            else:
                print('not in range')
        else:
            print('must be a number')
    客户端

     

    socket收发消息原理剖析

    # import socket
    # 把后期经常改动的变量提取出来 
    # 一般不提倡import*,socket比较特殊
    # 都导入进来后就不需要每次socket.来调用方法了
    from socket import *
    ip_port=('127.0.0.1',8083)
    back_log=5
    buffer_size=1024
    ​
    tcp_server=socket(AF_INET,SOCK_STREAM)
    tcp_server.bind(ip_port)
    tcp_server.listen(back_log)
    ​
    while True:
      #循环的等待接收服务
        print('服务端开始运行了')
        conn,addr=tcp_server.accept() #服务端阻塞等待链接 
        print('双向链接是',conn)  #链接信息
        print('客户端地址',addr)  #地址+端口
    while True:
        #循环的为这个链接服务
            try:
                data=conn.recv(buffer_size)
                print('客户端发来的消息是',data.decode('utf-8'))
                conn.send(data.upper())
            except Exception:
                break
        conn.close()
    ​
    tcp_server.close()
    服务端
    # import socket
    from socket import *
    ip_port=('127.0.0.1',8083)
    back_log=5
    buffer_size=1024
    ​
    tcp_client=socket(AF_INET,SOCK_STREAM)
    tcp_client.connect(ip_port)
    ​
    while True:
        msg=input('>>: ').strip()
        if not msg:continue #如果客户端输入为空,重新回到输入
        tcp_client.send(msg.encode('utf-8'))
        print('客户端已经发送消息')
        data=tcp_client.recv(buffer_size)
        print('收到服务端发来的消息',data.decode('utf-8'))
    ​
    tcp_client.close()
    ​
    # recv(1024),其实是在内核态内存收消息
    # send(msg),其实是消息从用户态内存拷贝到内核态内存
    客户端

    服务端循环链接请求+循环收发消息

    堆结构:先进先出,吃了拉 先吃进去的先拉出来 python中的堆结构为队列 栈结构:先进后出,后进先出 吃了吐,后面吃的先吐出来 python中没有专门的栈结构

    客户端直接终止:四次挥手都没有,直接中断的,而服务端本来等着收,conn突然被干掉了,recv就没意义了(远程主机强迫关闭了一个现有连接)

    C/S架构的美好愿望,服务端启动后,永远运行下去,服务很多人

    循环提供服务 如果服务端正在为客户端a提供服务,那么服务端进入 “为a链接服务”的循环中,此时这个循环还没结束,为a链接服务的conn.close()没有执行,回不到上一个循环,即无法接收新的会话连接 此时客户端b试图建立链接,send到自己的内核态内存,并发往服务器,进入服务器的block_log连接池中挂起。 等对a的服务进程完成断开后,进入下一个接收连接循环,服务端的内核态内存才会把挂起的“b连接”发给用户态内存,服务端程序才能接收并进入服务循环

    客户端b试图想建立链接,这时候客户端如果发了一条消息,其实发消息就已经进入客户端的循环,只是服务端没有回应,客户端的内核态内存没新数据,故客户端的tcp_client.recv(buffer_size)没有收到信息,自然前面的data= 就没有被赋值,再次就中断等待了

    此时手动结束客户端a进程,服务端conn被干掉,报错:远程主机强迫关闭了一个现有连接,服务端进程卡死。故需要在 为a连接服务的 循环,加一个异常处理 ,遇到问题 break,继续运行退出循环

    避免通信循环中的报错
    1.服务端在conn.recv()下一句
    用if 为空 :break 解决conn断开,服务端一直收空消息造成的死循环
    2.服务端在通信循环 用处理客户端进程非正常中断造成的报错(远程主机强迫关闭了一个现有的连接)
    try:
    #通信循环
    except Exception as e:
      print(e)
      break
    import socket
    """
    服务端
        固定的ip和port
        24小时不间断提供服务
    """
    server = socket.socket()  # 生成一个对象
    server.bind(('127.0.0.1',8080))  # 绑定ip和port
    server.listen(5)  # 半连接池
    while True:
        conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道
        print(addr)  # ('127.0.0.1', 51323) 客户端的地址
        while True:
            try:
                data = conn.recv(1024)
                print(data)  # b''  针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b''
                if len(data) == 0:break
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()
    服务端
    import socket
    ​
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    ​
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:continue
        client.send(msg)
        data = client.recv(1024)
        print(data)
    客户端

    recv谁发起的:由用户态内存中的应用程序发起的,可以是客户端,可以使服务端 最多收到设定的字节数

    TCP服务端防止收空

    若客户端直接回车,即发了一个None,客户端send空到客户端内核态内存 而服务端的内核态缓存,没东西。,自然卡住了,后面的send就不会执行。客户端在send空后,会一直等着收内核态缓存的信息,而客户端没收到新消息,所以也卡住了

    卡住recv 是由于:内核态缓存没东西,跟对方没关系

    linux、unix遇到客户端终端,服务端不会抛出异常,而是recv会一直接收None,跟windows 不一样

    解决方法:

    while True:
       #循环的为这个链接服务
               data=conn.recv(buffer_size)
               if not data:break #如果收到的是空,退出循环
               print('客户端发来的消息是',data.decode('utf-8'))
               conn.send(data.upper())
       conn.close()

    基于UDP的套接字

    UDP服务端


    ss = socket()   #创建一个服务器的套接字
    ss.bind()       #绑定服务器套接字
    inf_loop:       #服务器无限循环
    cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    ss.close()                     # 关闭服务器套接字

    没有listen:listen是用来挂连接请求的,半链接池,UDP没有链接 少了一个链接循环:不需要链接

    UDP客户端

    cs = socket()                  # 创建客户套接字
    comm_loop:       # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
    cs.close()                     # 关闭客户套接字

    没有绑定:直接创建套接字,不需要绑定 只需要通讯循环

    UDP服务端

    from socket import *
    ip_port=('127.0.0.1',8080)
    buffer_size=1024
    ​
    udp_server=socket(AF_INET,SOCK_DGRAM) #数据报
    udp_server.bind(ip_port)
    ​
    while True:
        data,addr=udp_server.recvfrom(buffer_size)
        #客户端发的是(二进制内容,ip_port),所以收到的是元组
        print(data)
    ​
        udp_server.sendto(data.upper(),addr)
    UDP服务端
    from socket import *
    ip_port=('127.0.0.1',8080) #服务端的
    buffer_size=1024
    ​
    udp_client=socket(AF_INET,SOCK_DGRAM) #数据报
    while True:
        msg=input('>>: ').strip()
        udp_client.sendto(msg.encode('utf-8'),ip_port)
        #没有链接,每次要指定发给哪个ip端口
        #发的是(二进制内容,ip_port)的元组
        data,addr=udp_client.recvfrom(buffer_size)
        # 收到的也是(二进制内容,ip_port)的元组
        # print(data.decode('utf-8'))
        print(data)
    UDP服务端

    recv与recvfrom的区别

    tcp收和udp收,都是从自己的缓冲区拿内容,tcp收不到空内容,udp却可以收到(None,ip_port)元组 recv在自己的缓冲区为空时,阻塞 recvfrom在收到一个空内容时,虽然内容为空,但是带了ip_port的报文头

    TCP 一端发完一个包后,收到对面的ACK确认收到响应,才会在内核缓冲区把数据清空 socket用户态内存发给内核态内存,本质是copy操作

    UDP不依赖于服务端 UDP没有链接,天然实现会话的并发

     

    socketserver模块

    TCP的socketserver

    import socketserver
    ​
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            # print('来啦 老弟')
            while True:
                data = self.request.recv(1024)
                print(self.client_address)  # 客户端地址
                print(data.decode('utf-8'))
                self.request.send(data.upper())
    ​
    if __name__ == '__main__':
        """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
        server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
        server.serve_forever()  # 启动该服务对象
    服务端

    连续开多个客户端进行验证

    import socket
    ​
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    ​
    while True:
        client.send(b'hello')
        data = client.recv(1024)
        print(data.decode('utf-8'))
    客户端

    UDP的socketserver

    import socketserver
    ​
    class MyServer(socketserver.BaseRequestHandler):
        def handle(self):
            # print('来啦 老弟')
            while True:
                data,sock = self.request
                print(self.client_address)  # 客户端地址
                print(data.decode('utf-8'))
                sock.sendto(data.upper(),self.client_address)
    ​
    if __name__ == '__main__':
        """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
        server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
        server.serve_forever()  # 启动该服务对象
    服务端

    连续开多个客户端进行验证

    import socket
    import time
    ​
    client = socket.socket(type=socket.SOCK_DGRAM)
    server_address = ('127.0.0.1',8081)
    ​
    while True:
        client.sendto(b'hello',server_address)
        data,addr = client.recvfrom(1024)
        print(data.decode('utf-8'),addr)
        #睡一秒,不然速度太快,收不到其余客户端的消息
        time.sleep(1) 
    客户端
  • 相关阅读:
    Lua_第 20 章 IO库
    maven具体解释之坐标与依赖
    用python做自己主动化測试--对Java代码做单元測试 (1)
    OSG粒子系统应用:雨雪效果
    Snort:Barnyard2+MySQL+BASE 基于Ubuntu 14.04SNORT
    shiro高速入门
    解决Cocos项目中遇到的fatal error c1083(无法打开包含文件)
    解决TIME_WAIT过多造成的问题
    Web后端语言模拟http请求(带username和password)实例代码大全
    Python
  • 原文地址:https://www.cnblogs.com/lishuaing/p/11321835.html
Copyright © 2011-2022 走看看