设为首页 收藏本站
查看: 1684|回复: 0

[经验分享] 第十七章 Python网络编程

[复制链接]

尚未签到

发表于 2018-8-16 08:08:15 | 显示全部楼层 |阅读模式
  Socket简介
  在网络上的两个程序通过一个双向的通信连接实现数据的交换,这个链接的一端称为一个Socket(套接字),用于描述IP地址和端口。
  建立网络通信连接至少要一对端口号(Socket),Socket本质是编程接口(API),对TCP/IP的封装,提供了网络通信能力。
  每种服务都打开一个Socket,并绑定到端口上,不同的端口对应不同的服务,就像http对应80端口。
  Socket是面向C/S(客户端/服务器)模型设计,客户端在本地随机申请一个唯一的Socket号,服务器拥有公开的socket,任何客户端都可以向它发送连接请求和信息请求。
  比如:用手机打电话给10086客服,你的手机号就是客户端,10086客服是服务端。必须在知道对方电话号码前提下才能与对方通讯。
  Socket数据处理流程如图:
DSC0000.png

  17.1 socket
  在Python中提供此服务的模块是socket和SocketServer,下面是socket常用的类、方法:
方法描述socket.socket([family[, type[, proto]]])socket初始化函数,(地址族,socket类型,协议编号)协议编号默认0socket.AF_INETIPV4协议通信socket.AF_INET6IPV6协议通信socket.SOCK_STREAMsocket类型,TCPsocket.SOCK_DGRAMsocket类型,UDPsocket.SOCK_RAW原始socket,可以处理普通socker无法处理的报文,比如ICMPsocket.SOCK_RDM更可靠的UDP类型,保证对方收到数据socket.SOCK_SEQPACKET可靠的连续数据包服务  socket.socket()对象有以下方法:
accept()接受连接并返回(socket object, address info),address是客户端地址bind(address)绑定socket到本地地址,address是一个双元素元组(host,port)listen(backlog)开始接收连接,backlog是最大连接数,默认1connect(address)连接socket到远程地址connect_ex(address)连接socket到远程地址,成功返回0,错误返回error值getpeername()返回远程端地址(hostaddr, port)gettimeout()返回当前超时的值,单位秒,如果没有设置返回nonerecv(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演示下。
  先写一个服务端:
#!/usr/bin/python  
# -*- coding: utf-8 -*-
  
import socket
  
HOST = ''                 # 为空代表所有可用的网卡
  
PORT = 50007              # 任意非特权端口
  
s = 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()
  再写一个客户端:
#!/usr/bin/python  
# -*- coding: utf-8 -*-
  
import socket
  
HOST = '192.168.1.120'    # 远程主机IP
  
PORT = 50007              # 远程主机端口
  
s = 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
  写好后,打开一个终端窗口执行:
# python socket-server.py  
监听中...
  
# 直到客户端运行会接收到下面数据并退出
  
Connected by ('192.168.1.120', 37548)
  
Received:  Hello, world
  再打开一个终端窗口执行:
  # 如果端口监听说明服务端运行正常
# 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编程
  服务端:
import socket  
HOST = ''
  
PORT = 50007
  
s = 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()
  客户端:
import socket  
HOST = '192.168.1.99'
  
PORT = 50007
  
s = 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命令,服务端接收到并执行,把返回结果回应给客户端。
  服务端:
#!/usr/bin/python  
# -*- coding: utf-8 -*-
  
import sys
  
import subprocess
  
import socket
  
HOST = ''
  
PORT = 50007
  
try:
  
    s = 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()
  客户端:
#!/usr/bin/python  
# -*- coding: utf-8 -*-
  
import sys
  
import socket
  
HOST = '192.168.1.120'
  
PORT = 50007
  
try:
  
    s = 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()
  查看运行效果,先运行服务端,再运行客户端:
# 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:
>>> 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,
  RequestHandlerClass, bind_and_activate=True)
服务器类,TCP协议  SocketServer.UDPServer(server_address,
  RequestHandlerClass, bind_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用户提供的请求处理类socketsocket对象上的服务器将监听传入的请求allow_reuse_address服务器是否允许地址的重用。默认Falserequest_queue_size请求队列的大小。socket_typesocket类型。socket.SOCK_STREAM或socket.SOCK_DGRAMtimeout超时时间,以秒为单位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编程
  服务端:
#!/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()
  另一个请求处理程序类,利用流(类文件对象简化通信提供标准文件接口):
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())
  客户端:
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
  服务端结果:
# python TCPServer.py  
127.0.0.1 wrote:
  
hello
  
127.0.0.1 wrote:
  
nice
  客户端结果:
# python TCPClient.py hello  
Sent: hello
  
Received: HELLO
  
# python TCPClient.py nice
  
Sent: nice
  
Received: NICE
  17.2.2 UDP编程
  服务端:
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()
  客户端:
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类的一个例子:
#!/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()
# 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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-552413-1-1.html 上篇帖子: Python Web 框架 Sanic Linux平台安装 下篇帖子: python3_装饰器_异常处理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表