第十七章 Python网络编程

简介:

Socket简介

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

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

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

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

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

Socket数据处理流程如图:

wKiom1hFB3ihlqXzAAD9zEGLSeA858.png

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
#!/usr/bin/python
# -*- coding: utf-8 -*-
import  socket
HOST  =  ''                  # 为空代表所有可用的网卡
PORT  =  50007               # 任意非特权端口
=  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen( 1 )    # 最大连接数
conn, addr  =  s.accept()    # 返回客户端地址
print  'Connected by' , addr
while  1 :
     data  =  conn.recv( 1024 )    # 每次最大接收客户端发来数据1024字节
     if  not  data:  break        # 当没有数据就退出死循环 
     print  "Received: " , data  # 打印接收的数据
     conn.sendall(data)        # 把接收的数据再发给客户端
conn.close()

再写一个客户端:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python
# -*- coding: utf-8 -*-
import  socket
HOST  =  '192.168.1.120'     # 远程主机IP
PORT  =  50007               # 远程主机端口
=  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall( 'Hello, world' # 发送数据
data  =  s.recv( 1024 )        # 接收服务端发来的数据
s.close()
print  'Received: ' , data

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

1
2
3
4
5
# python socket-server.py
监听中...
# 直到客户端运行会接收到下面数据并退出
Connected by ( '192.168.1.120' 37548 )
Received:  Hello, world

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

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

1
2
3
4
# netstat -antp |grep 50007
tcp         0       0  0.0 . 0.0 : 50007            0.0 . 0.0 : *                LISTEN       72878 / python
# python socket-client.py
Received: Hello, world

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

1)打开socket

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

3)监听进来的连接

4)接受连接

5)处理数据

17.1.2 UDP编程

服务端:

1
2
3
4
5
6
7
8
9
10
11
import  socket
HOST  =  ''               
PORT  =  50007             
=  socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))
while  1 :
     data, addr  =  s.recvfrom( 1024 )
     print  'Connected by' , addr
     print  "Received: " , data
     s.sendto( "Hello %s" %  repr (addr), addr)
conn.close()

客户端:

1
2
3
4
5
6
7
8
import  socket
HOST  =  '192.168.1.99'                 
PORT  =  50007             
=  socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(data, (HOST, PORT))
data  =  s.recv( 1024 )
s.close()
print  'Received: ' , data

运行方式与TCP编程一样。

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

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

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

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

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

服务端:

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
#!/usr/bin/python
# -*- coding: utf-8 -*-
import  sys
import  subprocess
import  socket
HOST  =  ''               
PORT  =  50007             
try :
     =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     s.bind((HOST, PORT))
     s.listen( 1 )
except  socket.error as e:
     s.close()
     print  e
     sys.exit( 1 )
while  1 :
     conn, addr  =  s.accept()
     print  'Connected by' , addr
     while  1 :
         # 每次读取1024字节
         data  =  conn.recv( 1024 )
         if  not  data:  # 客户端关闭服务端会收到一个空数据
             print  repr (addr)  +  " close."
             conn.close()
             break     
         print  "Received: " , data
         cmd  =  subprocess.Popen(data, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True )
         result_tuple  =  cmd.communicate()
         if  cmd.returncode ! =  0  or  cmd.returncode  = =  None :
             result  =  result_tuple[ 1 ]
             # result = cmd.stderr.read()
         else :
             result  =  result_tuple[ 0 ]
             # result = cmd.stdout.read()  # 读不到标准输出,不知道为啥,所以不用
         if  result:
             conn.sendall(result)
         else :
             conn.sendall( "return null" )
s.close()

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
# -*- coding: utf-8 -*-
import  sys
import  socket
HOST  =  '192.168.1.120'   
PORT  =  50007             
try :
     =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     s.connect((HOST, PORT))
except  socket.error as e:
     s.close()
     print  e
     sys.exit( 1 )
while  1 :
     cmd  =  raw_input ( "Please input command: " )
     if  not  cmd:  continue
     s.sendall(cmd)
     recv_data  =  s.recv( 1024 )
     print  'Received: ' , recv_data
s.close()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# python socket-server.py
Connected by ( '192.168.1.120' 45620 )
Received:  ls
Received:  touch a.txt
Received:  ls
 
# python socket-client.py
Please  input  command: ls
Received: 
socket - client.py
socket - server.py
Please  input  command: touch a.txt
Received:   return  null
Please  input  command: ls
Received: 
a.txt
socket - client.py
socket - server.py
Please  input  command:

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

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

1
2
3
4
5
6
7
8
9
10
>>> socket.gethostname()
'ubuntu'
>>> socket.gethostbyname(socket.gethostname())
'127.0.1.1'
>>> s  =  socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.connect(( '10.255.255.255' 0 ))
>>> s.getsockname()
( '192.168.1.120' 35765 )
>>> s.getsockname()[ 0 ]
'192.168.1.120'


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

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


17.2 SocketServer

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

下面是几个常用的类:

SocketServer.TCPServer(server_address

RequestHandlerClassbind_and_activate=True)

