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. iOS按钮设置图片在上文字在下

    UIButton同时设置Title和Image后,默认是图片在左文字在右,如下图1,很多情况下我们希望图片在上图片在下,如下图2,只需要简单的几行代码,即可实现. (1)因为需要处理多个按钮,所以将实 ...

  2. 指针数组 null与空字符串

    指针数组常适用于指向若干字符串,这样使字符串处理更加灵活方便. 在c++中,null表示:对象为空,它是对指针而言的.而""表示:值为空,它是对字符串而言的.

  3. Uncaught ReferenceError: WebForm_DoPostBackWithOptions is not defined

    环境:Asp.Net网站,Framework版本4.0,IIS版本7.0问题:按钮失效,下面是按钮代码: <a id="dnn_ctr1161_Login_Login_DNN_cmdL ...

  4. 用powershell批量新增user profile

    SharePoint 2013 新系统,要在User Profile Service里把人全加一下,其实同步ad更方便,但ad里的人太多,没必要全要,只要大中华区就行了,问hr要了一份人员名单,写了个 ...

  5. (转) Xcode 7 Bitcode

    Xcode 7 Bitcode的工作流程及安全性评估 2015-12-18 06:13 编辑: suiling 分类:iOS开发 来源:FreeBuf黑客与极客 简介 随着 Xcode 7 的发布,苹 ...

  6. Android 媒体存储服务(二)

    Android 媒体存储服务 简介: 本文是<深入Android媒体存储服务>系列第二篇,简要介绍媒体存储服务扫描文件的流程.文中介绍的是 Android 4.2. Android 有一套 ...

  7. Python 程序员经常犯的 10 个错误

    关于PythonPython是一种解释性.面向对象并具有动态语义的高级程序语言.它内建了高级的数据结构,结合了动态类型和动态绑定的优点,这使得... 关于Python Python是一种解释性.面向对 ...

  8. Mongodb优化

    本文将从各个层面讲述Mongodb的优化方法

  9. Java中的Object类介绍

    Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object类. Object类中的所有方法适用于所有子类 Object中比较常见的方法: ...

  10. java读取properties文件工具

    public class PropertiesUtil { public static String get(String filePath, String key) { String val = n ...