天天看点

第十七章 Python网络编程

Socket简介

在网络上的两个程序通过一个双向的通信连接实现数据的交换,这个链接的一端称为一个Socket(套接字),用于描述IP地址和端口。

建立网络通信连接至少要一对端口号(Socket),Socket本质是编程接口(API),对TCP/IP的封装,提供了网络通信能力。

每种服务都打开一个Socket,并绑定到端口上,不同的端口对应不同的服务,就像http对应80端口。

Socket是面向C/S(客户端/服务器)模型设计,客户端在本地随机申请一个唯一的Socket号,服务器拥有公开的socket,任何客户端都可以向它发送连接请求和信息请求。

比如:用手机打电话给10086客服,你的手机号就是客户端,10086客服是服务端。必须在知道对方电话号码前提下才能与对方通讯。

Socket数据处理流程如图:

<a href="http://s1.51cto.com/wyfs02/M00/8B/1E/wKiom1hFB3ihlqXzAAD9zEGLSeA858.png" target="_blank"></a>

17.1 socket

在Python中提供此服务的模块是socket和SocketServer,下面是socket常用的类、方法:

方法

描述

socket.socket([family[, type[, proto]]])

socket初始化函数,(地址族,socket类型,协议编号)协议编号默认0

socket.AF_INET

IPV4协议通信

socket.AF_INET6

IPV6协议通信

socket.SOCK_STREAM

socket类型,TCP

socket.SOCK_DGRAM

socket类型,UDP

socket.SOCK_RAW

原始socket,可以处理普通socker无法处理的报文,比如ICMP

socket.SOCK_RDM

更可靠的UDP类型,保证对方收到数据

socket.SOCK_SEQPACKET

可靠的连续数据包服务

socket.socket()对象有以下方法:

accept()

接受连接并返回(socket object, address info),address是客户端地址

bind(address)

绑定socket到本地地址,address是一个双元素元组(host,port)

listen(backlog)

开始接收连接,backlog是最大连接数,默认1

connect(address)

连接socket到远程地址

connect_ex(address)

连接socket到远程地址,成功返回0,错误返回error值

getpeername()

返回远程端地址(hostaddr, port)

gettimeout()

返回当前超时的值,单位秒,如果没有设置返回none

recv(buffersize[, flags])

接收来自socket的数据,buffersize是接收数据量

send(data[, flags])

发送数据到socket,返回值是发送的字节数

sendall(data[, flags])

发送所有数据到socket,成功返回none,失败抛出异常

setblocking(flag)

设置socket为阻塞(flag是true)或非阻塞(flag是flase)

温习下TCP与UDP区别:

TCP和UDP是OSI七层模型中传输层提供的协议,提供可靠端到端的传输服务。

TCP(Transmission Control Protocol,传输控制协议),面向连接协议,双方先建立可靠的连接,再发送数据。适用于可靠性要求高的应用场景。

UDP(User Data Protocol,用户数据报协议),面向非连接协议,不与对方建立连接,直接将数据包发送给对方,因此相对TCP传输速度快 。适用于可靠性要求低的应用场景。

17.1.1 TCP编程

下面创建一个服务端TCP协议的Socket演示下。

先写一个服务端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>#!/usr/bin/python</code>

<code># -*- coding: utf-8 -*-</code>

<code>import</code> <code>socket</code>

<code>HOST </code><code>=</code> <code>''                 </code><code># 为空代表所有可用的网卡</code>

<code>PORT </code><code>=</code> <code>50007</code>              <code># 任意非特权端口</code>

<code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>s.bind((HOST, PORT))</code>

<code>s.listen(</code><code>1</code><code>)   </code><code># 最大连接数</code>

<code>conn, addr </code><code>=</code> <code>s.accept()   </code><code># 返回客户端地址</code>

<code>print</code> <code>'Connected by'</code><code>, addr</code>

<code>while</code> <code>1</code><code>:</code>

<code>    </code><code>data </code><code>=</code> <code>conn.recv(</code><code>1024</code><code>)   </code><code># 每次最大接收客户端发来数据1024字节</code>

<code>    </code><code>if</code> <code>not</code> <code>data: </code><code>break</code>       <code># 当没有数据就退出死循环 </code>

<code>    </code><code>print</code> <code>"Received: "</code><code>, data </code><code># 打印接收的数据</code>

<code>    </code><code>conn.sendall(data)       </code><code># 把接收的数据再发给客户端</code>