服务器类,TCP协议

SocketServer.UDPServer(server_address

RequestHandlerClassbind_and_activate=True)

服务器类,UDP协议

SocketServer.BaseServer(server_address

RequestHandlerClass)

这个是所有服务器对象的超类。它定义了接口,不提供大多数方法,在子类中进行。
SocketServer.BaseRequestHandler 这个是所有请求处理对象的超类。它定义了接口,一个具体的请求处理程序子类必须定义一个新的handle()方法。
SocketServer.StreamRequestHandler 流式socket,根据socket生成读写socket用的两个文件对象,调用rfile和wfile读写
SocketServer.DatagramRequestHandler 数据报socket,同样生成rfile和wfile,但UDP不直接关联socket。这里rfile是由UDP中读取的数据生成,wfile则是新建一个StringIO,用于写数据
SocketServer.ForkingMixIn/ThreadingMixIn 多进程(分叉)/多线程实现异步。混合类,这个类不会直接实例化。用于实现处理多连接

SocketServer.BaseServer()对象有以下方法:

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编程

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python
# -*- coding: utf-8 -*
import  SocketServer
class  MyTCPHandler(SocketServer.BaseRequestHandler):
     """
     请求处理程序类。
     每个连接到服务器都要实例化一次,而且必须覆盖handle()方法来实现与客户端通信
     """
     def  handle( self ):
         # self.request 接收客户端数据
         self .data  =  self .request.recv( 1024 ).strip()
         print  "%s wrote:"  %  ( self .client_address[ 0 ])
         print  self .data
         # 把接收的数据转为大写发给客户端
         self .request.sendall( self .data.upper())
if  __name__  = =  "__main__" :
     HOST, PORT  =  "localhost" 9999
     # 创建服务器并绑定本地地址和端口
     server  =  SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
     # 激活服务器,会一直运行,直到Ctrl-C中断
     server.serve_forever()

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

1
2
3
4
5
6
7
8
class  MyTCPHandler(SocketServer.StreamRequestHandler):
     def  handle( self ):
         # self.rfile创建的是一个类文件对象处理程序,就可以调用readline()而不是recv()
         self .data  =  self .rfile.readline().strip()
         print  "%s wrote:"  %  ( self .client_address[ 0 ])
         print  self .data
         # 同样,self.wfile是一个类文件对象,用于回复客户端
         self .wfile.write( self .data.upper())

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
import  socket
import  sys
HOST, PORT  =  "localhost" 9999
data  =  " " .join(sys.argv[ 1 :])
sock  =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try :
     sock.connect((HOST, PORT))
     sock.sendall(data  +  "\n" )
     received  =  sock.recv( 1024 )
finally :
     sock.close()
print  "Sent: %s"  %  data
print  "Received: %s"  %  received

服务端结果:

1
2
3
4
5
# python TCPServer.py
127.0 . 0.1  wrote:
hello
127.0 . 0.1  wrote:
nice

客户端结果:

1
2
3
4
5
6
# python TCPClient.py hello
Sent: hello
Received: HELLO
# python TCPClient.py nice
Sent: nice
Received: NICE

17.2.2 UDP编程

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
import  SocketServer
class  MyTCPHandler(SocketServer.BaseRequestHandler):
     def  handle( self ):
         self .data  =  self .request[ 0 ].strip()
         self .socket  =  self .request[ 1 ]
         print  "%s wrote:"  %  ( self .client_address[ 0 ])
         print  self .data
         self .socket.sendto( self .data.upper(),  self .client_address)
if  __name__  = =  "__main__" :
     HOST, PORT  =  "localhost" 9999
     server  =  SocketServer.UDPServer((HOST, PORT), MyTCPHandler)
     server.serve_forever()

客户端:

1
2
3
4
5
6
7
8
9
import  socket
import  sys
HOST, PORT  =  "localhost" 9999
data  =  " " .join(sys.argv[ 1 :])
sock  =  socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(data  +  "\n" , (HOST, PORT))
received  =  sock.recv( 1024 )
print  "Sent: %s"  %  data
print  "Received: %s"  %  received

与TCP执行结果一样。

17.2.3 异步混合

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

ThreadingMixIn类的一个例子:

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
#!/usr/bin/python
# -*- coding: utf-8 -*
import  socket
import  threading
import  SocketServer
class  ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
     def  handle( self ):
         data  =  self .request.recv( 1024 )
         cur_thread  =  threading.current_thread()
         response  =  "%s: %s"  %  (cur_thread.name, data)
         self .request.sendall(response)
class  ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
     pass
def  client(ip, port, message):
     sock  =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     sock.connect((ip, port))
     try :
         sock.sendall(message)
         response  =  sock.recv( 1024 )
         print  "Received: %s"  %  response
     finally :
         sock.close()
