1. 《计算机网络编程》
我们接触网络编程,肯定是要对网络编程的一些专业术语及基本理论知识是要有所认知的。python网络编程无非是在这些基础理论知识之上给我们提供了一些方便实用的网络库来供我们使用
。尽管做了非常底层的封装,并且给我们暴露了上层的应用接口,但是读者注意,尽管是这样,我们仍然需要学习基础的网络知识,才更有助于我们的发展
。实际上,这些基础知识掌握到头了,无论是python,c++,c#亦或是java等,在正式工作中,我们基本都是使用内置的网络库或第三方网络库去编写程序
。有关于网络编程的基础知识,此处将不会过多的讲解。如果有兴趣,可自行参考书籍《计算机网络(第7版)谢希仁版》
,这本书基本是所有高校的计算机专业必学的一门课程。这里给出网盘下载地址,根据需要,自行下载即可。
下载地址:
链接:
https://pan.baidu.com/s/1GPPdr3RyxhlQJPCQIzbO0Q
提取码:
eyef
2. ip地址和端口号
(1)
ip地址
整个互联网就是一个单一的、抽象的网路。IP地址就是给互联网上的每一台主机(或路由器)的每一个接口分配一个在全世界范围内是唯一的32位的标识符
。
(2)
端口号
两台计算机之间的进程进行相互通信,不仅需要对方的IP地址(为了找到计算机),还需要知道对方的端口号(为了找到对方计算机中的具体的应用进程)
。
(3)
解释
这和我们寄信的过程是类似的。当我们要给某人写信时,就必须在信封上注明收件人的通信地址(这相当于是IP地址)
。并且还要注明收件人的姓名(因为同一个住所里可能有好多人,这相当于是端口号)
。互联网上的计算机通信采用的是C/S架构(客户-服务器)模式。客户端在发起通信请求时,必须先知道对方服务器的IP地址和端口号
。
3. socket编程
socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。
根据RFC 793定义:端口号拼接到IP地址就构成了套接口(socket)
。因此,套接字的表示方式是 点分十进制的IP地址后面写上端口号,中间用冒号或逗号隔开。例如,若IP地址为192.168.1.232,端口是8888,那么得到的套接字就是(192.168.1.232:8888
)。
总之,我们有:
套接字 socket = (IP地址 : 端口号)
。
4. TCP协议的特点
TCP是TCP/IP体系中非常复杂的一个协议。下面介绍一下基本特点。
(1)
TCP是面向连接的运输层协议
这就是说,应用程序在使用TCP协议之前,必须首先建立TCP连接
。在传送完数据之后,必须释放已经建立的TCP连接。也就是说,应用进程之间的通信好像是在"打电话":通话前要先拨号建立连接,通话结束后要挂机释放连接。
(2)
每一条TCP连接只能有两个端点
每一条TCP连接只能是点对点的(一对一)
。
(3)
TCP提供可靠交付的服务
通过TCP连接传送的数据,无差错、不丢失、不重复,并且按序到达
。
(4)
TCP提供全双工通信
TCP允许通信双方的应用进程在任何时候都能发送数据
。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。在发送时,应用程序在把数据传送给TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。在接收时,TCP 把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。
(5)
面向字节流
TCP中的“流”(stream)指的是流入到进程或从进程流出的字节序列。
“面向字节流”.的含义是:虽然应用程序和TCP的交互是一次一个数据块〈大小个等),但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流
。TCP 并不知道所传达的字节流的含义
。TCP 不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系
(例如,发送方应用程序交给发送方的TCP共10个数掂坎,但接收方的TCP可能只用了4个数据块就把收到的字节流交付上层的应用程序)。但按收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接收方的应用程序必须有能力识别收到的字节流,把它还原成有意义的应用层数据
。(实际上在正式的项目开发中,客户端和服务端之间的数据交互一定会基于自定义的通信协议,这涉及到拆包组包等
)
5. TCP编程之客户端
5.1
客户端程序
# 导入socket模块
import socket
# 创建socket对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM代指TCP协议
# 设置端口号
port = 8888
# 连接服务器, 这里连接本地8888端口开启的服务器
s.connect(('127.0.0.1', port))
# 接收小于 1024 字节的数据
while True:
msg = s.recv(1024)
print (msg.decode('utf-8'))
# 关闭套接字
s.close()
好,上述一个简单的tcp客户端就写完了,我们需要验证,该如何验证?
为此,我们借助一个网络调试助手NetAssist(可自行下载)。在此处,我们使用NetAssist来开启一个基于本机的8888端口的服务器
,当我们运行程序后,如果不出意外,就会建立连接,然后我们在NetAssist中向我们的客户端程序发送数据,客户端这边应该是能收到服务器发来的数据的。
5.2
验证步骤
[a].
首先开启服务器
[b].
运行客户端程序
然后在服务端中向客户端发送数据,进行观察。
运行程序后,发现客户端和服务器确实建立了连接。并且服务端发送的数据,在客户端也是同样接收到了。
6. TCP编程之服务端
6.1
服务端程序
# 导入相关模块
import socket, threading, time
# 创建socket对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口
serversocket.bind(('127.0.0.1', 9999))
# 监听端口, 并设置最大连接数
serversocket.listen(5)
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 from %s:%s closed.' % addr)
# 每个连接都必须创建新的线程来处理这条对话, 否则单线程在处理连接的过程中, 无法接受其他客户端的连接.
# 关于线程, 可以参考后续文章.
while True:
# 接收来自客户端的连接, clicketsocket是后续与客户端通信的套接字
clientsocket, addr = serversocket.accept()
t = threading.Thread(target=tcplink, args=(clientsocket, addr))
t.start()
6.2
验证步骤
同样,我们使用NetAssist来开启两个客户端,使其分别连接我们的服务端程序,然后进行交互。
[a].
首先运行服务端程序
[b].
启动客户端
运行2个NetAssist,然后进行连接。
7. 写在最后
tcp 协议相对比较复杂,但是内置库以及第三方库为我们进行了深度封装,使得我们使用几句代码就可以创建一个客户端或服务器,以上代码是最简单的示例程序。在正式的项目开发中,远要复杂的很多,比如由于tcp是基于流的,所以我们在接收数据时要进行协议划分来完成基于tcp的拆包组包等;另外,网络编程中几乎是要伴随着多线程来进行开发的。关于多线程编程,参考后续文章。