<code>conn.close()</code>

再写一个客户端:

<code>HOST </code><code>=</code> <code>'192.168.1.120'</code>    <code># 远程主机IP</code>

<code>PORT </code><code>=</code> <code>50007</code>              <code># 远程主机端口</code>

<code>s.connect((HOST, PORT))</code>

<code>s.sendall(</code><code>'Hello, world'</code><code>) </code><code># 发送数据</code>

<code>data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)       </code><code># 接收服务端发来的数据</code>

<code>s.close()</code>

<code>print</code> <code>'Received: '</code><code>, data</code>

写好后,打开一个终端窗口执行:

<code># python socket-server.py</code>

<code>监听中...</code>

<code># 直到客户端运行会接收到下面数据并退出</code>

<code>Connected by (</code><code>'192.168.1.120'</code><code>, </code><code>37548</code><code>)</code>

<code>Received:  Hello, world</code>

再打开一个终端窗口执行:

# 如果端口监听说明服务端运行正常

<code># netstat -antp |grep 50007</code>

<code>tcp        </code><code>0</code>      <code>0</code> <code>0.0</code><code>.</code><code>0.0</code><code>:</code><code>50007</code>           <code>0.0</code><code>.</code><code>0.0</code><code>:</code><code>*</code>               <code>LISTEN      </code><code>72878</code><code>/</code><code>python</code>

<code># python socket-client.py</code>

<code>Received: Hello, world</code>

通过实验了解搭到Socket服务端工作有以下几个步骤:

1)打开socket

2)绑定到一个地址和端口

3)监听进来的连接

4)接受连接

5)处理数据

17.1.2 UDP编程

服务端:

<code>HOST </code><code>=</code> <code>''               </code>

<code>PORT </code><code>=</code> <code>50007</code>             

<code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>    </code><code>data, addr </code><code>=</code> <code>s.recvfrom(</code><code>1024</code><code>)</code>

<code>    </code><code>print</code> <code>'Connected by'</code><code>, addr</code>

<code>    </code><code>print</code> <code>"Received: "</code><code>, data</code>

<code>    </code><code>s.sendto(</code><code>"Hello %s"</code><code>%</code> <code>repr</code><code>(addr), addr)</code>

客户端:

<code>HOST </code><code>=</code> <code>'192.168.1.99'</code>                 

<code>s.sendto(data, (HOST, PORT))</code>

<code>data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)</code>

运行方式与TCP编程一样。

使用UDP协议时,服务端就少了listen()和accept(),不需要建立连接就直接接收客户端的数据,也是把数据直接发送给客户端。

客户端少了connect(),同样直接通过sendto()给服务器发数据。

而TCP协议则前提先建立三次握手。

17.1.3 举一个更直观的socket通信例子

客户端发送bash命令,服务端接收到并执行,把返回结果回应给客户端。

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

<code>import</code> <code>sys</code>

<code>import</code> <code>subprocess</code>

<code>try</code><code>:</code>

<code>    </code><code>s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>s.bind((HOST, PORT))</code>

<code>    </code><code>s.listen(</code><code>1</code><code>)</code>

<code>except</code> <code>socket.error as e:</code>

<code>    </code><code>s.close()</code>

<code>    </code><code>print</code> <code>e</code>

<code>    </code><code>sys.exit(</code><code>1</code><code>)</code>

<code>    </code><code>conn, addr </code><code>=</code> <code>s.accept()</code>

<code>    </code><code>while</code> <code>1</code><code>:</code>

<code>        </code><code># 每次读取1024字节</code>

<code>        </code><code>data </code><code>=</code> <code>conn.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>if</code> <code>not</code> <code>data: </code><code># 客户端关闭服务端会收到一个空数据</code>

<code>            </code><code>print</code> <code>repr</code><code>(addr) </code><code>+</code> <code>" close."</code>

<code>            </code><code>conn.close()</code>

<code>            </code><code>break</code>     

<code>        </code><code>print</code> <code>"Received: "</code><code>, data</code>

<code>        </code><code>cmd </code><code>=</code> <code>subprocess.Popen(data, stdout</code><code>=</code><code>subprocess.PIPE, stderr</code><code>=</code><code>subprocess.PIPE, shell</code><code>=</code><code>True</code><code>)</code>

<code>        </code><code>result_tuple </code><code>=</code> <code>cmd.communicate()</code>

