【Python入门】39.网络编程

摘要:网络编程的概念介绍;TCP/IP协议的基本介绍;介绍Python的socket库,在Python中进行TCP编程与UDP编程。


*写在前面:为了更好的学习python,博主记录下自己的学习路程。本学习笔记基于廖雪峰的Python教程,如有侵权,请告知删除。欢迎与博主一起学习Pythonヽ( ̄▽ ̄)ノ *


目录

网络编程
TCP/IP协议
TCP编程
客户端
服务器
UDP编程

网络编程

计算机网络就是把各个计算机连接在一起,让各个计算机之间可以通信。

网络编程就是要通过程序编程来实现两个计算机之间的通信。

实际上,网络通信就是两个进程之间的通信

比如我们用浏览器访问百度网页,这一过程就是浏览器进程与百度服务器上的某个Web服务进程在通信。

网络编程对所有开发语言都是一样的。接下来将简单介绍TCP/IP协议,然后介绍如何在Python中进行TCP编程与UDP编程。

TCP/IP协议

早期的计算机网络,为了实现计算机之间的联网,各厂商自己规定自己的协议,从而实现联网。但是使用不同网络协议的计算机之间是不可以联网的。

后来为了解决这个问题,就规定了一套全球通用的协议标准,称互联网协议族(Internet Protocol Suite)。只要支持符合这一协议标准的协议,就能进入互联网(Internet)

互联网协议包含上百种协议,其中最重要的便是TCP/IP协议

IP协议负责把传输数据,它会把数据分成一块块,然后通过IP包,由发送端传输到接收端。由互联网线路复杂,两个计算机之间会有很多线路,所以IP协议并不能保证可以到达或者按顺序到达接收端。

TCP协议是建立在IP协议之上,它负责建立两个计算机之间的可靠连接,保证数据按量按顺序到达。如果出现包丢了,就会重新发送。

在错综复杂的互联网中,计算机用IP地址来标识自身的位置。

实际上IP地址是一个32位整数(IPv4),为了便于阅读,把32位整数按8位分组后以十进制表示,如:192.168.1.1

IPv6实际上是一个128位整数,把128位整数按16位分组后,以十六进制表示。
如:abab:910c:2222:5498:5475:1111:39f0:2520

一台计算机可以有多个IP地址,当一台计算机接入两个或多个网络时,如路由器,就会有两个或多个IP地址。

两个计算机之间通信仅靠IP地址是不行的,还需要多个端口来对应不同的网络程序。每个网络程序在进行通信时,会向操作系统申请唯一的端口号,当该进程需要多个连接时,就会申请多个端口。

一个完整的TCP报文需要包含源IP地址、目标IP地址、源端口、目标端口和需要传输的数据

TCP编程

在介绍TCP编程之前,我们要引入一个计算机术语——socket

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。——百度百科

socket在英文释义中是孔、插座的意思。在网络编程中,表示网络连接的一端。要实现网络连接,就要打开socket。而要打开socket,需要知道目标IP地址、端口号以及协议类型。

在Python中有socket库,可以用它来实现网络编程。

在建立TCP连接时,我们称主动发起连接的为客户端,被动响应连接的为服务器。

客户端

我们先进行客户端的编写。

  • 首先,引入socket库:
import socket
  • 然后,创建一个socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

这里的AF_INET表示指定使用IPv4协议,如果用IPv6,则为AF_INET6

SOCK_STREAM表示指定使用面向流的TCP协议。

  • 然后,建立连接:
s.connect(('ai.taobao.com', 80))

需要注意的是,这里的参数是一个tuple,里面包含目标IP地址和目标端口。

这里用域名ai.taobao.com可以自动转到淘宝服务器的IP地址。

80指的是目标端口。服务器所提供的每个服务会对应一个固定的端口号。而80端口就是Web服务的标准端口。还有其他如SMTP服务是25端口,FTP服务是21端口等。

