摘要:网络编程的概念介绍;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()
运行结果:
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()
运行结果:
可以发现,数据传输速度比TCP快很多。
一般要求速度快,但不要求可靠到达的数据,就可以用UDP协议。
还有一点是服务器绑定的TCP端口与UDP端口不冲突,也就是说TCP的9999端口与UDP的9999端口是可以各自绑定的。
以上就是本节的全部内容,感谢你的阅读。
下一节内容:电子邮件
有任何问题与想法,欢迎评论与吐槽。
和博主一起学习Python吧( ̄▽ ̄)~*