在信息化, 网络化的时代浪潮下, 基本上所有程序都是网络程序. 最大的区别无非网络环境的区别: 内网和外网. Python语言提供了大量的内置模块和第三方模块用于支持各种网络访问,而且Python语言在网络通信方面的优点特别突出,远远领先其他语言.
网络编程就是如何在程序中实现两台计算机的通信.
IP是Internet Protocol Address,即"互联网协议地址". 用来标识网络中的一个通信实体的地址. 通信实体可以是计算机、路由器等. 互联网的每个服务器都要有自己的IP地址,而每个局域网的计算机要通信也要配置IP地址
ip地址的分类
常用地址分类如下所示, E类地址: 224.0.0.1~239.255.255.254
, F类地址: 240.0..0.1~239.255.255.254
.
因为使用较少因此不做单独介绍
IPV6
公有地址 公有地址(Public address)由Inter NIC(Internet NetworkInformation Center互联网信息中心)负责. 这些IP地址分配给注册并向Inter NIC提出申请的组织机构, 通过它直接访问互联网.
私有地址 私有地址(Private address)属于非注册地址,专门为组织机构内部使用. 以下列出留用的内部私有地址
127.0.0.1
本机地址;
192.168.0.0--192.168.255.255
: 私有地址,属于非注册地址,专门为组织机构内部使用端口号用来识别计算机中进行通信的应用程序. 因此它也被称为程序地址. 一台计算机上同时可以运行多个程序, 传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地进行数据传输
OSI是Open System Interconnection的缩写,意为开放式系统互联. 国际标准化组织(ISO)制定了OSI模型,该模型定义了不同计算机互联的标准,是设计和描述计算机网络通信的基本框架; TCP/IP 是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,网络接口层(物理+数据链路层).
ISO模型与TCP/IP模型的对应关系如图所示
TCP(Transmission Control Protocol,传输控制协议): 使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据 UDP(User Data Protocol,用户数据报协议): 是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上
TCP是面向连接的,传输数据安全,稳定,效率相对较低 UDP是面向无连接的,传输数据不安全,效率较高
通信传输中的数据单位,一般也称“数据包”或者“数据报”, 在数据包中包括:包、帧、数据包、段、消息 网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据. 首部的结构由协议的具体规范详细定义, 在数据包的首部,明确标明了协议应该如何读取数据.
数据包结构:
数据包处理流程:
TCP协议和UDP协议是传输层的两种协议. Socket是传输层供给应用层的编程接口,所以Socket编程就分为TCP编程和UDP编程两类
socket()函数介绍
语法 在Python 中,通常用一个Socket表示“打开了一个网络连接”,语法格式如下:
socket.socket([family[, type[, proto]]])
family
: 套接字家族可以使 AF_UNIX 或者 AF_INET ;
AF 表示ADDRESS FAMILY 地址族, AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型;而AF_UNIX 则是 Unix 系统本地通信.type
: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM
(TCP) 或 SOCK_DGRAM
(UDP) ;protocol
: 一般不填,默认为0例如: 创建套接字UDP/IP套接字,可以调用 socket.socket()
. 示例代码如下:
udpSocket=socket.socket (AF_INET,SOCK_DGRAM)
socket对象的内置函数和属性 在Python语言中socket对象中,提供如表所示的内置函数
UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包. 虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议.
实操: 实现udp通信
服务端代码编写
from socket import *
"""UDP接收数据"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(传入1个元组)
s.bind(("127.0.0.1", 8848))
print("等待接收数据:")
# 3. 接收数据, 1024为当前最大传输字节
rspData = s.recvfrom(1024)
print(rspData)
print(f'收到远程的消息: {rspData[0].decode("utf-8")}, from{rspData[1]}')
# 4. 关闭连接
s.close()
客户端代码编写
from socket import *
"""udp发送数据"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(发送时绑定的, 是接收的端口)
addr = ("127.0.0.1", 8848)
data = input("请输入:")
# 3. 发送数据
s.sendto(data.encode("utf-8"), addr)
# 4. 关闭连接
s.close()
按顺序分别启动服务端模块(接收数据) 和 客户端模块(发送数据)
运行结果
核心: 利用While循环让程序持续挂起, 并且设置一个点让循环关闭
实操: 实现基于UPD的持续通信
服务端代码
from socket import *
"""持续通信: UDP接收数据"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(传入1个元组)
s.bind(("127.0.0.1", 8848))
print("等待接收数据:")
while True:
# 3. 接收数据, 1024为当前最大传输字节
rspData = s.recvfrom(1024)
rspContent = rspData[0].decode("utf-8")
print(f'收到远程的消息: {rspContent}')
if rspContent == "88":
print("结束聊天! ")
break
# 4. 关闭连接
s.close()
客户端代码
from socket import *
"""持续通信: udp发送数据"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(发送时绑定的, 是接收的端口)
addr = ("127.0.0.1", 8848)
while True:
data = input("请输入:")
# 3. 发送数据
s.sendto(data.encode("utf-8"), addr)
if data == "88":
print("结束聊天!")
break
# 4. 关闭连接
s.close()
运行结果
核心: 结合多线程的实现方式, 然后利用持续通信的代码进行改造, 将接收和发送的方法通过类包装的形式来绑定线程并启动
实操: 基于多线程 + UDP实现socket编程
服务端代码
from socket import *
from threading import Thread
# 不停接收
def recv_data():
while True:
# 3. 接收数据, 1024为当前最大传输字节
rspData = s.recvfrom(1024)
rspContent = rspData[0].decode("utf-8")
print(f'服务端收到远程的消息: {rspContent}')
if rspContent == "88":
print("客户端请求结束聊天! ")
break
# 不停发送
def send_data():
addr = ("127.0.0.1", 7777)
while True:
data = input(">")
s.sendto(data.encode("utf-8"), addr)
if data == "88":
break
if __name__ == '__main__':
"""多线程实现: UDP实现多线程服务端"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(传入1个元组)
s.bind(("127.0.0.1", 9999))
print("等待接收数据:")
# 创建两个线程
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
客户端代码
from socket import *
from threading import Thread
# 不停接收
def recv_data():
while True:
# 3. 接收数据, 1024为当前最大传输字节
rspData = s.recvfrom(1024)
rspContent = rspData[0].decode("utf-8")
print(f'客户端收到远程的消息: {rspContent}')
if rspContent == "88":
print("服务端请求结束聊天! ")
break
# 不停发送
def send_data():
while True:
data = input(">")
addr = ("127.0.0.1", 9999)
s.sendto(data.encode("utf-8"), addr)
if data == "88":
break
if __name__ == '__main__':
"""多线程实现: UDP实现多线程客户端"""
# 1. 创建套接字
s = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定端口(传入1个元组)
s.bind(("127.0.0.1", 7777))
# 创建两个线程
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
运行结果
面向连接的Socket使用的协议是TCP协议. TCP的Socket名称是SOCK_STREAM, 创建套接字TCP套接字,可以调用 socket.socket()
创建Socket服务器程序的步骤如下:
实操: 实现TCP通信
服务端代码编写
from socket import *
"""TCP服务器端接收数据"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 绑定端口号
tcp.bind(("127.0.0.1", 8979))
# 3. 监听端口号
tcp.listen(5)
# 4. 等待socket连接
"""
client 表示这个新的客户端
client_info 表示这个新的客户端的ip以及port
"""
client, client_info = tcp.accept()
# 5. 读取数据
recv_data = client.recv(1024)
print(f"收到信息:{recv_data.decode('utf-8')},来自:{client_info}")
# 6. 关闭连接
client.close()
tcp.close()
客户端代码编写
from socket import *
"""TCP客户端发送数据到服务端"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 连接端口号(和接收略微不同)
tcp.connect(("127.0.0.1", 8979))
"""
注意:
1. tcp客户端已经链接好了服务器,所以在以后的数据发送中,不需要填写对方的ip和port----->打电话
2. udp在发送数据的时候,因为没有之前的链接,所以需要在每次的发送中,都要填写接收方的ip和port----->写信
"""
# 3. 等待socket连接
tcp.send("客户端发送消息".encode("utf-8"))
# 4. 关闭连接
tcp.close()
按顺序分别启动服务端模块(接收数据) 和 客户端模块(发送数据)
运行结果
核心: 利用While循环让程序持续挂起, 并且设置一个点让循环关闭
实操: 实现基于TCP的持续通信
服务端代码
from socket import *
"""TCP服务器端接收数据"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 绑定端口号
tcp.bind(("127.0.0.1", 8979))
# 3. 监听端口号
tcp.listen(5)
# 4. 等待socket连接
"""
client 表示这个新的客户端
client_info 表示这个新的客户端的ip以及port
"""
client, client_info = tcp.accept()
print("一个客户端建立连接成功! ")
while True:
# 5. 读取数据
recv_data = client.recv(1024).decode("utf-8")
print(f"收到信息:{recv_data},来自:{client_info}")
if recv_data == "88":
break
# 获取控制台信息
msg = input('>')
# tcp连接成功后, 发送或接收的使用的不是一个对象!!!
client.send(msg.encode("utf-8"))
# 6. 关闭连接
client.close()
tcp.close()
客户端代码
from socket import *
"""
双向通信Socket之客户端
将控制台输入的信息发送给服务器端
读取服务器端的数据,将内容输出到控制台
"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 连接端口号
tcp.connect(("127.0.0.1", 8979))
# 3. 发送和接收数据
while True:
# 获取控制台信息
msg = input('>')
tcp.send(msg.encode("utf-8"))
if msg == "88":
break
# 接收服务器端数据
rsp_data = tcp.recv(1024).decode("utf-8")
print("服务端回复:", rsp_data)
# 4. 关闭连接
tcp.close()
运行结果
核心: 结合多线程的实现方式, 然后利用持续通信的代码进行改造, 将接收和发送的方法通过类包装的形式来绑定线程并启动
实操: 基于多线程 + TCP实现socket编程
服务端代码
from socket import *
from threading import Thread
"""TCP服务器端接收数据"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 绑定端口号
tcp.bind(("127.0.0.1", 8979))
# 3. 监听端口号
tcp.listen(5)
# 4. 等待socket连接
"""
client 表示这个新的客户端
client_info 表示这个新的客户端的ip以及port
"""
client, client_info = tcp.accept()
print("一个客户端建立连接成功! ")
def recv_data():
while True:
# 5. 读取数据
recv_data = client.recv(1024).decode("utf-8")
print(f"服务端收到信息:{recv_data},来自:{client_info}")
if recv_data == "88":
break
def send_data():
while True:
# 获取控制台信息
msg = input('>')
# tcp连接成功后, 发送或接收的使用的不是一个对象!!!
client.send(msg.encode("utf-8"))
if msg == "88":
print("服务端结束聊天")
break
if __name__ == "__main__":
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
# 6. 关闭连接
client.close()
tcp.close()
客户端代码
from socket import *
from threading import Thread
"""
双向通信Socket之客户端
将控制台输入的信息发送给服务器端
读取服务器端的数据,将内容输出到控制台
"""
# 1. 创建Socket对象(TCP)
tcp = socket(AF_INET, SOCK_STREAM)
# 2. 连接端口号
tcp.connect(("127.0.0.1", 8979))
# 3. 发送和接收数据
def send_data():
while True:
# 获取控制台信息
msg = input('>')
tcp.send(msg.encode("utf-8"))
if msg == "88":
print("客户端结束聊天")
break
def recv_data():
while True:
# 接收服务器端数据
rsp_data = tcp.recv(1024).decode("utf-8")
print("服务端回复:", rsp_data)
if rsp_data == "88":
break
if __name__ == "__main__":
t1 = Thread(target=send_data)
t2 = Thread(target=recv_data)
t1.start()
t2.start()
t1.join()
t2.join()
# 4. 关闭连接
tcp.close()
运行结果