小于1024的端口号是Internet标准服务的端口,大于1024的端口号可以任意使用

  • 然后,向服务器发送请求:

建立TCP连接后,就可以向百度服务器发送请求了。我们要求返回首页的内容:

s.send(b'GET / HTTP/1.1\r\nHost:ai.taobao.com\r\nConnection: close\r\n\r\n')

TCP连接创建的通道是双向的,即双方都可以给对方发送数据。

HTTP协议则规定发送信息的顺序——客户端必须先给服务端发送信息,服务端收到信息后才能给客户端发送信息。

发送的文本要符合HTTP格式。

  • 接下来,编写接收服务端信息部分:
buffer = []                  # 创建一个缓冲区,用于暂存接收数据
while True:
    d = s.recv(1024)         # 指定一次最多接收的数据字节数
    if d :
        buffer.append(d)     # 若有接收数据,则添加到缓冲区
    else: 
        break                # 直到数据接收完毕,退出循环

data = b''.join(buffer)      # 把数据存放到data变量中
 

recv(max)方法是指定一次接收的最大字节数,使用while循环接收数据,直到数据接收完毕就会退出循环。

  • 然后,关闭socket

调用close( )来关闭socket,这样就完成了网络通信客户端的编写:

s.close()
  • 最后,处理接收数据:

对接收的数据进行简单的处理,把HTTP头打印出来,把网页内容保存:

header, html = data.split(b'\r\n\r\n', 1)    # 分离HTTP头与网页内容
print(header.decode('utf_8'))                # 打印HTTP头
with open('baidu.html', 'wb') as f:          # 保存网页内容
    f.write(html)

这样,我们打开指定路径下的taobao.html就可以看到网页的内容。

我们来看一下完整的代码以及运行结果吧。

客户端:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(('ai.taobao.com', 80))

s.send(b'GET / HTTP/1.1\r\nHost:ai.taobao.com\r\nConnection: close\r\n\r\n')

buffer = []                  # 创建一个缓冲区,用于暂存接收数据
while True:
    d = s.recv(1024)         # 指定一次最多接收的数据字节数
    if d :
        buffer.append(d)     # 若有接收数据,则添加到缓冲区
    else: 
        break                # 直到数据接收完毕,退出循环

data = b''.join(buffer)      # 把数据存放到data变量中
 
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf_8'))
with open('taobao.html', 'wb') as f:
    f.write(html)

运行结果:

HTTP/1.1 200 OK 
Date: Mon, 10 Sep 2018 14:24:38 GMT 
Content-Type: text/html;charset=UTF-8 
Transfer-Encoding: chunked 
Connection: close 
Vary: Accept-Encoding 
Vary: Accept-Encoding 
S: STATUS_NOT_EXISTED 
Set-Cookie: t=e4ad8c4112aa5205748b3bf8780e216d; Domain=.taobao.com; Expires=Sun, 09-Dec-2018 14:24:38 GMT; Path=/ 
Set-Cookie: cookie2=1e5f1cc4e8adf4b072d111c70933bea9;Domain=.taobao.com;Path=/;HttpOnly 
Set-Cookie: v=0; Domain=.taobao.com; Path=/ 
Set-Cookie: _tb_token_=e78b393e779e9; Domain=.taobao.com; Path=/ 
P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR' 
Content-Language: zh-CN 
Server: Tengine/Aserver 
Timing-Allow-Origin: * 

在指定的路径下会生成一个taobao.html文件。

服务器

服务器负责响应连接。

它通过绑定一个固定的端口,来监听客户端的连接。一旦接收到某个客户端的连接,就创建一个socket,然后进行通信。

由于一个服务器端口可能会接收到多个客户端的连接,所以每个连接都需要一个新的进程或线程来处理,否则一个服务端口一次只能服务一个客户端。

下面我们编写一个简单的服务器,它接收客户端的连接,然后在接收的字符串上添加“hello”,并返回信息。

  • 首先,创建一个基于IPv4和TCP协议的socket
import socket

