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. Android ListView 多样式Item的一个注意点:(

    闲来无事,在写一个多样式Item的ListView的Demo时,遇到了一个以前没遇过的问题; ╮( ̄▽ ̄")╭ 我们知道,ListView里可以有多种样式的item, 实现只需要重写: @O ...

  2. [转载 ]POJ 1273 最大流模板

    转载 百度文库花了5分下的 不过确实是自己需要的东西经典的最大流题POJ1273 ——其他练习题 POJ3436 . 题意描述: 现在有m个池塘(从1到m开始编号,1为源点,m为汇点),及n条水渠,给 ...

  3. SVN 服务启动报错 0x8007042a

    服务器环境:Windows Server 2008 R2 企业版,Visual SVNServer 2.6.5 不能签出代码,发现svn服务关闭,手动启动报错: 事件查看器: Error: no li ...

  4. Robot_bfs

    Description The Robot Moving Institute is using a robot in their local store to transport different ...

  5. 第一次用Axure~

    刚刚接触axure感觉好多不会呢~但是一步一步来吧~ 操作到后来发现字体的变化很奇怪,总是只有一个字体出现,只有在编辑时才出现我设定的字体. 但最后还是有个样子出来了~做了一个联系的新页面 最后学姐又 ...

  6. LayaAir引擎——(八)

    var a = new Array(); var b = new Array(); var ksjmCursor = 0; function ksjminit() { ksjminitName(); ...

  7. tinyXML的用法

    tinyXML一款很优秀的操作C++类库,文件不大,但方法很丰富,和apache的Dom4j可以披靡啊!习惯了使用java类库的我看到这么丰富的c++类库,很高兴!它使用很简单,只需要拷贝几个文件到你 ...

  8. CentOS 安装 Wine

    1. 下载安装包 Wine的中文官网可以下载到最新稳定和开发版本的Wine安装包,根据不同需求可以自行下载 2. 解压安装包,编译前检查 根据不同的平台选择不同的编译选项: For 32-Bit Sy ...

  9. MCMC: The Metropolis-Hastings Sampler

    本文主要译自:MCMC:The Metropolis-Hastings Sampler 上一篇文章中,我们讨论了Metropolis 采样算法是如何利用马尔可夫链从一个复杂的,或未归一化的目标概率分布 ...

  10. 全新的跨平台app软件开发工具——Lae软件开发平台

    Lae是一款运行于windows的界面开发工具,具有所见即所得.开发跨平台.UI布局自由.机制简单.维护容易等诸多优点,可以开发同时运行在windows.Linux.MacOX.iOS.Android ...