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

    socket

    socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

    socket 与 file 的却别:

    • file 是对指定文件进行打开,读写,关闭
    • socket 是对服务器和客户端的 socket 进行打开,读写,关闭

    一、socket 客户端与服务器交互流程

    简单实例

    服务端将客户端发送的字符串转成大写后再返回给客户端

    import socket
    
    sk = socket.socket()
    
    sk.bind(('127.0.0.1', 8001))
    
    sk.listen(5)
    
    connect, address = sk.accept()
    
    while True:
        receive_data = connect.recv(1024)
    
        if not receive_data:
            break
    
        connect.sendall(receive_data.upper())
    
    connect.close()
    服务端代码
    import socket
    
    sk = socket.socket()
    
    sk.connect(('127.0.0.1', 8001))
    
    while True:
        msg = input(">>>").strip()
        if not msg:
            continue
        sk.sendall(bytes(msg, encoding='utf8'))
    
        receive_data = sk.recv(1024)
    
        print(receive_data.decode())
    
    sk.close()
    客户端代码

    服务器与客户端交互时不能发送空字符串

    二、socket 类

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

    family:

    • socket.AF_INET  默认值,IPv4
    • socket.AF_INET6  IPv6

    type:

    • socket.SOCK_STREAM  默认值,TCP
    • socket.SOCK_DGRAM  UDP

    proto: 默认值 0 ,一般不填

    三、socket 对象方法

    • sk.bind(address)  绑定地址(IP,Port)到套接字,在 AF_INET 下以元组(IP,Port)的形式表示
    • sk.listen(backlog)  开始 TCP 监听,backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数,该值至少为 1,一般情况下设置为 5 即可
    • sk.setblocking(bool)  是否阻塞,默认值为 True, 如果设置为 False,一旦 accept 和 recv 没有数据,则报错
    • sk.accept()  被动接收客户端的连接(阻塞式),等待客户端连接,返回(sock, addr) , sock 为新的套接字对象,可以用来接收和发送消息,addr 为客户端的地址
    • sk.connect(address)   连接到 address 所在的服务器,address 以元组的形式表示(IP,Port) or (HOST,Port)
    • sk.connect_ex(address)  和 sk.connect() 类似,只是会有返回值,而不是抛出异常,如果连接成功返回 0
    • sk.close() 关闭套接字
    • sk.recv(bufsize[,flag]) 接收 socket 数据,数据以 bytes 返回(python2.x为 str), bufsize 指定一次可以接收的最大数据,flag 忽略,默认值为 0
    • sk.recvform(bufsize[,flag])  接收 socket 数据,返回值为 (data, address), data 为 bytes 类型的数据, address 为从哪个客户端接收的数据
    • sk.send(bytes[,flag]) 将 bytes 数据发送给 socket,返回值为发送的字节大小
    • sk.sendall(bytes[,flag])  将 bytes 数据发送给 socket,如果成功则返回 None,失败则抛出异常。
    • sk.sendto(bytes,address) 将 bytes 数据发送给 socket,通常用于 UDP
    • sk.settimeout(value)  设置 blocking 的超时时间,value 为一个浮点数,单位为 s,默认为不超时,通常配置在 socket 建立之初。value = 5,表示客户端最多等待 5 s
    • sk.getpeername() 返回套接字远端的地址,(IP, Port)
    • sk.getsockname() 返回自身的地址 (IP,Port)
    • sk.fileno() 返回 socket 的文件描述符,返回值为一个数字,-1 表示获取失败,通常在 select.select() 中使用

    四、使用 socket 实现简单的 ssh 操作

    粘包问题:

      sk.recv(bufsize) 在接收 socket 数据时,是有大小限制的,如果 send 的数据过大,就会出现一次性接收不完全的情况,导致后面的交互数据混乱

    解决方法:

      在发送数据之前,先将需要发送的 bytes 大小发送给对方,等待对方准备完成后,在将数据发送。而接收方会根据 bytes 的大小一直接收,并进行数据组合

    import socket
    import subprocess
    
    s = socket.socket()
    
    s.bind(("127.0.0.1",9999))
    
    s.listen(5)
    
    while True:
    
        print("Wating New Connection ... ")
    
        conn, addr = s.accept()
    
        print("	Wating IP: {0[0]} Port: {0[1]}".format(addr))
    
        while True:
    
            # 捕获客户端发送过来的数据,最大接收 1024 字节
            recv_data = conn.recv(1024)
    
            # 如果接收到的自己为空,则跳出循环
            if not recv_data:break
    
            # 捕获苦短发送的命令
            cmd = str(recv_data, encoding='utf8')
            res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            output = str(res.stdout.read(), encoding='utf8')
            error = str(res.stderr.read(), encoding='utf8')
            if error:
                if output:
                    send_data = "%s
    %s" % (output, error)
                else:
                    send_data = error
            else:
                send_data = output
            print(len(send_data))
            # 如果需要发送的字节大约 1024,则先发送一个 Ready|{字节大小},作为标记,然后再紧接着发送数据
            if len(send_data) > 1024:
                ready_tag = "Ready|%s" % len(send_data)
                conn.send(bytes(ready_tag, encoding='utf8'))
                start_tag = conn.recv(1024)
                if start_tag.deconde() == 'Start':
                    conn.send(bytes(send_data, encoding='utf8'))
            else:
                conn.send(bytes(send_data, encoding='utf8'))
    
        conn.close()
        print("	IP:{0[0]} Port:{0[1]} Close Connection".format(addr))
    服务端代码
    import re
    import socket
    
    s = socket.socket()
    
    s.connect(('127.0.0.1', 9999))
    
    while True:
        try:
            # 捕获输入
            send_data = input(">> ").strip()
    
            # 如果捕获数据为空,则继续提示输入
            if not send_data:continue
    
            # 如果输入的数据为 exit,则跳出循环
            if send_data == 'exit':break
    
            # 发送捕获到的数据,python 3.5 之后发送的数据必须为 bytes 类型
            s.send(bytes(send_data, encoding='utf8'))
    
            # 接收最大 1024 字节的数据
            recv_data = s.recv(1024)
    
            # 将数据转换为 str
            recv_data = str(recv_data, encoding='utf-8')
    
            # 如果接收到的数据为匹配正则: Ready|d+$, 说明后面会有一个大约 1024的数据进行发送
            # 获取到需要接收的字节大小,然后进行多次接收,并进行组合,然后再输出
            if re.match('Ready|d+$', recv_data):
                # 获取数据的字节大小
                msg_size = recv_data.split("|")[-1]
                msg_size = int(msg_size)
    
                recv_data = b""     # 接受的数据
                recv_size = 0       # 已接收数据大小
    
                s.send(bytes("Start", encoding='utf-8'))
    
                while recv_size < msg_size:
                    recv_msg = s.recv(1024)     # 多次接收
                    recv_data += recv_msg       # 组合数据
                    recv_size += len(recv_msg)  # 修改已接收数据大小
    
                recv_data = str(recv_data, encoding='utf-8')
    
            # 打印数据
            print(recv_data)
        except KeyboardInterrupt:
            break
    s.close()
    客户端代码

    IO多路复用

    通过一种机制监视多个文件描述符,一但某个描述符就绪(一般是读就绪或写就绪)能够通知程序做相应的读写操作

    Linux中的 select,poll,epoll 都是IO多路复用的机制。

    Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。

    网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。

    select 方法

    readlist, writelist, errorlist = select.select(rlistwlistxlist[, timeout])

    rlist  必选参数,监听读事件

    wlist 必选参数,监听写事件

    xlist 必选参数,监听异常事件

    timeout 可选参数,select 阻塞的超时事件,如指定时间内监听的所有时间均无变化,则返回 3 个空list,如果 timeout 未指定则一直阻塞,知道监听到变化

    socketserver 

    通过 socket 创建的 socker 服务器默认只支持一个连接的操作,当已经存在一个连接时,新来的连接会处于等待状态(sk.listen(5),最多支持 5 个客户端等待),当已有连接断开后才能连接和下一个客户端进行连接。

    socketserver 模块内部使用 “IO多路复用” 和 “多线程”、“多进程” 方式实现多并发的 socket

    import socketserver
    
    
    class Server(socketserver.BaseRequestHandler):
    
        # 这里的方法必须是 handle
        def handle(self):
    
            # 定义与客户端的所有交互内容
            while True:
                receive_data = self.request.recv(1024)
                if not receive_data:
                    break
    
                send_data = receive_data.upper()
                self.request.send(send_data)
    
    
    if __name__ == '__main__':
        # 创建 TreadingTCPServer 对象,并执行 serve_forever 方法
        server = socketserver.ThreadingTCPServer(('127.0.0.1',9998), Server)
        server.serve_forever()
    服务端代码
    import socket
    
    
    s = socket.socket()
    
    s.connect(('127.0.0.1',9998))
    
    while True:
        inp = input(">>>")
        if not inp:
            continue
    
        if inp == 'quit':
            break
    
        s.send(bytes(inp, encoding='utf-8'))
        receive_data = s.recv(1024)
        print(receive_data.decode())
    
    
    s.close()
    客户端代码

    一、ThreadingTCPSever 源码剖析

    二、ThreadingTCPServer 源码精简版

    import socket
    import select
    import threading
    
    
    def process(request, client_ip):
    
        print("HOST[{0[0]}] Port[{0[1]}] Connect".format(client_ip))
        request.sendall(bytes('欢迎连接到服务器。。。', encoding='utf-8'))
    
        while True:
            receive_data = request.recv(1024)
            if not receive_data:
                break
    
            request.sendall(receive_data.upper())
    
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8001))
    sk.listen(5)
    
    while True:
        # 监听 socket 是否有 IO 变化
        r, w, e = select.select([sk], [], [], 1)
        if sk in r:
            request, client_ip = sk.accept()
            # 为 socket 创建多线程
            t = threading.Thread(target=process, args=(request, client_ip))
            t.start()
    sk.close()
    代码精简

     

  • 相关阅读:
    C语言开发框架、printf(day02)
    Linux C(day01)
    线程同步、信号量、system v IPC
    UDP、线程、mutex锁(day15)
    Socket编程(day14)
    共享内存、网络(day13)
    pause、jobs、setitimer(2)、system v ipc(day12)
    PIPE、SIGNAL(day11)
    环境变量、system(day10)
    进程(day09)
  • 原文地址:https://www.cnblogs.com/wenchong/p/5906394.html
Copyright © 2011-2022 走看看