Python 网络编程

上一篇博客介绍了socket的基本概念以及实现了简单的TCP和UDP的客户端、服务器程序,本篇博客主要对socket编程进行更深入的讲解

一、简化版ssh实现

这是一个极其简单的仿ssh的socket程序,实现的功能为客户端发送命令,服务端接收到客户端的命令,然后在服务器上通过subrocess模块执行命令,如果命令执行有误,输出内容为空,则返回"command error"的语句给客户端,否则将命令执行的结果返回给客户端

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket
import subprocess
 
ip = '0.0.0.0'
port = 8005
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((ip, port))
sock.listen(5)
 
while True:
    conn, addr = sock.accept()
    while True:
        try:
            cmd = str(conn.recv(1024), encoding="utf-8")
            if cmd == "exit":
                conn.close()
                break
            p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
            send_data = p.stdout.read()
            if len(send_data) == 0:
                send_data = "command error"
            else:
                send_data = str(send_data, encoding="gbk")
            send_data = bytes(send_data, encoding="utf-8")
            conn.sendall(send_data)
        except Exception as e:
            print (e)
            break

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket
 
ip = '127.0.0.1'
port = 8005
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
 
while True:
    cmd = input("client>").strip()
    if cmd == "":
        continue
    else:
        sock.sendall(bytes(cmd, encoding="utf-8"))
    if cmd == "exit":
        break
    recv_data = str(sock.recv(1024), encoding="utf-8")
    print (recv_data)
sock.close()

上面的程序有个问题,如果命令执行的结果比较长,那么客户端发送下一个命令过去之后仍然返回上一个命令没接收完的结果,这样的现象我们称作粘包的现象。我们知道TCP传输的是数据流,发送数据时会等缓冲区满了然后再发送,或者等待要发送的时间超时了再发送,几个包组合在一起发送可以提高发送效率,此时也造成了粘包现象的产生,目标机器一次性接收几个包的数据,可能导致本次请求接收多余的数据。粘包还有一种情况,就是本次程序里面出现的情况,因为服务端要发送的数据大于1024,导致客户端无法一次性接收完数据,虽然我们可以修改接收的大小,但是治标不治本。解决方法有我们在发送具体数据前,先将数据的大小发送给客户端,客户端做好接收准备并告诉给服务端,服务端一次性发送数据之后,客户端根据服务器端发送数据的大小进行循环接收,直到数据接收完毕。

改进版

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import socket
import subprocess
 
ip = '0.0.0.0'
port = 8005
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((ip, port))
sock.listen(5)
 
while True:
    conn, addr = sock.accept()
    while True:
        try:
            cmd = str(conn.recv(1024), encoding="utf-8")
            if cmd == "exit":
                conn.close()
                break
            p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
            send_data = p.stdout.read()
            if len(send_data) == 0:
                send_data = "command error"
            else:
                send_data = str(send_data, encoding="gbk")
            send_data = bytes(send_data, encoding="utf-8")
            data_len = len(send_data)
            ready_tag = "Ready|%d" %data_len
            conn.sendall(bytes(ready_tag, encoding="utf-8"))
            start_data = str(conn.recv(1024), encoding="utf-8")
            if start_data.startswith("Start"):
                conn.sendall(send_data)
        except Exception as e:
            print (e)
            break

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket
 
ip = '127.0.0.1'
port = 8005
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
 
while True:
    cmd = input("client>").strip()
    if cmd == "":
        continue
    else:
        sock.sendall(bytes(cmd, encoding="utf-8"))
    if cmd == "exit":
        break
    ready_data = str(sock.recv(1024), encoding="utf-8")
    if ready_data.startswith("Ready"):
        msg_size = int(ready_data.split("|")[-1])
    start_tag = "Start"
    sock.sendall(bytes(start_tag, encoding="utf-8"))
    msg = ""
    recv_size = 0
    while recv_size < msg_size:
        recv_data = sock.recv(1024)
        recv_size += len(recv_data)
        msg += str(recv_data, encoding="utf-8")
    print (msg)
sock.close()

二、IO多路复用

IO多路复用指通过一种机制,可以监视多个描述符,一旦某个描述符就绪,就能够通过程序进行相应的读写操作

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

对于select:

1
2
3
4
5
6
7
8
9
10
11
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
  
参数: 可接受四个参数(前三个必须)
返回值:三个列表
  
select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
   当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import select
 
ip = "0.0.0.0"
port = 8003
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((ip, port))
sock.listen(5)
 
inputs = [sock]
outputs = []
message_queues = {}
 
while True:
    read_list, write_list, error_list = select.select(inputs, outputs, inputs, 1)
    print ("inputs: %d outputs: %d read_list: %d write_list: %d" %(len(inputs), len(outputs), len(read_list), len(write_list)))
    for r in read_list:
        if r is sock:
            conn, addr = r.accept()
            conn.sendall(bytes("welcome to here", encoding="utf-8"))
            inputs.append(conn)
            message_queues[conn] = []
        else:
            data = str(r.recv(1024), encoding="utf-8")
            if data == "exit":
                if r in outputs:
                    outputs.remove(r)
                inputs.remove(r)
                r.close()
                del message_queues[r]
            else:
                message_queues[r].append(data)
                if r not in outputs:
                    outputs.append(r)
 
    for w in write_list:
        data = message_queues[w].pop()
        w.sendall(bytes(data, encoding="utf-8"))
        if len(message_queues[w]) == 0:
            outputs.remove(w)
 
    for e in error_list:
        inputs.remove(e)
        if e in outputs:
            outputs.remove(e)
        e.close()
        del message_queues[e]

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket
 