<code>        </code><code>if</code> <code>cmd.returncode !</code><code>=</code> <code>0</code> <code>or</code> <code>cmd.returncode </code><code>=</code><code>=</code> <code>None</code><code>:</code>

<code>            </code><code>result </code><code>=</code> <code>result_tuple[</code><code>1</code><code>]</code>

<code>            </code><code># result = cmd.stderr.read()</code>

<code>        </code><code>else</code><code>:</code>

<code>            </code><code>result </code><code>=</code> <code>result_tuple[</code><code>0</code><code>]</code>

<code>            </code><code># result = cmd.stdout.read()  # 读不到标准输出,不知道为啥,所以不用</code>

<code>        </code><code>if</code> <code>result:</code>

<code>            </code><code>conn.sendall(result)</code>

<code>            </code><code>conn.sendall(</code><code>"return null"</code><code>)</code>

<code>HOST </code><code>=</code> <code>'192.168.1.120'</code>   

<code>    </code><code>s.connect((HOST, PORT))</code>

<code>    </code><code>cmd </code><code>=</code> <code>raw_input</code><code>(</code><code>"Please input command: "</code><code>)</code>

<code>    </code><code>if</code> <code>not</code> <code>cmd: </code><code>continue</code>

<code>    </code><code>s.sendall(cmd)</code>

<code>    </code><code>recv_data </code><code>=</code> <code>s.recv(</code><code>1024</code><code>)</code>

<code>    </code><code>print</code> <code>'Received: '</code><code>, recv_data</code>

查看运行效果,先运行服务端,再运行客户端:

<code>Connected by (</code><code>'192.168.1.120'</code><code>, </code><code>45620</code><code>)</code>

<code>Received:  ls</code>

<code>Received:  touch a.txt</code>

<code>Please </code><code>input</code> <code>command: ls</code>

<code>Received: </code>

<code>socket</code><code>-</code><code>client.py</code>

<code>socket</code><code>-</code><code>server.py</code>

<code>Please </code><code>input</code> <code>command: touch a.txt</code>

<code>Received:  </code><code>return</code> <code>null</code>

<code>a.txt</code>

<code>Please </code><code>input</code> <code>command:</code>

我想通过上面这个例子你已经大致掌握了socket的通信过程。

再举一个例子,通过socket获取本机网卡IP:

<code>&gt;&gt;&gt; socket.gethostname()</code>

<code>'ubuntu'</code>

<code>&gt;&gt;&gt; socket.gethostbyname(socket.gethostname())</code>

<code>'127.0.1.1'</code>

<code>&gt;&gt;&gt; s </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>&gt;&gt;&gt; s.connect((</code><code>'10.255.255.255'</code><code>, </code><code>0</code><code>))</code>

<code>&gt;&gt;&gt; s.getsockname()</code>

<code>(</code><code>'192.168.1.120'</code><code>, </code><code>35765</code><code>)</code>

<code>&gt;&gt;&gt; s.getsockname()[</code><code>0</code><code>]</code>

<code>'192.168.1.120'</code>

博客地址:http://lizhenliang.blog.51cto.com

QQ群:323779636(Shell/Python运维开发群)

17.2 SocketServer

ScoketServer是Socket服务端库,比socket库更高级,实现了多线程和多线程,并发处理多个客户端请求。

下面是几个常用的类:

<code>SocketServer</code><code>.</code><code>TCPServer</code>(server_address, 

RequestHandlerClass, bind_and_activate=True)

服务器类,TCP协议

