• 网络编程socket


    socket

    socket解释

    socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。

    应用程序通常通过"套接字"向网络发出请求或者应答网络请求。说白了就是一种通信机制。

    socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。

    socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

    socket和file的区别:

    • file模块是针对某个指定文件进行【打开】【读写】【关闭】 

    • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

     更多功能

    sk.bind(address)

      s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

    sk.listen(backlog)

      开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

          backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
          这个值不能无限大,因为要在内核中维护连接队列

    sk.setblocking(bool)

      是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

    sk.accept()

      接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

      接收TCP 客户的连接(阻塞式)等待连接的到来

    sk.connect(address)

      连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

    sk.connect_ex(address)

      同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

    sk.close()

      关闭套接字

    sk.recv(bufsize[,flag])

      接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

    sk.recvfrom(bufsize[.flag])

      与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

    sk.send(string[,flag])

      将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

    sk.sendall(string[,flag])

      将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

          内部通过递归调用send,将所有内容发送出去。

    sk.sendto(string[,flag],address)

      将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

    sk.settimeout(timeout)

      设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

    sk.getpeername()

      返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

    sk.getsockname()

      返回套接字自己的地址。通常是一个元组(ipaddr,port)

    sk.fileno()

      套接字的文件描述符

    s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
    s.listen(backlog)   开始监听传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
    
    s.connect(address)  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接同一台机器上的服务器,可以将hostname设为‘localhost’。如果连接出错,返回socket.error错误。
    
    s.connect_ex(adddress)  功能与connect(address)相同,但是成功返回0,失败返回errno的值。
    
    s.accept() 接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
    
    s.close()  关闭套接字。
    
    s.fileno()  返回套接字的文件描述符。
    
    s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    
    s.getsockname()  返回套接字自己的地址。通常是一个元组(ipaddr,port)
    
    s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
    
    s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
    
    s.recv(bufsize[,flag])  接受套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
    
    s.recvfrom(bufsize[.flag])  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
    
    s.send(string[,flag])  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
    
    s.sendall(string[,flag])  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
    
    s.sendto(string[,flag],address)  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
    
    s.setblocking(flag)  如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
    
    s.setsockopt(level,optname,value)   设置给定套接字选项的值。
    
    s.settimeout(timeout)   设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())普通的非套接字实例的函数
    
    getdefaulttimeout()返回默认的套接字超时时间(以秒为单位)。None表示不设置任何超时时间。
    
    gethostbyname(hostname)   将主机名(如“www.baidu.com”)转换为IPv4地址,IP地址将以字符串的形式返回,如“8.8.8.8”。不支持IPv6
    
    gethostname() 返回本地机器的主机名。
    更多功能

    建立连接

     单一的服务端与客户端建立连接

    server 服务端

    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9999,))
    sk.listen(5)
    
    while True:
        conn,address = sk.accept()
        print(address,conn)


    client 客户端

    import socket
    
    obj = socket.socket()
    
    obj.connect(('127.0.0.1', 9999,))
    
    obj.close()

    实例:

    (聊天机器人)

    服务端 :接受客户端的请求并返回给客户端数据。完成对话(聊天机器人)

    import socket
    
    sk = socket.socket()
    sk.bind(('127.0.0.1', 9999,))
    sk.listen(5)
    
    while True:
        conn,address = sk.accept()
        conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8'))
        while True:
            ret_bytes = conn.recv(1024)
            ret_str = str(ret_bytes,encoding='utf-8')
            if ret_str == 'q':
                break
            conn.sendall(bytes(ret_str + "你好", encoding='utf-8'))

    客户端:发送内容服务端会返回数据

    import socket
    
    obj = socket.socket()
    
    obj.connect(('127.0.0.1', 9999,))
    
    ret_bytes = obj.recv(1024)
    ret_str = str(ret_bytes, encoding='utf')
    print(ret_str)
    while True:
        inp = input('请输入要发送的内容:')
        if inp =='q':
            obj.sendall(bytes(inp, encoding='utf-8'))
            break
        else:
            obj.sendall(bytes(inp, encoding='utf-8'))
            ret = str(obj.recv(1024), encoding='utf-8')
            print(ret)
    obj.close()

    FTP上传

    服务端    

      发送返回值,防止粘包

    import socket
    
    sk = socket.socket()  #创建服务器套接字
    sk.bind(('127.0.0.1',9999,))  #绑定ip和端口,是元组
    sk.listen(5)  #监听链接;数值是表示一次性最多监听5个
    
    while True:
        #连接,客户端地址信息
        conn,address =sk.accept()  #在此阻塞,等待客户端的发送请求。accept阻塞
        conn.sendall(bytes("欢迎登录老男孩FTP", encoding='utf-8'))  #sendall表示发送数据,给客户端(只能发送字节)
        #先接收文件大小,然后再开始接收
        file_size = str(conn.recv(1024), encoding='utf-8')  #recv接受数据
    
        conn.sendall(bytes("ack",encoding='utf-8'))   ###发送一个返回值,进行一次交互,防止粘包
    
        # print(file_size)
        total_size = int(file_size)  #文件总大小
        has_recv = 0                 #已经接收的文件大小
        f = open('new.png','wb')
        #接收文件内容,直到获取完毕
        while True:
            if total_size == has_recv:
                break
            data = conn.recv(1024)  #接收
            f.write(data)
            has_recv += len(data)
    
        f.close()  #关闭服务端        

    客户端  

      接受服务端发来的返回值,防止粘包

    import socket
    import os
    
    obj = socket.socket()  #创建客户端套接字
    obj.connect(('127.0.0.1', 9999))  #尝试连接服务器
    
    ret_bytes = obj.recv(1024)  #在此阻塞,等待服务端发送数据。recv接收数据
    ret_str = str(ret_bytes, encoding='utf-8')
    print(ret_str)
    
    size = os.stat('f.png').st_size
    obj.sendall(bytes(str(size),encoding='utf-8')) #发送
    
    obj.recv(1024)   ####接收服务端发来的返回值,防止粘包。
    
    with open('f.png', 'rb') as f:
        for line in f:
            obj.sendall(line)  #发送数据
    
    obj.close()  #关闭客户端

     IO多路复用

    I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

    服务端

    import socket
    
    sk1 = socket.socket()
    sk1.bind(('127.0.0.1',8801))
    sk1.listen()
    
    sk2 = socket.socket()
    sk2.bind(('127.0.0.1',8802))
    sk2.listen()
    
    sk3 = socket.socket()
    sk3.bind(('127.0.0.1',8803))
    sk3.listen()
    
    inputs = [sk1,sk2,sk3, ]
    
    import select
    while True:
        # #[sk1,sk2,sk3],selsct内部自动监听sk1,sk2,sk3三个对象,一旦某个句柄发生变化
        # #如果有连接 sk1
        # #r_list = [sk1,sk2,sk3]
        # r_list,w_list,e_list = select.select(inputs,[],[],1)
        # for sk in r_list:
        #     #每一个连接对象
        #     conn,address = sk.accept()
        #     conn.sendall(bytes("hello",encoding="utf-8"))
        #     conn.close()
    
    
        r_list, w_list, e_list = select.select(inputs, [], inputs, 1)
    
        print('正在监听的socket对象%d' % len(inputs))
        print(r_list)
        for sk1_or_conn in r_list:
            #每一个连接对象
            if sk1_or_conn == sk1:
                #表示有新用户来连接
                conn, address = sk1_or_conn.accept()
                inputs.append(conn)
            else:
                #有老用户发消息
                try:
                    data_bytes = sk1_or_conn.recv(1024)
                    data_str = str(data_bytes,encoding="utf-8")
                    sk1_or_conn.sendall(bytes(data_str+"",encoding="utf-8"))
                except Exception as ex:
                    inputs.remove(sk1_or_conn)

    客户端1

    import socket
    
    obj = socket.socket()
    obj.connect(("127.0.0.1", 8801))
    
    content = str(obj.recv(1024),encoding="utf-8")
    print(content)
    
    
    
    obj.close()

    客户端2

    import socket
    
    obj = socket.socket()
    obj.connect(("127.0.0.1", 8802))
    
    content = str(obj.recv(1024),encoding="utf-8")
    print(content)
    
    obj.close()

    读写分离

    服务端

    import socket
    
    sk1 = socket.socket()
    sk1.bind(('127.0.0.1',8801))
    sk1.listen()
    
    sk2 = socket.socket()
    sk2.bind(('127.0.0.1',8802))
    sk2.listen()
    
    sk3 = socket.socket()
    sk3.bind(('127.0.0.1',8803))
    sk3.listen()
    
    inputs = [sk1,sk2,sk3, ]
    outputs = []
    message_dict = {}
    
    import select
    
    while True:
        r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1)
    
        print('正在监听的socket对象%d' % len(inputs))
        print(r_list)
        for sk1_or_conn in r_list:
            #每一个连接对象
            if sk1_or_conn == sk1:
                #表示有新用户来连接
                conn, address = sk1_or_conn.accept()
                inputs.append(conn)
                message_dict[conn] = []
            else:
                #有老用户发消息
                try:
    
                    data_str = str(data_bytes, encoding="utf-8")
                    message_dict[sk1_or_conn].append(data_str)
                    outputs.append(sk1_or_conn)
        for conn in w_list:
            recv_str = message_dict[conn][0]
            del message_dict[conn][0]
            conn.sendall(bytes(recv_str + "",encoding="utf-8"))
            outputs.remove(conn)
        for sk in e_list:
            inputs.remove(sk)

    客户端

    import socket
    
    obj = socket.socket()
    obj.connect(("127.0.0.1", 8801))
    while True:
        inp = input('>>>')
        obj.sendall(bytes(inp,encoding="utf-8"))
        ret = str(obj.recv(1024),encoding="utf-8")
        print(ret)
    
    obj.close()

    socketserver

    从而实现并发处理多个客户端请求的Socket服务端
     使用:
      1,创建类,必须继承(socketserver.BaseRequestHandler)
      2,handle方法
      3,server_forever 循环着等待着客户端连接

    服务端

      并发处理多个客户端请求     

    import socketserver  #进行并发处理,接受多个客户端的请求
    
    class MyServer(socketserver.BaseRequestHandler):
    
        def handle(self):
            conn = self.request
            conn.sendall(bytes('欢迎致电老男孩', encoding='utf-8'))
            while True:
                ret_bytes = conn.recv(1024)
                ret_str = str(ret_bytes,encoding='utf-8')
                if ret_str == 'q':
                    break
                conn.sendall(bytes(ret_str + " 你好", encoding='utf-8'))
    
    if __name__ == '__main__':
        server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999),MyServer)  #MYserver就是创建的类
        server.serve_forever()  #循环着等待着客户端连接

    客户端  

      多进程的与服务端连接

    import socket
    
    obj = socket.socket()
    
    obj.connect(('127.0.0.1', 9999,))
    
    ret_bytes = obj.recv(1024)
    ret_str = str(ret_bytes, encoding='utf')
    print(ret_str)
    while True:
        inp = input('请输入要发送的内容:')
        if inp =='q':
            obj.sendall(bytes(inp, encoding='utf-8'))
            break
        else:
            obj.sendall(bytes(inp, encoding='utf-8'))
            ret = str(obj.recv(1024), encoding='utf-8')
            print(ret)
    obj.close()

    ThredingTCPServer分析

    ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

    1,ThreadingTCPServer基础

    • 创建一个继承自 SocketServer.BaseRequestHandler 的类
    • 类中必须定义一个名称为 handle 的方法
    • 启动ThreadingTCPServer

    2,ThreadingTCPServer源码剖析

    内部调用流程为:

    • 启动服务端程序
    • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
    • 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给self.RequestHandlerClass
    • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
    • 当客户端连接到达服务器
    • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
    • 执行 ThreadingMixIn.process_request_thread 方法
    • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

    文件续传

    服务端

    import socket
    import os
    
    BASE_DIR=os.path.dirname(os.path.abspath(__file__))  #文件名的上一级目录
    
    home=os.path.join(BASE_DIR,"home/yuan")  #在拼接路径
    ip_port=("127.0.0.1",8998)
    sk=socket.socket()
    sk.bind(ip_port)
    sk.listen(5)
    
    while True:
        print("waiting ....")
        conn,addr=sk.accept()
        conn.sendall(bytes("欢迎登录","utf8"))
        flag=True
        while flag:
            client_bytes=conn.recv(1024)  #拿到客户端发来的文件名文件大小文件路径这三个参数
            client_str=str(client_bytes,"utf8")  #把获得的字节转换字符串
    
            func,file_name,file_byte_size,target_path=client_str.split("|",3)  #分解发来的三个参数
            file_byte_size=int(file_byte_size)  #
            path=os.path.join(home,file_name)  #拼接的路径
    
            has_received=0
    
            if os.path.exists(path):  #判断是否有文件
                conn.sendall(bytes("2003","utf8"))  #返回一个值做一次交互,表示文件存在
                is_continue=str(conn.recv(1024),"utf8") #等待客户端操作
                if is_continue=="2004":
                    has_file_size=os.stat(path).st_size  #得到上次上次传送的文件大小
                    conn.sendall(bytes(str(has_file_size),"utf8"))  #服务端发送给客户端,告诉上次传送的文件大小
                    has_received+=has_file_size  #把之前上传的文件与续传的文件进行想加
          
                    f=open(path,"ab")  #打开文件进行追加
                else:
                    f=open(path,"wb")  #否则就进行写入
            else:
                conn.sendall(bytes("2002","utf8"))  #
                f=open(path,"wb")
    
            while has_received<file_byte_size:  #判断发送过的文件与要上传文件进行比较,如果小于文件的大小就一直进行上传直到上传完成
                try:
                    data=conn.recv(1024) #对出现客户端进行中断的处理
                    if not data:
                        raise Exception
                except Exception:
                    flag=False
                    break
    
                f.write(data)
                has_received+=len(data)
            print("ending")
            f.close()

    客户端

    import socket
    import re,os,sys
    
    ip_port=("127.0.0.1",8998)
    sk=socket.socket()
    sk.connect(ip_port)
    print(str(sk.recv(1024),"utf8"))
    
    
    
    #def bar(num=1, sum=100):
    
    #    rate = float(num) / float(sum)
    #    rate_num = int(rate * 100)
    #  temp = '
    %d %%' % (rate_num, )
    #    sys.stdout.write(temp)
      #  sys.stdout.flush()
    while True:
        inp=input("please input:").strip()  #拿到输入值
        func,file_path=inp.split("|",1)  #用|分开方法名和路径
        local_path,target_path=re.split("s*",file_path,1)  #通过正则的s*(空格) 一个或是多个空格分开本地路径和目标路径
        file_byte_size=os.stat(local_path).st_size  #获取文件大小
        file_name=os.path.basename(local_path)  #拿到文件名称
    
        post_info="post|%s|%s|%s"%(file_name,file_byte_size,target_path)  #拼接文件名、文件大小和文件路径
        sk.sendall(bytes(post_info,"utf8"))  #拼接后上传到服务端
    
        result_exist=str(sk.recv(1024),"utf8")  #接受服务端传过来的文件存在的返回值
        has_sent=0
        if result_exist=="2003":
            inp=input("文件存在,是否续传?Y/N").strip()
            if inp.upper()=="Y":  #
                sk.sendall(bytes("2004","utf8"))  #告诉服务端进行断点续传 
                result_continue_pos=str(sk.recv(1024),"utf8")  #
                print(result_continue_pos)
                has_sent=int(result_continue_pos)  #已经上传文件的大小
    
            else:
                sk.sendall(bytes("2005","utf8"))  #不进行续传
    
        file_obj=open(local_path,"rb")  #在绝对路径下读取内容
        file_obj.seek(has_sent)  #寻找到指针的位置(上次传到哪就跳到哪,如果没有传就从开始进行读取)
    
        while has_sent<file_byte_size:
            data=file_obj.read(1024)
            sk.sendall(data)  #发送给服务端
            has_sent+=len(data)  #一次最多传送1024,超过那就进行累加
            
            #进度条
            def bar(num=1, sum=100):
    
                rate = float(num) / float(sum)
                rate_num = int(rate * 100)
                temp = '
    %d %%' % (rate_num, )
                sys.stdout.write(temp)
                sys.stdout.flush()
            
            bar(has_sent,file_byte_size)
        file_obj.close()
        print("上传成功")

    详细请参考:http://www.cnblogs.com/wupeiqi/articles/5040823.html

  • 相关阅读:
    概率图模型 ——(6)团树传播算法
    概率图模型 ——(5)变量消元法求边缘概率
    Catkin workspace `/home/qian` is already initialized. No action taken.
    安装TensorRT
    vscode教程
    概率图模型 ——(4)因子图
    概率图模型 ——(3)马尔科夫随机场
    概率图模型 ——(2)贝叶斯网络
    概率图模型 ——(1)概率论与图论基础
    Kubernetes 是怎么实现服务发现的?
  • 原文地址:https://www.cnblogs.com/kongqi816-boke/p/5570624.html
Copyright © 2020-2023  润新知