s = socket.socket(socket.AF_INET, slcket.SOCK_STREAM)

  • 然后,通过bind()方法绑定需要监听的端口:
s.bind(('127.0.0.1', 9999))

需要注意的是,这里的参数是一个tuple,含IP地址与端口号。

IP地址127.0.0.1是一个特殊地址,表示本机地址。若绑定这个地址,则客户端必须同时在本机运行才能连接。

由于我们编写的不是标准的服务端口,所以用9999端口号。

  • 然后,调用listen()方法监听绑定的端口,参数为最大的等待连接数:
s.listen(5)
print('Waiting for connecting...')

  • 接着,用while循环来永久接收客户端的连接:
while True:
    sock, addr = s.accept()                                  # 接收客户端的连接和地址
    t = threading.Thread(target=tcplink, args=(sock, addr)   # 创建新线程来处理TCP连接
    t.start()                                                # 启动线程

accept()方法会接收客户端的连接和地址。

通过threading.Thread()建立新线程来处理TCP连接。target为运行对象,args为传入参数。

  • 接着,我们来编写对响应连接部分:
def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')                     # 给客户端发送欢迎信息
    while True:
        data = sock.recv(1024)                 # 设定最大的接收数据字节数
        time.sleep(1)                          # 休眠1s
        if not data or data.decode('utf-8') == 'exit':        # 如果没有接收到数据或收到'exit'则退出
            break
        sock.send(('Hello,%s!' % data.decode('utf-8')).encode("utf-8"))   # 返回客户端的信息
    sock.close()
    print('Connection form %s:%s closed' % addr)

这样我们就完成了服务器的编写,最后我们需要编写一个简单的客户端来测试该服务器的运行。

我们来看一下完整的代码以及运行结果吧。

服务器:

import socket,threading,time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.bind(('127.0.0.1', 9999))

s.listen(5)
print('Waiting for connecting...')

def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('Hello,%s!' % data.decode('utf-8')).encode("utf-8"))
    sock.close()
    print('Connection form %s:%s closed' % addr)
    
while True:
    sock, addr = s.accept()                                   # 接收客户端的连接和地址
    t = threading.Thread(target=tcplink, args=(sock, addr))   # 创建新线程来处理TCP连接
    t.start()

客户端:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 9999))                           # 建立连接
print(s.recv(1024).decode('utf-8'))                      # 接收欢迎消息
for data in [b'Michael', b'Tracy', b'Sarah']:
    s.send(data)                                         # 发送数据
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

运行结果:


简单TCP编程.png

UDP编程

TCP是通过建立可靠连接,以流的方式传输数据。

而UDP则是面向无连接的协议。只需要知道目标的IP地址和端口号就能传输数据,无需建立连接。

UDP的优点是速度快,缺点是不能保证数据能够到达。

相比TCP,由于不用建立连接,UDP编程简单很多,同样从服务器与客户端入手。

服务器:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 999...')
while True:
    data, addr = s.recvfrom(1024)
    print('Receive from %s:%s.' % addr)
    s.sendto(b'Hello,%s!' % data, addr)

注意这里SOCK_DGRAM指定socket类型是UDP。不用调用listen()方法,直接获取数据。

客户端:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Ming', b'Hong', b'Leo']:
    s.sendto(data, ('127.0.0.1', 9999))
    print(s.recv(1024).decode('utf-8'))
s.close()

运行结果:


简单的UDP编程

可以发现,数据传输速度比TCP快很多。

一般要求速度快,但不要求可靠到达的数据,就可以用UDP协议。

还有一点是服务器绑定的TCP端口与UDP端口不冲突,也就是说TCP的9999端口与UDP的9999端口是可以各自绑定的。


以上就是本节的全部内容,感谢你的阅读。

下一节内容:电子邮件

有任何问题与想法,欢迎评论与吐槽。

和博主一起学习Python吧( ̄▽ ̄)~*

你可能感兴趣的:(【Python入门】39.网络编程)