Python 网络编程(二)

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)

时间: 2024-10-22 04:02:41

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

【学习笔记】Python网络编程(二)socket处理多个连接

贴代码,server端: import socket # s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) host =  '' port = 6074 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((host,port)) s.listen(1) while 1:     conn,addr = s.accept()     while 1:         pri

python 网络编程 (二)---tcp

异常 python的socket模块实际上定义了4种可能出现的异常: 1)与一般I/O 和通信问题有关的socket.error; 2)与查询地址信息有关的socket.gaierror; 3)与其他地址错误有关的socket.herror; 4)与在一个socket上调用settimeout()后,处理超时有关的socket.timeout; import socket, sys, time host = sys.argv[1] textport = sys.argv[2] filename

[python] 网络编程之套接字Socket、TCP和UDP通信实例

很早以前研究过C#和C++的网络通信,参考我的文章: C#网络编程之Tcp实现客户端和服务器聊天 C#网络编程之套接字编程基础知识 C#网络编程之使用Socket类Send.Receive方法的同步通讯 Python网络编程也类似.同时最近找工作笔试面试考察Socket套接字.TCP\UDP区别比较多,所以这篇文章主要精简了<Python核心编程(第二版)>第16章内容.内容包括:服务器和客户端架构.套接字Socket.TCP\UDP通信实例和常见笔试考题. 最后希望文章对你有所帮助,如果有不

Python 网络编程(一)

Python 网络编程 socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者. socket和file的区别: file模块是针对某个指定文件进行[打开][读写][关闭] socket模块是针对 服务器端 和 客户端Socket 进行[打开][读写][关闭] socket服务端和客户端的网

python 网络编程:socket

在学习socket之前,我们先复习下相关的网络知识. OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层.OSI七层模型是由国际标准化组织ISO定义的网络的基本结构,不仅包括一些概念和结构,还包括一系列的协议. TCP/IP四层模型:既然有OSI七层模型,为什么我们还要定义一个TCP/IP的四层模型呢,那是因为OSI七层模型对应面过于宽泛,很多概念实现不了,也没必要实现,因此,实际生产中广泛应用的是TCP/IP四层结构,他们的对应关系如下表: TCP/IP OSI 应用层

python 网络编程:socket和select实现伪并发

上节地址:Python网络编程:socket 先补充点内容: 一.send和sendall区别 send,sendall ret = send('safagsgdsegsdgew') #send 发送完成后会有一个返回值,告知发送了多少,并不一定会把数据全部发送过去. sendall:内部调用send,将数据全部发送完为止. 因此我们使用时最好使用sendall 二.粘包 粘包问题需要理解recv()的使用,我们定义接收值的时候会写recv(1024)表示一次接收1024字节,但是有时候接收的数

Python网络编程08----Django模版

模板系统基本知识 模板是一个文本文件(可以是HTML,XML,CSV等任何文本格式),同时包含了静态内容(例如HTML)和动态标记的逻辑,用于分离文档的表现形式和内容. 模板定义了占位符以及各种用于规范文档该如何显示的各部分基本逻辑(模板标签). 模板通常用于产生HTML,但是Django的模板也能产生任何基于文本格式的文档.使用哪个模版以及渲染什么数据是由视图函数本身(通过显式的渲染或者使用render_to_response)或者视图的参数(比如通用视图里的template_name参数)决

PYTHON网络编程基础 pdf扫描版高清下载

PYTHON网络编程基础 pdf,本书全面介绍了使用PYTHON进行网络编程的基础知识,高级网络操作.WebServices.解析HTML和XHTML.XML.FTP.使用PYTHON操作数据库.SSL.几种服务器端框架,以及多任务处理等,实用性比较强,书中提供了175个实例,6600行以上的代码. 目录 第1部分 底层网络 第1章 客户/服务器网络介绍 第2章 网络客户端 第3章 网络服务器 第4章 域名系统 第5章 域名系统 第2部分 Web Service 第6章 Web客户端访问 第7章

[Python网络编程] DNS缓存解决方案

记得以前写爬虫的时候为了防止dns多次查询,是直接修改/etc/hosts文件的,最近看到一个优美的解决方案,修改后记录如下: import socket _dnscache={} def _setDNSCache(): """ Makes a cached version of socket._getaddrinfo to avoid subsequent DNS requests. """ def _getaddrinfo(*args, **