Skip to content

Latest commit

 

History

History
452 lines (318 loc) · 18.9 KB

10.md

File metadata and controls

452 lines (318 loc) · 18.9 KB

第十章 网络基础 - Socket编程

本章中我们将学习sockets和三种互联网协议:http, ftplib和urllib。我们会学习Python中用于网络的socket模块。http是一个用于处理超文本传输协议的包。ftplib用于执行自动化的FTP相关工作。urllib是一个处理URL相关工作的包。

本章中我们将学习如下内容:

  • Sockets
  • http包
  • ftplib模块
  • urllib包

Socket接口

这一部分中, 我们将学习socket(套接字接口)的知识。我们将使用Python的socket模块。socket是本地或远程机器间通过的端点(endpoint)。socket模块有一个socket类,用于处理数据通道。它还包含网络相关操作的函数。要使用socket模块的功能,首先我们要导入socket模块。

我们来看看如何创建一个socket。socket类有一个socket函数,带有两个参数:address_family和socket类型。

语法如下:

import socket
s = socket.socket(address_family, socket type)

address_family控制OSI(Open System Interconnection 开放式系统互联)网络分层协议

socket type控制传输层协议

Python支持三种地址类型:AF_INET, AF_INET6和AF_UNIX。最常用的为AF_INET,用于因特网寻址。AF_INET6用于IPv6因特网寻址。AF_UNIX用于Unix域套接字(Unix Domain Sockets - UDS),是一种跨进程通讯协议。

有两种socket类型:SOCK_DGRAM和SOCK_STREAM。SOCK_DGRAM套接字类型用于面向消息的datagram传输,这些与UDP协议相关联。datagram套接字投递单个消息。SOCK_STREAM处理面向数据流的传输,与TCP协议相关联。流套接字接口在客户端和服务器端之间提供字节流。

socket可配置为服务端和客户端接口。在TCP/IP套接字接口都连接时,通讯是双向的。下面我们来探讨一个客户端-服务端通讯的示例。我们会创建两个脚本文件:server.py和client.py。

server.py脚本内容如下:

import socket

host_name  = socket.gethostname()
port = 5000
s_socket = socket.socket()
s_socket.bind((host_name, port))
s_socket.listen(2)

conn, address = s_socket.accept()
print("Connection from: " + str(address))

while True:
        recv_data = conn.recv(1024).decode()
        if not recv_data:
                break
        print("from connected user: " + str(recv_data))
        recv_data = input(' -> ')
        conn.send(recv_data.encode())

conn.close()

下面我们来编写客户端脚本。client.py脚本内容如下:

import socket

host_name = socket.gethostname()
port = 5000

c_socket = socket.socket()
c_socket.connect((host_name, port))
msg = input(" -> ")

while msg.lower().strip() != 'bye':
        c_socket.send(msg.encode())
        recv_data = c_socket.recv(1024).decode()
        print('Received from server: ' + recv_data)
        msg = input(" -> ")

c_socket.close()

下面我们要在两个不同的终端中运行这两个程序。第一个终端中运行server.py,在第二个终端中运行client.py。

输出结果如下:

Socket 服务端和客户端运行结果

http包

这部分中,我们将学习http包相关知识。http包有四个模块:

  • http.client: 这是一个底层HTTP协议客户端
  • http.server: 包含基本HTTP服务器类
  • http.cookies: 用于实现带cookie的状态管理
  • http.cookiejar: 该模块提供cookie持久化

这一部分中,我们将学习http.client和http.server模块。

http.client模块

我们将来看两种http请求:GET和POST。我们还会来做一个http连接。

首先,我们来探讨一个进行http连接的示例。为此创建一个脚本make_connection.py并在其中编写如下内容:

import http.client

con_obj = http.client.HTTPConnection("https://www.baidu.com", 80, timeout=20)
print(con_obj)

运行脚本,我们将会得到如下输出:

$ python3 make_connection.py
<http.client.HTTPConnection object at 0x7f4f0d6e0438>

在上例中,我们对传入的 URL 的80端口以一个指定的超时时间进行了连接。