<code>SocketServer</code><code>.</code><code>UDPServer</code>(server_address, 

服务器类,UDP协议

<code>SocketServer</code><code>.</code><code>BaseServer</code>(server_address, 

RequestHandlerClass)

这个是所有服务器对象的超类。它定义了接口,不提供大多数方法,在子类中进行。

<code>SocketServer</code><code>.</code><code>BaseRequestHandler</code>

这个是所有请求处理对象的超类。它定义了接口,一个具体的请求处理程序子类必须定义一个新的handle()方法。

SocketServer.StreamRequestHandler

流式socket,根据socket生成读写socket用的两个文件对象,调用rfile和wfile读写

SocketServer.DatagramRequestHandler

数据报socket,同样生成rfile和wfile,但UDP不直接关联socket。这里rfile是由UDP中读取的数据生成,wfile则是新建一个StringIO,用于写数据

SocketServer.ForkingMixIn/ThreadingMixIn

多进程(分叉)/多线程实现异步。混合类,这个类不会直接实例化。用于实现处理多连接

<code>SocketServer</code><code>.</code><code>BaseServer()对象有以下方法:</code>

fileno()

返回一个整数文件描述符上服务器监听的套接字

handle_request()

处理一个请求

serve_forever(poll_interval=0.5)

处理,直至有明确要求shutdown()的请求。轮训关机每poll_interval秒

shutdown()

告诉serve_forever()循环停止并等待

server_close()

清理服务器

address_family

地址族

server_address

监听的地址

RequestHandlerClass

用户提供的请求处理类

socket

socket对象上的服务器将监听传入的请求

allow_reuse_address

服务器是否允许地址的重用。默认False

request_queue_size

请求队列的大小。

socket_type

socket类型。socket.SOCK_STREAM或socket.SOCK_DGRAM

timeout

超时时间,以秒为单位

finish_request()

实际处理通过实例请求RequestHandleClass并调用其handle()方法

get_request()

必须接受从socket的请求,并返回

handle_error(request, client_address)

如果这个函数被条用handle()

process_request(request, client_address)

?

server_activate()

server_bind()

由服务器构造函数调用的套接字绑定到所需的地址

verify_request(request, client_address)

返回一个布尔值,如果该值是True,则该请求将被处理,如果是False,该请求将被拒绝。

创建一个服务器需要几个步骤:

1)创建类,继承请求处理类(BaseRequestHandler),并重载其handle()方法,此方法将处理传入的请求

2)实例化服务器类之一,它传递服务器的地址和请求处理程序类

3)调用handle_request()或serve_forever()服务器对象的方法来处理一个或多个请求

4)调用server_close()关闭套接字

17.2.1 TCP编程

<code># -*- coding: utf-8 -*</code>

<code>import</code> <code>SocketServer</code>

<code>class</code> <code>MyTCPHandler(SocketServer.BaseRequestHandler):</code>