if  __name__  = =  "__main__" :
     # 端口0意味着随机使用一个未使用的端口
     HOST, PORT  =  "localhost" 0
     server  =  ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
     ip, port  =  server.server_address
     # 服务器启动一个线程,该线程将开始。每个线程处理每个请求
     server_thread  =  threading.Thread(target = server.serve_forever)
     # 作为守护线程
     server_thread.daemon  =  True
     server_thread.start()
     print  "Server loop running in thread:" , server_thread.name
     client(ip, port,  "Hello World 1" )
     client(ip, port,  "Hello World 2" )
     client(ip, port,  "Hello World 3" )
     server.shutdown()
       server.server_close()

1
2
3
4
5
# python socket-server.py
Server loop running  in  thread: Thread - 1
Received: Thread - 2 : Hello World  1
Received: Thread - 3 : Hello World  2
Received: Thread - 4 : Hello World  3



本文转自 李振良OK 51CTO博客,原文链接:http://blog.51cto.com/lizhenliang/1879549,如需转载请自行联系原作者
相关文章
|
12天前
|
搜索推荐 程序员 调度
精通Python异步编程:利用Asyncio与Aiohttp构建高效网络应用
【10月更文挑战第5天】随着互联网技术的快速发展,用户对于网络应用的响应速度和服务质量提出了越来越高的要求。为了构建能够处理高并发请求、提供快速响应时间的应用程序,开发者们需要掌握高效的编程技术和框架。在Python语言中,`asyncio` 和 `aiohttp` 是两个非常强大的库,它们可以帮助我们编写出既简洁又高效的异步网络应用。
61 1
|
22天前
|
数据采集 存储 JavaScript
构建你的第一个Python网络爬虫
【9月更文挑战第34天】在数字信息泛滥的时代,快速有效地获取和处理数据成为一项重要技能。本文将引导读者通过Python编写一个简易的网络爬虫,实现自动化地从网页上抓取数据。我们将一步步走过代码的编写过程,并探讨如何避免常见陷阱。无论你是编程新手还是想扩展你的技术工具箱,这篇文章都将为你提供有价值的指导。
68 18
|
3天前
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
|
8天前
|
机器学习/深度学习 人工智能 算法
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
玉米病害识别系统,本系统使用Python作为主要开发语言,通过收集了8种常见的玉米叶部病害图片数据集('矮花叶病', '健康', '灰斑病一般', '灰斑病严重', '锈病一般', '锈病严重', '叶斑病一般', '叶斑病严重'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。再使用Django搭建Web网页操作平台,实现用户上传一张玉米病害图片识别其名称。
22 0
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
|
14天前
|
运维 监控 网络安全
Python 在网络运维方面的自动化应用实例
Python 在网络运维方面的自动化应用实例
39 4
|
1月前
|
机器学习/深度学习 人工智能 算法
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
文本分类识别系统。本系统使用Python作为主要开发语言,首先收集了10种中文文本数据集("体育类", "财经类", "房产类", "家居类", "教育类", "科技类", "时尚类", "时政类", "游戏类", "娱乐类"),然后基于TensorFlow搭建CNN卷积神经网络算法模型。通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型,并保存为本地的h5格式。然后使用Django开发Web网页端操作界面,实现用户上传一段文本识别其所属的类别。
70 1
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
|
29天前
|
机器学习/深度学习 数据采集 网络安全
使用Python实现深度学习模型:智能网络安全威胁检测
使用Python实现深度学习模型:智能网络安全威胁检测
106 5
|
1月前
|
数据采集 存储 JavaScript
构建您的第一个Python网络爬虫:抓取、解析与存储数据
【9月更文挑战第24天】在数字时代,数据是新的金矿。本文将引导您使用Python编写一个简单的网络爬虫,从互联网上自动抓取信息。我们将介绍如何使用requests库获取网页内容,BeautifulSoup进行HTML解析,以及如何将数据存储到文件或数据库中。无论您是数据分析师、研究人员还是对编程感兴趣的新手,这篇文章都将为您提供一个实用的入门指南。拿起键盘,让我们开始挖掘互联网的宝藏吧!
|
8天前
|
运维 安全 网络协议
Python 网络编程:端口检测与IP解析
本文介绍了使用Python进行网络编程的两个重要技能:检查端口状态和根据IP地址解析主机名。通过`socket`库实现端口扫描和主机名解析的功能,并提供了详细的示例代码。文章最后还展示了如何整合这两部分代码,实现一个简单的命令行端口扫描器,适用于网络故障排查和安全审计。
13 0
|
1月前
|
机器学习/深度学习 人工智能 算法
【果蔬识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
【果蔬识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台。果蔬识别系统,本系统使用Python作为主要开发语言,通过收集了12种常见的水果和蔬菜('土豆', '圣女果', '大白菜', '大葱', '梨', '胡萝卜', '芒果', '苹果', '西红柿', '韭菜', '香蕉', '黄瓜'),然后基于TensorFlow库搭建CNN卷积神经网络算法模型,然后对数据集进行训练,最后得到一个识别精度较高的算法模型,然后将其保存为h5格式的本地文件方便后期调用。再使用Django框架搭建Web网页平台操作界面,实现用户上传一张果蔬图片识别其名称。
45 0
【果蔬识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台