下面我们来看 http GET请求方法,使用GET请求方法我们可以看一个获取返回码以及头部列表的示例。创建一个脚本get_example.py并编写如下内容:

import http.client

con_obj = http.client.HTTPSConnection("www.imdb.com")
con_obj.request("GET", "/")
response = con_obj.getresponse()

print("Status: {}".format(response.status))

headers_list = response.getheaders()
print("Headers: {}".format(headers_list))

con_obj.close()

运行脚本如下:

$ python3 get_example.py

得到的结果如下:

Status: 200
Headers: [('Content-Type', 'text/html;charset=UTF-8'), ('Transfer-Encoding', 'chunked'), ('Connection', 'keep-alive'), ('Server', 'Server'), ('Date', 'Sun, 10 Mar 2019 23:38:15 GMT'), ('Strict-Transport-Security', 'max-age=47474747; includeSubDomains; preload'), ('X-Frame-Options', 'SAMEORIGIN'), ('Content-Security-Policy', "frame-ancestors 'self' imdb.com *.imdb.com *.media-imdb.com withoutabox.com *.withoutabox.com amazon.com *.amazon.com amazon.co.uk *.amazon.co.uk amazon.de *.amazon.de translate.google.com images.google.com www.google.com www.google.co.uk search.aol.com bing.com www.bing.com"), ('Ad-Unit', 'imdb.home.homepage'), ('Entity-Id', ''), ('Section-Id', 'homepage'), ('Page-Id', 'homepage'), ('Content-Language', 'en-US'), ('Set-Cookie', 'uu=BCYt02mIaqWrtBRATN2QI_74kecgylRA4PBQ84rOmTRgH0lzs_-_4vqASi1sM9EiaFzDTa_vd9WX%0D%0AtQGMqLFOWs4cz8_neIhv1zjnL_V9aq4mq6UUCO4DC9ysALBFhJPLzxgCrBTR7IdTK0Aw4fWCzb2g%0D%0AkA%0D%0A; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 02:52:22 GMT; Path=/; Secure'), ('Set-Cookie', 'session-id=135-0078761-6346505; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 02:52:22 GMT; Path=/; Secure'), ('Set-Cookie', 'session-id-time=2182981095; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 02:52:22 GMT; Path=/; Secure'), ('Vary', 'Accept-Encoding,X-Amzn-CDN-Cache,User-Agent'), ('x-amz-rid', '4QWFEM27QGYNC08J00YP'), ('X-Cache', 'Miss from cloudfront'), ('Via', '1.1 4798af72c7f6cead30e0da0525c1880c.cloudfront.net (CloudFront)'), ('X-Amz-Cf-Id', 'T3rlZyMEo2obc6NzsErVSy-pYEPJ26JE72ff2fPzzNC4SKpEXzTKQg==')]

上例中,我们使用了HTTPSConnection,因为该网站以HTTPS协议提供服务。我们根据网站的具体情况可以使用HTTPSConnection或HTTPConnection。我们传入了一个 URL 并通过连接对象查看了其状态。然后,我们获取到了一个header列表。该header列表中包含服务器返回数据类型的相关信息。getheaders() 方法可获取到header列表。

下面我们来看一个POST请求的示例。我们可以使用HTTP POST来向URL post数据。下面创建一个脚本post_example.py并编写如下内容:

import http.client
import json

con_obj = http.client.HTTPSConnection('www.httpbin.org')
headers_list = {'Content-Type': 'application/json'}
post_text = {'text': 'Hello World'}
json_data = json.dumps(post_text)
con_obj.request('POST', '/post', json_data, headers_list)
response = con_obj.getresponse()
print(response.read().decode())

运行脚本如下:

$ python3 post_example.py

我们将得到如下的输出:

{
  "args": {},
  "data": "{"text": "Hello World"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "23",
    "Content-Type": "application/json",
    "Host": "www.httpbin.org"
  },
  "json": {
    "text": "Hello World"
  },
  "origin": "112.64.119.235, 112.64.119.235",
  "url": "https://www.httpbin.org/post"
}

