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. WCF XmlSerializer GetType 反射类型 报错 解决方案

    为图省事,用现有的EF,和web一起使用一个Model类 当进行到 XmlSerializer xmlSerializer = new XmlSerializer(obj.GetType()); 会报 ...

  2. 问题:C++形参默认值为什么一定要放在最后?

    问题:C++形参默认值为什么一定要放在最后? 1.会出现歧义! 2.从汇编角度看是怎么回事?   待解答!!

  3. C语言指针,你还觉得难吗?

    在研究式学习-c语言程序设计指针式这样介绍的: 内存中存储变量的第一个单元的地址 称为指针,存放指针的变量称为指针变量: 变量的访问方式有: 直接访问方式:按变量名查找其首地址 间接访问方式:通过指针 ...

  4. BT协议分析(1)—1.0协议

    简述 BT下载是采用P2P的下载方式,下载的大致形式采用如下图所示,处于图示中心的称为Tracker服务器,其余称为Peer.   缺点 1.资源的安全性 2.资源的实效性(没有上传者则BT也将失效) ...

  5. winform 使用 ReportViewer做报表

    之前用过的水晶报表觉得有些麻烦,因此尝试了使用微软自带的报表. 第一种方法是 在winform界面上放置ReportViewer界面,相关的代码如下: public DataTable dt; pri ...

  6. ubuntu14 opencv python 安装

    本文记录了Ubuntu 14.04下使用源码手动安装OpenCV 3.0的过程.此外记录了在Python中安装及载入OpenCV的方法. 1.安装OpenCV所需的库(编译器.必须库.可选库) GCC ...

  7. 关于MVC中Start.cs文件的作用

    当我们建立默认的 .NET Framework4.5.2框架下的Web  MVC 应用程序后,调试过程中我发现在程序启动页面加载完成会执行一段代码,这段代码让人有点摸不着头脑,因为之前完全没见过,调试 ...

  8. Java笔记7-多态父类静态

    多态的应用-面向父类编程 1.对象的编译时类型写成父类 2.方法的返回类型写成父类 3.方法的参数类型写成父类 编译时类型:对象的声明时类型,在于编译期间 运行时类型:new运算符后面的类型 编译时类 ...

  9. 开启Java博客

    已经转Java大半年了,Java知识都来自于工作,没有一个系统的学习,所以这一个多月我都在看Java的一些基本东西,准备系统性的学习下Java知识.这一个多月看的也挺多,从servlet,jsp,st ...

  10. C++ CreateThread 实例

    //ThreadBase.h#pragma once #include<windows.h> class CThreadBase { public: CThreadBase(void); ...