第十七章 Python网络编程

Socket简介

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

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

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

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

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

Socket数据处理流程如图:

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演示下。

先写一个服务端:

#!/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 and https://yq.aliyun.com/u/lizhenliang
QQ群:323779636(Shell/Python运维开发群)


17.2 SocketServer

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

下面是几个常用的类:

SocketServer.TCPServer(server_addressRequestHandlerClassbind_and_activate=True) 服务器类,TCP协议
SocketServer.UDPServer(server_addressRequestHandlerClassbind_and_activate=True) 服务器类,UDP协议
SocketServer.BaseServer(server_addressRequestHandlerClass) 这个是所有服务器对象的超类。它定义了接口,不提供大多数方法,在子类中进行。
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编程

服务端:

#!/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

时间: 2024-10-03 00:53:59

第十七章 Python网络编程的相关文章

python 教程 第十七章、 网络编程

第十七章. 网络编程 1)    FTP客户端 import ftplib import os import socket HOST = '127.0.0.1' DIRN = 'menus' FILE = 'hello.txt' USER = 'taojin' PASS = 'pass123' def main(): try: f = ftplib.FTP(HOST) f.login(user = USER, passwd = PASS) f.cwd(DIRN) f.retrbinary('RE

python网络编程之数据传输UDP实例分析

  本文实例讲述了python网络编程之数据传输UDP实现方法.分享给大家供大家参考.具体分析如下: 一.问题: 你觉得网络上像msn,qq之类的工具在多台机器之间互相传输数据神秘吗?你也想玩一下在两台机器之间传数据吗?今天让python告诉我们基本原理吧,当然只是做简单的了解,实际情况复杂的多. 我们今天用python实现一个简单的udp程序. 二.程序实现: 1) 使用模块 (socket)套接字模块: 套接字模块是一个非常简单的基于对象的接口,它提供对低层BSD套接字样式网络的访问 .使用

python网络编程之文件下载实例分析

  本文实例讲述了python网络编程之文件下载实现方法.分享给大家供大家参考.具体如下: 真是越看越喜欢python啊,想要了解它提供的http和ftp下载功能,原来是如此的简单. 1.相应模块 ftplib模块定义了FTP类和一些方法,用以进行客户端的ftp编程.我们可用python编写一个自已的ftp客户端程序,用于下载文件或镜像站点.如果想了解ftp协议的详细内容,请参考RFC959或是查看python帮助吧. Urllib模块提供了非常高级的接口来从网络上抓取数据,主要使用到的是url

使用rpclib进行Python网络编程时的注释问题

       这篇文章主要介绍了使用rpclib进行Python网络编程时的注释问题,作者讲到了自己在编写服务器时要用unicode注释等需要注意的地方,需要的朋友可以参考下            rpclib 是一个非常好用的 python webservice 库,可以动态的生成 wsdl, 不过这个项目已经基本停止,并被一个新的项目取代 spyne,由于旧的项目 工作已经比较稳定,所以我没有贸然升级到 spyne.         我在 rpclib 编写 service 方法时,遇到一个

python网络编程之读取网站根目录实例_python

本文实例讲述了python网络编程之读取网站根目录的方法,分享给大家供大家参考. 具体实现方法如下: import socket, sys port = 70 host = "quux.org" filename = "//" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.sendall(filename+"\r\n") while(

python网络编程实例简析_python

本文实例讲述了python网络编程,分享给大家供大家参考. 具体方法如下: 服务端代码如下: from SocketServer import(TCPServer as TCP, StreamRequestHandler as SRH) from time import ctime HOST = '' PORT = 21567 ADDR = (HOST, PORT) class MyRequestHandle(SRH): def handle(self): print 'connecting f

python网络编程学习笔记(二):socket建立网络客户端_python

1.建立socket 建立socket对象需要搞清通信类型和协议家族.通信类型指明了用什么协议来传输数据.协议的例子包括IPv4.IPv6.IPX\SPX.AFP.对于internet通信,通信类型基本上都是AF_INET(和IPv4对应).协议家族一般表示TCP通信的SOCK_STREAM或者表示UDP通信的SOCK_DGRAM.因此对于TCP通信,建立一个socket连接的语句为:s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)对于UDP通

python 网络编程常用代码段_python

服务器端代码: # -*- coding: cp936 -*- import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#初始化socket sock.bind(("127.0.0.1", 8001))#绑定本机地址,8001端口 sock.listen(5)#等待客户连接 while True: print "waiting client connection..." connec

python网络编程学习笔记(一)_python

学习用书:<python 网络编程基础>作者John Goerzen 第一部分底层网络学习         Python提供了访问底层操作系统Socket接口的全部方法,需要的时候这些接口可以提供灵活而强有力的功能. (1)基本客户端操作         在<python 网络编程基础>一书中,作者列出了一个简单的Python客户端程序,具体如下: 复制代码 代码如下: import socket,sysport =70host=sys.argv[1] filename=sys.a