上例中,首先我们创建了一个HTTPSConnection对象。然后,我们创建了一个post_text对象,它post 了Hello World。再后,我们编写了一个POST请求,并接收到了响应信息。

http.server模块

这一部分中,我们将学习http包中的一个模块:http.server模块。该模块定义用于实现HTTP服务端的类。它有两个方法:GET和HEAD。通过使用该模块,我们可以在网络上分享文件。我们可以在任意端口上运行http服务端。确保该端口号大于1024。默认端口号为8000。

可像下面这样使用http.server。

首先进行到选定的目录,并运行如下命令:

$ python3 -m http.server 9000

这时打开浏览器,在地址栏中键入localhost:9000并按下Enter键。将会得到如下输出:

$ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
127.0.0.1 - - [10/Mar/2019 23:54:51] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Mar/2019 23:54:58] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Mar/2019 23:54:59] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Mar/2019 23:55:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Mar/2019 23:55:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [10/Mar/2019 23:55:01] "GET / HTTP/1.1" 200 -
172.20.10.2 - - [10/Mar/2019 23:55:27] "GET / HTTP/1.1" 200 -
172.20.10.2 - - [10/Mar/2019 23:55:28] code 404, message File not found
172.20.10.2 - - [10/Mar/2019 23:55:28] "GET /favicon.ico HTTP/1.1" 404 -
172.20.10.2 - - [10/Mar/2019 23:55:51] "GET / HTTP/1.1" 200 -
172.20.10.2 - - [10/Mar/2019 23:56:00] "GET / HTTP/1.1" 200 -

译者注: 纯命令行服务器可通过 curl localhost:9000进行访问,或通过服务器 IP 地址来进行访问,以示 Alan 演示了这两种效果

ftplib模块

ftplibj在Python中提供了包含所有FTP协议各类操作的功能。ftplib包含FTP客户端类,以及一些帮助函数。使用该模块我们可以很容易地连接FTP服务器来获取多个文件并对它们进行处理。通过导入ftplib模块,我们就可以使用其中的所有功能了。

这一部分中,我们将讲解如何使用ftplib模块来进行FTP传输。我们会一起来看各类FTP对象。

下载文件

这一部分中,我们将学习使用ftplib从另一台机器上下载文件。为此,创建一个get_ftp_files.py脚本并编写如下内容:

import os
from ftplib import FTP

ftp = FTP('FTP域名或IP')
with ftp:
        ftp.login('用户名', '密码')
        ftp.cwd('/home/student/work/')
        files = ftp.nlst()
        print(files)

        # 打印文件
        for file in files:
                print("Downloading..." + file)
                ftp.retrbinary("RETR " + file, open("/home/student/testing/" + file, 'wb').write)

ftp.close()

运行脚本如下:

$ python3 get_ftp_files.py

得到的结果如下:

['hello', 'hello.c', 'sample.txt', 'strip_hello', 'test.py']
Downloading...hello
Downloading...hello.c
Downloading...sample.txt
Downloading...strip_hello
Downloading...test.py

译者注: 要实现以上,首先要确保存在相关文件和目录并安装了 ftp 服务:sudo apt-get install vsftpdu并对/etc/vsftpd.conf进行相应的配置。

上例中,我们使用ftplib模块从主机上获取了多个文件。首先,我们传入了另一台机器的IP地址、用户名和密码。要从主机上获取所有文件,我们使用了ftp.nlst() 函数,并使用ftp.retrbinary()函数将这些文件下载到了本地电脑。

使用getwelcome()获取欢迎信息

一旦建立了初始化连接,服务端通常会返回一条欢迎信息。这一消息来自getwelcome()函数,有时会包含声明信息以及与可能与用户相关的帮助信息。

下面我们来看一个getwelcome()的示例,创建一个脚本get_welcome_msg.py并编写如下内容:

from ftplib import FTP

ftp = FTP('FTP的域名或 IP 地址')
ftp.login('用户名', '密码')

welcome_msg = ftp.getwelcome()
print(welcome_msg)

ftp.close()

运行脚本如下:

$ python3 get_welcome_msg.py
220 (vsFTPd 3.0.3)

以上代码中,首先我们传入了另一台机器的IP地址、用户名和密码。我们使用了getwelcome()函数来获取初始化连接建立之后的信息。

使用sendcmd()函数向服务器发送命令

这一部分中,我们将学习sendcmd()函数。我们可以使用sendcmd()来发送一个简单字符串命令来获取字符串响应。客户端可以发送STAT, PWD, RETR和STOR等FTP命令。ftplib模块中有多个方法能封装这些命令。这些命令可使用sendcmd()或voidcmd()方法进行发送。作为示例,我们将发送一个STAT命令来查看服务端的状态。

创建一个脚本send_command.py并编写如下内容:

from ftplib import FTP

ftp = FTP('FTP服务器域名或 IP 地址')
ftp.login('用户名', '密码')

ftp.cwd('/home/student')
s_cmd_stat = ftp.sendcmd('STAT')
print(s_cmd_stat)
print()

s_cmd_pwd = ftp.sendcmd('PWD')
print(s_cmd_pwd)
print()

ftp.close()

运行脚本如下:

$ python3 send_command.py

将得到如下输出:

211-FTP server status:
     Connected to ::ffff:192.168.xxx.xxx
     Logged in as student
     TYPE: ASCII
     No session bandwidth limit
     Session timeout in seconds is 300
     Control connection is plain text
     Data connections will be plain text
     At session startup, client count was 1
     vsFTPd 3.0.3 - secure, fast, stable
211 End of status

257 "/home/student" is the current directory

以上代码中,我们首先传入了另一台机器的IP地址、用户名和密码。接着,我们使用了sendcmd()向另一台机器发送了STAT命令。然后使用sendcmd()发送PWD命令。

urllib包

和http相似,urllib也是包含处理 URL 的诸多模块的一个包。urllib模块允许我们通过脚本访问多个网站。我们可以使用这个模块来下载数据、解决数据、修改header等。

urllib有几个不同的模块,列出如下:

  • urllib.request: 用于打开和读取URL
  • urllib.error: 包含urllib.request抛出的异常
  • urllib.parse: 用于解析URL
  • urllib.robotparser: 用于解析robots.txt文件

这一部分中,我们将学习使用urllib来打开URL以及如何从URL读取html文件。我们会看一个urllib用法的简单示例。我们将导入urllib.requests。然后将打开的 URL 分配给一个变量,再后,我们会使用a .read()命令来从URL读取数据。

创建脚本url_requests_example.py并在其中编写如下内容:

import urllib.request

x = urllib.request.urlopen('https://www.imdb.com/')
print(x.read())

运行脚本如下:

$ python3 url_requests_example.py

得到的结果如下:

b'\n\n<!DOCTYPE html>\n<html\n    xmlns:og="http://ogp.me/ns#"\n    xmlns:fb="http://www.facebook.com/2008/fbml">\n    <head>\n         \n        <meta charset="utf-8">\n        <meta http-equiv="X-UA-Compatible" content="IE=edge">\n\n    \
n    \n    \n\n    \n    \n    \n\n    <meta name="apple-itunes-app" content="app-id=342792525, app-argument=imdb:///?src=mdot">\n\n\n\n        <script type="text/javascript">var IMDbTimer={starttime: new Date().getTime(),pt:'java'};</script>\n\n<script>\n    if (typeof uet == 'function') {\n      uet("bb", "LoadTitle", {wb: 1});\n    }\n</script>\n  <script>(function(t){ (t.events = t.events|| {})["csm_head_pre_title"] = new Date().getTime(); })(IMDbTimer);</script>\n     <title>Ratings and Reviews for New Movies and TV Shows - IMDb</title>\n  <script>(function(t){ (t.events = t.events || {})["csm_head_post_title"] = new Date().getTime(); })(IMDbTimer);</script>\n<script>\n    if (typeof uet == 'function') {\n      uet("be", "LoadTitle", {wb: 1});\n    }\n</script>\n<script>\n  if (typeof uex == 'function') {\n      uex("ld", "LoadTitle", {wb: 1});\n }\n</script>\n\n        <link rel="canonical" href="https://www.imdb.com/" />\n
        <meta property="og:url" content="http://www.imdb.com/" />\n        <link rel="alternate" media="only screen and (max-width: 640px)" href="https://m.imdb.com/">\n\n<script>\n    if (typeof uet == 'function') {\n      uet("bb", "Loa
dIcons", {wb: 1});\n    }\n</script>\n  <script>(function(t){ (t.events = t.events || {})["csm_head_pre_icon"] = new Date().getTime(); })(IMDbTimer);</script>\n        <link href="https://m.media-amazon.com/images/G/01/imdb/images/safari-favicon-517611381._CB483525257_.svg" mask rel="icon" sizes="any">\n        <link rel="icon" type="image/ico" href="https://m.media-amazon.com/images/G/01/imdb/images/favicon-2165806970._CB470047330_.ico" />\n...

上例中,我们使用了read()方法,它返回一个字节数组。这会以不易于人类阅读的格式打印Imdb首页返回的数据,但我们可以使用HTML解析器来从中提取有用的信息。

Python urllib响应头

我们可以对响应对象调用 info()函数来获取响应头。它返回一个字典,这样我们还可以从响应中提取指定的头信息数据。创建一个脚本url_response_header.py并编写如下内容:

import urllib.request

x = urllib.request.urlopen('https://www.imdb.com/')
print(x.info())

运行脚本如下:

$ python3 url_response_header.py

输出如下:

Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Connection: close
Server: Server
Date: Mon, 11 Mar 2019 14:46:53 GMT
Strict-Transport-Security: max-age=47474747; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' imdb.com *.imdb.com *.media-imdb.com withoutabox.com *.withoutabox.com amazon.com *.amazon.com amazon.co.uk *.amazon.co.uk amazon.de *.amazon.de translate.google.com images.google.com www.google.com www.google.co.uk search.aol.com bing.com www.bing.com
Content-Language: en-US
Set-Cookie: uu=BCYk-Lx1Rx5JCui5VFoJirwDNEfFoFvCbsJ5orMtLw1TDK8-fF_BQTLUI_IfGdVeukWHb0gYP9oY%0D%0AuBTtmAFSPcQNwm4gEvjn6kprXoJKRWKIVfNU7LVaAljFoZM-vKFm9ipaaOijaiqEchXtg6piAL9F%0D%0AmQ%0D%0A; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 18:01:00 GMT; Path=/; Secure
Set-Cookie: session-id=000-0000000-0000000; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 18:01:00 GMT; Path=/; Secure
Set-Cookie: session-id-time=2183035613; Domain=.imdb.com; Expires=Sat, 29-Mar-2087 18:01:00 GMT; Path=/; Secure
Vary: Accept-Encoding,X-Amzn-CDN-Cache,User-Agent
x-amz-rid: M5WX23YG1PNN3CNBJ6ZP
X-Cache: Miss from cloudfront
Via: 1.1 3a189d473309dc117059fecb2737991b.cloudfront.net (CloudFront)
X-Amz-Cf-Id: uqSqfC60aZbj-SrxB6EVNjjzKAsl1veNjbBxpSBgQ2gMKHcvYgjQQg==

总结

本章中,我们学习了socket,它用于服务端-客户端双向通讯。我们还学习了三个互联网模块:http, ftplib和urllib。http包中有服务端和客户端模块:分别为http.client和http.server。使用ftplib,我们从另一台机器上下载了文件。我们还看了欢迎消息的知识以及发送send命令。

下一章中将涵盖创建和发送邮件的知识。我们会学习消息格式以及添加多媒体内容。同时,我们会学习SMTP, POP和IMAP服务器。

课后问题

  1. 什么是socket编程?
  2. 什么是RPC?
  3. 导入用户定义的模块或文件有哪些不同的方式?
  4. 列表和元组之间的区别是什么?
  5. 字典中的键是否可以重复?
  6. urllib, urllib2和requests模块之间的区别是什么?

扩展阅读