网络编程指的是:
在程序中实现两台计算机之间的通信。 Python提供了大量的内置模块和第三方模块用于支持各种网络访问,而且Python
语言在网络通信方面的优点特别突出,远远领先其他语言。
目录
一,网络编程基本概念
1,什么是IP地址?
IP是Internet Protocol Address
,即
”
互联网协议地址
”
。互联网之间的通信相当于快递收发,需要知道每个电脑的详细地址才能实现数据的准确收发。
IP地址用来标识网络中的一个通信实体的地址。通信实体可以是计算机、 路由器等
。
IP地址实际上是一个
32
位整数(称为
IPv4
),以字符串表示的
IP
地址如
192.168.0.1
实际上是把
32
位整数按
8
位分组后的数字表示,目的是便于阅读。
2,IP地址的分类
2.1 公有地址
公有地址(Public address)
由
Inter NIC
(
Internet Network Information Center互联网信息中心)负责。这些
IP
地址分配给注册并向Inter NIC
提出申请的组织机构。通过它直接访问互联网。
2.2 私有地址
私有地址(Private address
)属于非注册地址,专门为组织机构内部使用。
3,本地服务器IP地址
127.0.0.1本机地址。192.168.0.0–192.168.255.255为私有地址,属于非注册地址,专门为组织机构内部使用。
4,Windows下常用的ip命令
windows下,我们可以通过命令 ipconfig 获取网卡信息。(Linux 和Mac,是 ifconfig ),通过 ping 查看网络连接:
5,什么是子网?子网掩码?
6,什么是网关?
7,什么是交换机?
8,什么是路由器?
路由器具备WAN口和多个LAN口。它实际上是
网关和交换机的结合体。
wan口与宽带公司机房相连,LAN口与局域网设备相连。
9,什么是路由?
10,子网下的设备如何访问互联网?
通过网关设备里面的NAT技术,即网络地址转换技术。
11,什么是端口?
同一台设备下有很多的应用程序,但是网卡只有一个,数据通过网卡获得和发送,如何确定接收的数据到底是哪一个程序的呢?发给QQ的数据不可能被微信接收。利用端口可以解决这个问题。
11.1 端口分配
端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。 端口的表示是一个16位的二进制整数,对应十进制的
0-65535
。 操作系统中一共提供了0~65535
可用端口范围。 按端口号分类:
11.2 公认端口(
Well Known Ports
):
从
0
到
1023
,它们紧密绑定(binding
)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80
端口实际上总是
HTTP
通讯。
11.3
注册端口(
Registered Ports
):
从1024
到
65535
。它们松散地绑 定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同用于许多其它目的。例如:许多系统处理动态端口从1024
左右开始。
二,网络通信协议
1,一张神图
2,OSI网络协议七层模型
OSI模型制定的七层标准模型,分别是:应用层, 表示层,会话层,传输层,网络层,数据链路层,物理层。
3,TCP/IP协议模型
虽然国际标准化组织制定了这样一个网络通信协议的模型,但是实际上互联网通讯使用最多的网络通信协议是TCP/IP
网络通信协议。 TCP/IP 是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,网络接口层(物理+
数据链路层)。
与OSI模型的区别如下:
4,TCP和UDP的区别?
TCP 和
UDP
的优缺点无法简单地、绝对地去做比较:
TCP
用于在传输层有必要实现可靠传输的情况;UDP
主要用于那些对高速传输 和实时性有较高要求的通信或广播通信。TCP
和
UDP
应该根据应用的目的按需使用。主要在于他们传输数据的形式不同。
4.1 TCP
TCP(
Transmission Control Protocol
,传输控制协议)。
TCP
方 式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。
4.2 UDP
UDP(User Data Protocol
,用户数据报协议),UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP
传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制; 在接收端, UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
UDP方式就类似于发送短信
,使用这种方式进行网络通讯时,不需 要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户 端无法获得。UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络 条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网 络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP
而不是
TCP
4.3 TCP和UDP区别
这两种传输方式都在实际的网络编程中使用,重要的数据一般使用 TCP方式进行数据传输,而大量的非核心数据则可以通过
UDP
方式 进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。 由于TCP
需要建立专用的虚拟连接以及确认传输是否正确,所以使 用TCP
方式的速度稍微慢一些,而且传输时产生的数据量要比
UDP稍微大一些。
5,TCP建立链接的三次握手
TCP是面向连接的协议,也就是说,在收发数据前,必须和对方建 立可靠的连接。 一个TCP
连接必须要经过三次
“
对话
”
才能建立起 来,其中的过程非常复杂,只简单的描述下这三次对话的简单过程:
1)主机
A
向主机
B
发出连接请求:
“
我想给你发数据,可以吗?
”
,这是第一次对话;
2)主机
B
向主机
A
发送同意连接和要求同步 (同步就是两台主机一个在发送,一个在接收,协调工作)的数据包 :“
可以,你什么时候发?”
,这是第二次对话;
3)主机
A
再发出一个数据包确认主机
B
的要求同步:
“
我现在就发, 你接着吧!”
, 这是第三次握手。
三次“
对话
”
的目的是使数据包的发送和接收同步, 经过三次
“
对话
” 之后,主机A
才向主机
B
正式发送数据。
三次握手建立TCP连接时的状态。
第一步,客户端发送一个包含SYN
即同步(
Synchronize
)标志的
TCP
报文,
SYN
同步报文会指明客户端使用的端口以及TCP
连接的初始序号。
第二步,服务器在收到客户端的SYN
报文后,将返回一个
SYN+ACK
的报文,表示客户端的请求被接受,同时TCP
序号被加一,
ACK
即确认(
Acknowledgement
)
第三步,客户端也返回一个确认报文ACK
给服务器端,同样
TCP
序列号被加一,到此一个
TCP
连接完成。然后才开始通信的第二步:数据处理。
6,
TCP
断开连接的四次挥手
第一次: 当主机A完成数据传输后
,
将控制位
FIN
置
1
,提出停止
TCP 连接的请求 ;
第二次: 主机B收到FIN
后对其作出响应,确认这一方向上的
TCP
连接将关闭,
将
ACK
置
1
;
第三次: 由B 端再提出反方向的关闭请求,
将
FIN
置
1
;
第四次: 主机A对主机B
的请求进行确认,将
ACK
置
1
,双方向的关闭结束
。
由TCP的三次握手和四次断开可以看出,TCP
使用面向连接的通信方 式, 大大提高了数据通信的可靠性,使发送数据端和接收端在数据 正式传输前就有了交互, 为数据正式传输打下了可靠的基础。
7,
数据包与处理流程
7.1 什么是数据包?
通信传输中的数据单位,一般也称“
数据包
”
。在数据包中包括: 包、帧、数据包、段、消息。
网络中传输的数据包由两部分组成:一部分是协议所要用到的首 部,另一部分是上一层传过来的数据。首部的结构由协议的具体规 范详细定义。在数据包的首部,明确标明了协议应该如何读取数 据。反过来说,看到首部,也就能够了解该协议必要的信息以及所 要处理的数据。包首部就像协议的脸。
7.2
数据包处理流程
详细流程如下图。
三,socket套接字编程
1,什么是套接字编程?
TCP协议和
UDP
协议是传输层的两种协议。
Socket
是传输层供给应用层的编程接口,所以Socket
编程就分为
TCP
编程和
UDP
编程两类。
Socket编程封装了常见的TCP
、
UDP
操作,可以实现非常方便的网络编程。
2,socket()函数介绍
在Python
语言标准库中,通过使用
socket
模块提供的
socket
对象,可以在计算机网络中建立可以互相通信的服务器与客户端。在服务器端需要建立一个socket
对象,并等待客户端的连接。客户端使用socket对象与服务器端进行连接,一旦连接成功,客户端和服务器端就可以进行通信了。
注意:
我们可以看出socket
通讯中,发送和接收数据,都 是通过操作系统控制网卡来进行。因此,我们在使用之后,必须关闭socket
。
socket语法格式如下:
socket.socket([family[, type[, proto]]])
;
family的参数值有:
AF_UNIX
或者
AF_INET
;
AF 表示
ADDRESS FAMILY
地址族,AF_INET(又称
PF_INET
)是
IPv4
网络协议的套接字类型;而AF_UNIX 则是
Unix
系统本地通信。
type的参数值有:
type
:
套接字类型可以根据是面向连接的还是非连接分为
SOCK_STREAM
或
SOCK_DGRAM
;
protocol
:
一般不填,默认为
0
。
Socket主要分为面向连接的Socket和无连接的
Socket
。 无连接Socket
的主要协议是用户数据报协议,也就是常说的
UDP
, UDP Socket的名字是
SOCK_DGRAM
。创建套接字
UDP/IP
套接字,可以 调用
socket.socket()
。示例代码如下:
udpSocket=socket.socket(AF_INET,SOCK_DGRAM)
3,socket对象的方法一览
4,UDP编程实例
4.1 单工最简案例
UDP协议时,不需要建立连接,只需要知道对方的
IP
地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和
TCP
比,速度快,对于不要 求可靠到达的数据,就可以使用UDP
协议。 创建Socket
时,
SOCK_DGRAM
指定了这个
Socket
的类型是
UDP
。绑定 端口和TCP
一样,但是不需要调用
listen()
方法,而是直接接收来自任何客户端的数据。
recvfrom()
方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用
sendto()
就可以把数据用
UDP
发给客户端。
udp服务端:
from socket import *
# 最简化的UDP服务端代码
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字对象
s.bind(("127.0.0.1", 8888)) # 绑定端口,ip可以不写
print("等待接收数据!")
recv_data = s.recvfrom(1024) # 1024表示本次接收的最大字节数
print(f"收到远程信息:{recv_data[0].decode('gbk')},from {recv_data[1]}")
s.close()
udp客户端:
from socket import *
# 最简化的UDP客户端发送消息代码
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字
server_addr = ("127.0.0.1", 8888)
data = input("请输入:")
s.sendto(data.encode("gbk"), server_addr)
s.close()
4.2 单工持续通信
udp服务端:
from socket import *
# 持续通信
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字
s.bind(("127.0.0.1", 8888)) # 绑定端口,ip可以不写
print("等待接收数据!")
while True:
recv_data = s.recvfrom(1024) # 1024表示本次接收的最大字节数,recvfrom一直监听知道由数据被接受
recv_content = recv_data[0].decode('gbk')
print(f"收到远程信息:{recv_content},from {recv_data[1]}")
if recv_content == "exit":
print("结束聊天!")
break
s.close()
udp客户端:
from socket import *
# UDP客户端持续发送消息代码
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字
addr = ("127.0.0.1", 8888)
while True:
data = input("请输入:")
s.sendto(data.encode("gbk"), addr)
if data == "exit":
print("结束聊天!")
break
s.close()
4.3 双工持续通信
使用本地主机,开启两个接收和发送两个线程,实现双工通信。
udp服务端:
from socket import *
from threading import Thread
def recv_data():
while True:
recv_data = s.recvfrom(1024) # 1024表示本次接收的最大字节数
recv_content = recv_data[0].decode('gbk')
print(f"收到远程信息:{recv_content},from {recv_data[1]}")
if recv_content == "exit":
print("结束聊天!")
break
def send_data():
addr = ("127.0.0.1", 9999)
while True:
data = input("请输入:")
s.sendto(data.encode("gbk"), addr)
if data == "exit":
print("结束聊天!")
break
if __name__ == '__main__':
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字
s.bind(("127.0.0.1", 8888)) # 绑定端口,ip可以不写
# 创建两个线程
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
udp客户端:
from socket import *
from threading import Thread
def recv_data():
while True:
recv_data = s.recvfrom(1024) # 1024表示本次接收的最大字节数
recv_content = recv_data[0].decode('gbk')
print(f"收到远程信息:{recv_content},from {recv_data[1]}")
if recv_content == "exit":
print("结束聊天!")
break
def send_data():
addr = ("127.0.0.1", 8888)
while True:
data = input("请输入:")
s.sendto(data.encode("gbk"), addr)
if data == "exit":
print("结束聊天!")
break
if __name__ == '__main__':
s = socket(AF_INET, SOCK_DGRAM) # 创建UDP类型的套接字
s.bind(("127.0.0.1", 9999)) # 绑定端口,ip可以不写
# 创建两个线程
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
5,TCP/IP编程实例
5.1 TCP/IP编程简介
面向连接的Socket
使用的协议是
TCP
协议。
TCP
的
Socket
名称是
SOCK_STREAM
。创建套接字
TCP
套接字,可以调用
socket.socket()
。示例代码如下:
tcpSocket=socket.socket(AF_INET,SOCK_STREAM)
5.2 TCP
编程的实现过程
在Python
语言中创建
Socket
服务端程序,需要使用
socket
模块中的socket类。创建
Socket
服务器程序的步骤如下:
(1) 创建
Socket
对象。
(2) 绑定端口号。
(3) 监听端口号。
(4) 等待客户端Socket
的连接。
(5) 读取客户端发送过来的数据。
(6) 向客户端发送数据。
(7) 关闭客户端Socket
连接。
(8) 关闭服务端Socket
连接。
5.3 TCP单工通信服务端
from socket import *
server_socket = socket(AF_INET,SOCK_STREAM) #建立TCP套接字
server_socket.bind(("127.0.0.1",8899)) #本机监听8899端口
server_socket.listen(5)
print("等待接收连接!")
client_socket,client_info = server_socket.accept()
recv_data = client_socket.recv(1024) #最大接收1024字节
print(f"收到信息:{recv_data.decode('gbk')},来自:{client_info}")
client_socket.close()
server_socket.close()
5.4 TCP单工通信客户端
from socket import *
client_socket = socket(AF_INET,SOCK_STREAM)
client_socket.connect(("127.0.0.1",8899))
client_socket.send("hello".encode("gbk"))
client_socket.close()
#注意:
# 1. tcp客户端已经链接好了服务器,所以在以后的数据发送中,不需要填写对方的ip和port----->打电话
# 2. udp在发送数据的时候,因为没有之前的链接,所以需要在每次的发送中,都要填写接收方的ip和port--->写信
5.5 TCP单工持续通信服务端
from socket import *
server_socket = socket(AF_INET,SOCK_STREAM) #建立TCP套接字
server_socket.bind(("127.0.0.1",8899)) #本机监听8899端口
server_socket.listen(5)
print("等待接收连接!")
client_socket,client_info = server_socket.accept()
print("一个客户端建立连接成功!")
while True:
recv_data = client_socket.recv(1024) #最大接收1024字节
recv_content = recv_data.decode('gbk')
print(f"客户端说:{recv_content},来自:{client_info}")
if recv_content == "end":
break
msg = input(">")
client_socket.send(msg.encode("gbk"))
client_socket.close()
server_socket.close()
5.6 TCP单工持续通信客户端
from socket import *
client_socket = socket(AF_INET,SOCK_STREAM)
client_socket.connect(("127.0.0.1",8899))
while True:
#给服务端发消息
msg = input(">")
client_socket.send(msg.encode("gbk"))
if msg =="end":
break
#接收服务器端数据
recv_data = client_socket.recv(1024) #最大接收1024字节
print(f"服务器端说:{recv_data.decode('gbk')}")
client_socket.close()
5.7 TCP双工持续通信服务端
from socket import *
from threading import Thread
def recv_data():
while True:
recv_data = client_socket.recv(1024) # 最大接收1024字节
recv_content = recv_data.decode("gbk")
print(f"客户端说:{recv_content},来自:{client_info}")
if recv_content == "end":
print("结束接收消息!")
break
def send_data():
while True:
msg = input(">")
client_socket.send(msg.encode("gbk"))
if msg == "end":
print("结束发送消息!")
break
if __name__ == '__main__':
server_socket = socket(AF_INET, SOCK_STREAM) # 建立TCP套接字
server_socket.bind(("127.0.0.1", 8899)) # 本机监听8899端口
server_socket.listen(5)
print("等待接收连接!")
client_socket, client_info = server_socket.accept()
print("一个客户端建立连接成功!")
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
client_socket.close()
server_socket.close()
5.8 TCP双工持续通信客户端
from socket import *
from threading import Thread
def recv_data():
while True:
# 接收服务器端数据
recv_data = client_socket.recv(1024) # 最大接收1024字节
recv_content = recv_data.decode('gbk')
print(f"服务器端说:{recv_content}")
if recv_content == "end":
print("结束接收消息")
break
def send_data():
while True:
# 给服务端发消息
msg = input(">")
client_socket.send(msg.encode("gbk"))
if msg == "end":
break
if __name__ == '__main__':
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(("127.0.0.1", 8899))
t1 = Thread(target=recv_data)
t2 = Thread(target=send_data)
t1.start()
t2.start()
t1.join()
t2.join()
client_socket.close()