ip = "127.0.0.1"
port = 8003
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
 
while True:
    data = str(sock.recv(1024), encoding="utf-8")
    print ("server>%s" %data)
    send_data = input("client>").strip()
    if not send_data:
        send_data = "empty"
    sock.sendall(bytes(send_data, encoding="utf-8"))
    if send_data == "exit":
        exit()
sock.close()

三、SocketServer

SocketServer内部使用 IO多路复用 以及多线程和多进程,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求


SocketServer有以下几种类型:

  • socketserver.TCPServer

  • socketserver.UDPServer

  • socketserver.UnixStreamServer

  • socketserver.UnixDatagramServer

​每种类型都可以通过多线程或者多进程的方式处理多个客户的请求,这里介绍ThreadingTCPServer的使用方式:

  1. 创建一个继承自 SocketServer.BaseRequestHandler 的类

  2. 类中必须定义一个名称为 handle 的方法

  3. 启动ThreadingTCPServer

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socketserver
 
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        request = self.request
        Flag = True
        while Flag:
            data = str(request.recv(1024), encoding="utf-8")
            if data == "exit":
                request.close()
                Flag = False
            else:
                request.sendall(bytes(data, encoding="utf-8"))
 
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('0.0.0.0', 8888), Myserver)
    server.serve_forever()

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import socket
 
ip = '127.0.0.1'
port = 8888
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect((ip, port))
 
while True:
    send_data = input("client>")
    sk.sendall(bytes(send_data, encoding="utf-8"))
    if send_data == "exit":
        sk.close()
        break
    recv_data = str(sk.recv(1024), encoding="utf-8")
    print("server>%s" % recv_data)

Python 网络编程(二)的更多相关文章

  1. Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性

    TCP/IP网络通讯粘包问题 案例:模拟执行shell命令,服务器返回相应的类容.发送指令的客户端容错率暂无考虑,按照正确的指令发送即可. 服务端代码 # -*- coding: utf- -*- # ...

  2. python 网络编程:socket(二)

    上节地址:Python网络编程:socket 一.send和sendall区别 send,sendall         ret = send('safagsgdsegsdgew') #send 发送 ...

  3. Python 网络编程(一)

    Python 网络编程 socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. ...

  4. 28、Python网络编程

    一.基于TCP协议的socket套接字编程 1.套接字工作流程 先从服务器端说起.服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客 ...

  5. python 网络编程:socket

    在学习socket之前,我们先复习下相关的网络知识. OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层.OSI七层模型是由国际标准化组织ISO定义的网络的基本结构,不仅包括一 ...

  6. 图解Python网络编程

    返回目录 本篇索引 (1)基本原理 (2)socket模块 (3)select模块 (4)asyncore模块 (5)asynchat模块 (6)socketserver模块 (1)基本原理 本篇指的 ...

  7. Python学习(22)python网络编程

    Python 网络编程 Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的 ...

  8. Day07 - Python 网络编程 Socket

    1. Python 网络编程 Python 提供了两个级别访问网络服务: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口 ...

  9. python网络编程-01

    python网络编程 1.socket模块介绍 ①在网络编程中的一个基本组件就是套接字(socket),socket是两个程序之间的“信息通道”. ②套接字包括两个部分:服务器套接字.客户机套接字 ③ ...

随机推荐

  1. 什么是 kNN 算法?

    学习 machine learning 的最低要求是什么?  我发觉要求可以很低,甚至初中程度已经可以.  首先要学习一点 Python 编程,譬如这两本小孩子用的书:[1][2]便可.   数学方面 ...

  2. 【LeetCode OJ】Recover Binary Search Tree

    Problem Link: https://oj.leetcode.com/problems/recover-binary-search-tree/ We know that the inorder ...

  3. 使用 WinAppDeployCmd 部署Win10 App 到移动设备

    WinAppDeployCmd是目前微软提供的Win10 App 部署工具,它和以前的Windows Phone Application Deployment 部署工具有所不同的是,WinAppDep ...

  4. web前端性能14条规则

    14条规则 1.减少Http请求 使用图片地图 使用CSS Sprites 合并JS和CSS文件 这个是由于浏览器对同一个host有并行下载的限制,http请求越多,总体下载速度越慢 2.使用CDN( ...

  5. Qt之qInstallMessageHandler(重定向至文件)

    简述 在Qt之qInstallMessageHandler(输出详细日志)一节中,我们讲解了如何使用自定义消息处理程序输出调试信息,而且可以很直观.很方便的得到输出代码所在的文件.函数.行号等信息. ...

  6. Chp11 11.7

    <Java语言程序设计>P327 题目要求使用数组来模拟实现ArrayList的一些方法,并要求可以根据实际长度来实现数组自动增长,这里只贴出LikeArrayList.java 测试方法 ...

  7. 获取select标签的值

    1.设置value为pxx的项选中 $(".selector").val("pxx"); 2.设置text为pxx的项选中 $(".selector& ...

  8. js jquery实时计算输入字符

    在项目中需要倒还可以输入多少字符

  9. 类:String,Math,DateTime,Random

    string类: 判断邮箱格式是否正确: 1.有且只能有一个@  2.不能以@开头  3.@之后至少有一个.  4.@和.不能靠在一起  5.不能以.结尾 math 类: math.ceiling() ...

  10. kernel/vsprintf.c

    /* *  linux/kernel/vsprintf.c * *  Copyright (C) 1991, 1992  Linus Torvalds */ /* vsprintf.c -- Lars ...