首页 > 代码库 > python学习-网络编程

python学习-网络编程

socket

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

socket 与 file 的却别:

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

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

技术分享

简单实例

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

技术分享
import socketsk = 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 socketsk = 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 = http://www.mamicode.com/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 socketimport subprocesss = socket.socket()s.bind(("127.0.0.1",9999))s.listen(5)while True:    print("Wating New Connection ... ")    conn, addr = s.accept()    print("\tWating IP: {0[0]} Port: {0[1]}".format(addr))    while True:        # 捕获客户端发送过来的数据,最大接收 1024 字节        recv_data = http://www.mamicode.com/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\n%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("\tIP:{0[0]} Port:{0[1]} Close Connection".format(addr))
服务端代码
技术分享
import reimport sockets = socket.socket()s.connect((127.0.0.1, 9999))while True:    try:        # 捕获输入        send_data = http://www.mamicode.com/input(">> ").strip()        # 如果捕获数据为空,则继续提示输入        if not send_data:continue        # 如果输入的数据为 exit,则跳出循环        if send_data =http://www.mamicode.com/= exit:break        # 发送捕获到的数据,python 3.5 之后发送的数据必须为 bytes 类型        s.send(bytes(send_data, encoding=utf8))        # 接收最大 1024 字节的数据        recv_data = http://www.mamicode.com/s.recv(1024)        # 将数据转换为 str        recv_data = http://www.mamicode.com/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:        breaks.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 socketserverclass 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 sockets = 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 socketimport selectimport threadingdef 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()
代码精简

 

 

python学习-网络编程