<code>    </code><code>"""</code>

<code>    </code><code>请求处理程序类。</code>

<code>    </code><code>每个连接到服务器都要实例化一次,而且必须覆盖handle()方法来实现与客户端通信</code>

<code>    </code><code>def</code> <code>handle(</code><code>self</code><code>):</code>

<code>        </code><code># self.request 接收客户端数据</code>

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.request.recv(</code><code>1024</code><code>).strip()</code>

<code>        </code><code>print</code> <code>"%s wrote:"</code> <code>%</code> <code>(</code><code>self</code><code>.client_address[</code><code>0</code><code>])</code>

<code>        </code><code>print</code> <code>self</code><code>.data</code>

<code>        </code><code># 把接收的数据转为大写发给客户端</code>

<code>        </code><code>self</code><code>.request.sendall(</code><code>self</code><code>.data.upper())</code>

<code>if</code> <code>__name__ </code><code>=</code><code>=</code> <code>"__main__"</code><code>:</code>

<code>    </code><code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>9999</code>

<code>    </code><code># 创建服务器并绑定本地地址和端口</code>

<code>    </code><code>server </code><code>=</code> <code>SocketServer.TCPServer((HOST, PORT), MyTCPHandler)</code>

<code>    </code><code># 激活服务器,会一直运行,直到Ctrl-C中断</code>

<code>    </code><code>server.serve_forever()</code>

另一个请求处理程序类,利用流(类文件对象简化通信提供标准文件接口):

<code>class</code> <code>MyTCPHandler(SocketServer.StreamRequestHandler):</code>

<code>        </code><code># self.rfile创建的是一个类文件对象处理程序,就可以调用readline()而不是recv()</code>

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.rfile.readline().strip()</code>

<code>        </code><code># 同样,self.wfile是一个类文件对象,用于回复客户端</code>

<code>        </code><code>self</code><code>.wfile.write(</code><code>self</code><code>.data.upper())</code>

<code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>9999</code>

<code>data </code><code>=</code> <code>" "</code><code>.join(sys.argv[</code><code>1</code><code>:])</code>

<code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>sock.connect((HOST, PORT))</code>

<code>    </code><code>sock.sendall(data </code><code>+</code> <code>"\n"</code><code>)</code>

<code>    </code><code>received </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

<code>finally</code><code>:</code>

<code>    </code><code>sock.close()</code>

<code>print</code> <code>"Sent: %s"</code> <code>%</code> <code>data</code>

<code>print</code> <code>"Received: %s"</code> <code>%</code> <code>received</code>

服务端结果:

<code># python TCPServer.py</code>

<code>127.0</code><code>.</code><code>0.1</code> <code>wrote:</code>

<code>hello</code>

<code>nice</code>

客户端结果:

<code># python TCPClient.py hello</code>

<code>Sent: hello</code>

<code>Received: HELLO</code>

<code># python TCPClient.py nice</code>

<code>Sent: nice</code>

<code>Received: NICE</code>

17.2.2 UDP编程

<code>        </code><code>self</code><code>.data </code><code>=</code> <code>self</code><code>.request[</code><code>0</code><code>].strip()</code>

<code>        </code><code>self</code><code>.socket </code><code>=</code> <code>self</code><code>.request[</code><code>1</code><code>]</code>

<code>        </code><code>self</code><code>.socket.sendto(</code><code>self</code><code>.data.upper(), </code><code>self</code><code>.client_address)</code>

<code>    </code><code>server </code><code>=</code> <code>SocketServer.UDPServer((HOST, PORT), MyTCPHandler)</code>

<code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_DGRAM)</code>

<code>sock.sendto(data </code><code>+</code> <code>"\n"</code><code>, (HOST, PORT))</code>

<code>received </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

与TCP执行结果一样。

17.2.3 异步混合

创建异步处理,使用ThreadingMixIn和ForkingMixIn类。

ThreadingMixIn类的一个例子:

<code>import</code> <code>threading</code>

<code>class</code> <code>ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):</code>

<code>        </code><code>data </code><code>=</code> <code>self</code><code>.request.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>cur_thread </code><code>=</code> <code>threading.current_thread()</code>

<code>        </code><code>response </code><code>=</code> <code>"%s: %s"</code> <code>%</code> <code>(cur_thread.name, data)</code>

<code>        </code><code>self</code><code>.request.sendall(response)</code>

<code>class</code> <code>ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):</code>

<code>    </code><code>pass</code>

<code>def</code> <code>client(ip, port, message):</code>

<code>    </code><code>sock </code><code>=</code> <code>socket.socket(socket.AF_INET, socket.SOCK_STREAM)</code>

<code>    </code><code>sock.connect((ip, port))</code>

<code>    </code><code>try</code><code>:</code>

<code>        </code><code>sock.sendall(message)</code>

<code>        </code><code>response </code><code>=</code> <code>sock.recv(</code><code>1024</code><code>)</code>

<code>        </code><code>print</code> <code>"Received: %s"</code> <code>%</code> <code>response</code>

<code>    </code><code>finally</code><code>:</code>

<code>        </code><code>sock.close()</code>

<code>    </code><code># 端口0意味着随机使用一个未使用的端口</code>

<code>    </code><code>HOST, PORT </code><code>=</code> <code>"localhost"</code><code>, </code><code>0</code>

<code>    </code><code>server </code><code>=</code> <code>ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)</code>

<code>    </code><code>ip, port </code><code>=</code> <code>server.server_address</code>

<code>    </code><code># 服务器启动一个线程,该线程将开始。每个线程处理每个请求</code>

<code>    </code><code>server_thread </code><code>=</code> <code>threading.Thread(target</code><code>=</code><code>server.serve_forever)</code>

<code>    </code><code># 作为守护线程</code>

<code>    </code><code>server_thread.daemon </code><code>=</code> <code>True</code>

<code>    </code><code>server_thread.start()</code>

<code>    </code><code>print</code> <code>"Server loop running in thread:"</code><code>, server_thread.name</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 1"</code><code>)</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 2"</code><code>)</code>

<code>    </code><code>client(ip, port, </code><code>"Hello World 3"</code><code>)</code>

<code>    </code><code>server.shutdown()</code>

<code>      </code><code>server.server_close()</code>

<code>Server loop running </code><code>in</code> <code>thread: Thread</code><code>-</code><code>1</code>

<code>Received: Thread</code><code>-</code><code>2</code><code>: Hello World </code><code>1</code>

<code>Received: Thread</code><code>-</code><code>3</code><code>: Hello World </code><code>2</code>

<code>Received: Thread</code><code>-</code><code>4</code><code>: Hello World </code><code>3</code>

<code></code>

本文转自 李振良OK 51CTO博客,原文链接:http://blog.51cto.com/lizhenliang/1879549,如需转载请自行联系原作者