网络通信
下载文件就是 在本地创建一个与要下载文件同名的文件然后服务器发送要下载文件的字节客户端就写入到同名文件中
IP地址的分类
A类IP段 1.0.0.0
到 126.255.255.255
(0段到127段不使用)
B类IP段 128.0.0.0
到 191.255.255.255
掩码 255.0.0.0
C类IP段 192.0.0.0
到 223.255.255.255
掩码 255.255.0.0
D类IP段 224.0.0.0
到 239.255.255.255
(用于广播)
D类的IP地址不标识网络 地址覆盖范围为224.0.0.0
~239.255.255.255
端口
不同机器发送数据通过IP区分机器在网络上的位置 通过port
端口分区哪个进程接收数据
知名端口号0-1023
一般程序要使用知名端口号 需要root
权限
注册端口号1024-49151
动态端口号49152-65535
程序如果在发送时没有指定端口 程序会随机指定一个端口
单工(广播)
要么只能发送数据要么只能接收数据的工作模式
半双工(对讲机)
同一时刻只能发送数据或接收数据的工作模式
全双工(电话)
同一时刻既可以发送也可以接收数据的工作模式
socket模块
socket
套接口具有全双工
的特点 套接口发送的数据必须是bytes
数据类型
在已经创建套接口 但没有开始读取数据时 接收来的数据系统会暂存 调用接收功能的时候再从系统读取
socket.AF_INET
指定通过ipv4
通信协议
UDP用户数据报协议
每条信息都要写上IP
和端口
socket.SOCK_DGRAM
数据报datagram
套接口类型
一个datagram
可能被压缩成多个包packets
分组在发送后可能无序
的到达接收端
分组可能丢失且不会采取任何措施 接收端也不知道分组丢失
数据报分组有尺寸大小的限制 超出限制在某些路由节点上可能就无法传出
分组是在不建立连接的情况下被发送到远程进程的
UDP收发数据
import socket
def main():
# 创建一个udp套接口
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定一个本地信息
upd.bind(("source_IP", source_port))
# 输入要发送的内容
string = input("输入要发送的数据:")
# 发送一个字符串的utf8编码的bytes对象 到ip:port
udp.sendto(string.encode("utf-8"), ("dest_IP", dest_port))
# 接收一个元组数据并设置单次接收的大小
data = udp.recv(1024)
# 打印接收到的元组并解码为utf8编码字符串
print(data[0].decode("utf-8"))
# 关闭套接口
udp.close()
udp.bind(("", port))
绑定一个接收数据的地址
和端口
ip
地址输入空字符串
使用默认网卡信息
udp.sendto(b"string".encode("gbk"), ("ipaddr", port))
b
将字符串转换为bytes
类型或重新编码为gbk
编码的bytes
类型进行发送
udp.recvfrom(1024)
接收发送方的数据返回一个元组("bytes", ("ipaddr", port))
并设置单次接收数据的大小
TCP传输控制协议
socket.SOCK_STREAM
流套接口类型
不保留任何消息的边界 接收方一次收到发送方多次发送的数据但是不知道是分几次发送的
接收的数据字节与发送的顺序完全一致(意味着通信前必须建立连接
)
接收的数据被无错的接收 如果有错误会尝试修复 如果无法修复会报错
TCP客户端
import socket
def main():
# 创建客户端tcp套接口
tcp_client = socket.socket(socket.AF_INAT, socket.STREAM)
# 绑定一个客户端使用的端口
tcp_client.bind(("", client_port))
# 与服务器建立连接
tcp_client.connect(("server_IP", server_port))
# 输入要发送的内容
string = input("输入要发送的内容")
# 发送数据
tcp_client.send(string.encode("utf-8"))
# 接收数据
data = tcp_client.recv(1024)
# 打印接收的数据把bytes解码为UTF8编码格式的字符串
print(data.decode("utf-8"))
# 关闭套接口
tcp_client.close()
tcp_client.bind(("", client_port))
绑定客户端的通信IP
和端口
否则端口随机
端口只能绑定一次
tcp_client.connect(("server_IP", server_port))
建立与服务端的TCP连接
tcp_client.send(b"string".encode("utf8"))
字符串加上b
转换为bytes
类型或重新编码为utf8
编码的bytes
类型
tcp_client.recv(1024)
接收对方发送的数据返回一个bytes
类型对象 并设置单次接收数据的大小
如果接收到的数据为空
则表示对方调用了close()
如果不为空则表示对方调用的send()
TCP服务器
import socket
def main():
# 创建服务端tcp套接口
tcp_server = socket.socket(socket.AF_INET, socket.STREAM)
# 服务器必须绑定一个端口 否则客户端不知道连接哪里
tcp_server.bind(("", server_port))
# 通过listen使套接口变为被动监听服务器进程
tcp_server.listen(128)
# 使用accept接收监听到的数据并返回新的套接口对象和客户端信息
server_client, client_addr = tcp.server.accept()
# 通过新套接口收发数据
string = "连接成功"
server_client.send(string.encode("utf8"))
# 通过新套接口接收数据并设置一次接收的数据大小
data = server_client.recv(1024)
if data: # 如果收到的数据为空则对方调用了close()
# 打印接收的数据
print("来自%s:%s" % (client_addr, data.decode("utf8")))
else:
# 关闭套接口
server_client.close()
# 关闭服务器套接口
tcp_server.close()
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
重复使用已经占用的端口
tcp_server.bind(("", server_port))
必须绑定服务端ip信息
否则客户机不知道发送给哪个端口
tcp_server.listen(128)
使套接口变为被动连接并监听
其他进程请求的服务器进程 listen
在TCP网络编程中使进程变为服务器
并把相应的套接口变为被动连接
tcp_server.accept()
返回一个元组(socket()对象, ("client_ip", client_port))
serv_client_socket, client_ip_port = tcp_server.accept()
固定包含两个信息 新的服务端套接口和客户端信息 使用拆包来分别接收两个对象
serv_client_socket.send(string.encode("utf8"))
通过套接口发送数据给已连接的客户端
serv_client_socket.recv(1024)
通过新的套接口继续与客户端通信 返回客户端发送的data
如果此时对方断开
连接会返回空数据
堵塞解堵塞
服务器的accept
会等待客户端connect
如果客户端不连接 会堵塞一直等待客户端的连接
recv(1024)
等待数据时会堵塞 当对方send()
或调用close()
会解堵塞 但是close
会收到空数据
server.settblocking(False)
设置套接口为非堵塞模式
TCP的三次握手与四次挥手
第一次握手
客户端在connect服务器时会发送一个syn同步的seq序列号给服务器表示已经准备就绪
第二次握手
服务器收到客户端syn同步的seq序列号后返回一个把seq+1的ack确认信息给客户端表示已经收到
同时也会发送一个syn同步的seq序列号表示服务器也准备就绪
第三次握手
客户端收到服务器返回的ack表示服务器已经知道自己准备就绪
再次发送包含第一次握手的seq+1的ack确认信息和返回服务器的syn确认的seq+1的ack给服务器表示已经收到服务器准备就绪的信息
第一次挥手
客户端关闭与服务器的数据传输底层会首先发送fin终止seq序列给服务器表示自己关闭了数据发送
第二次挥手
服务器收到这个fin后会首先给客户端返回一个ack确认的seq+1信息并通知程序关闭recv接收功能此时recv会解堵塞并收到空字符串 判断收到的信息是否为空来让程序判断客户端是否已经关闭与服务器的连接
第三次挥手
服务器可能也会关闭与客户端的数据连接但是也可能不关闭所以第三次挥手与第二次挥手不能合并否则如果不关闭那么客户端会一直处于等待服务器ack
服务器关闭与客户端的连接发送一个fin终止的seq序列和第二次挥手的ack确认的seq+1来通知客户端自己已经关闭与客户端的连接
第四次挥手
客户端收到服务器发送的fin终止seq序列并+1返回一个ack给服务器通知服务器自己已经收到
此时服务器应该通知客户端收到这个ack但是如果这样做会一直死循环 所以此时客户端不等待服务器的ack而是等待两倍的网络传输时间即2*MSL时间一般最大时间为2到5分钟
两种假设
如果服务器没有收到第四次挥手时客户端ack信息那么在一个time out后服务器会再次重复第三次挥手 此时客户端处于2MSL时间内直到收到后再发送一次ack给服务器
如果服务器收到了客户端第四次挥手的ack信息那么服务器顺利关闭数据连接但是客户端还是会等待2MSL时间 因为客户端并不知道服务器是否收到
所以一般客户端不绑定端口否则在2MSL的时间内端口不能再重复利用
所以一般服务器不主动关闭通信否则最后服务器会等待2MSL的时间
HTTP协议
HTTP
协议是Hyper Text Transfer Protocol
(超文本传输协议)的缩写
Http协议中的Header与Body
Header
的每行最后要加\r\n
Header
与Body
之间要用\r\n
隔开
Body
后无需加\r\n
ACSII
码中
'\n'
10 换行
'\r'
13 回车
也可以表示为'\x0a'
和'\x0d'
(16进制)
客户端的request
信息至少要包含GET / HTTP/1.1
服务器发送的response
HTTP
开始部分为header
开始部分为
body
HTTP/1.1 200 OK\r\n
Content-Encoding: gzip\r\n
Content-Type: text/xml\r\n
Content-Length: 399\r\n
Connection: keep-alive\r\n
X-Varnish-Cache: HIT\r\n
X-Varnish-Cache-Hits: 1241\r\n
\r\n
.....
一般客户端的GET
请求没有body
一般客户端的post
请求也有body
长连接短连接
HTTP1.0
协议是短连接
每请求一个数据都要经过3次握手与4次挥手
HTTP1.1
默认是长连接
也可以短连接
只要经过三次握手
确认连接后浏览器就可以一直请求想要的数据 但服务器必须告诉浏览器每次发送的数据长度
否则浏览器会一直等待或者意外断开才会结束等待
在response
中设置Content-Length
告诉浏览器发送数据的长度
浏览器发现所有请求的数据都已经收到后会主动断开连接