目录
一、网络编程基础
二、UDP协议
三、TCP协议
一、网络编程基础
数据编码与解码
- str -> bytes:encode编码,发送信息的时候用encode编码
- bytes -> str:decode解码,打印接收的信息用decode解码
test = '你好世界'
en_code1 = test.encode('utf-8')
en_code2 = test.encode('gbk')
en_code3 = test.encode('utf-8-sig')
print(en_code1) # b'\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c'
print(en_code2) # b'\xc4\xe3\xba\xc3\xca\xc0\xbd\xe7'
print(en_code1.decode('utf-8')) # 你好世界
print(en_code1.decode('utf-8-sig')) # 你好世界
print(en_code3.decode('utf-8')) # 你好世界
# 'utf-8'编码可用'utf-8-sig'互相编码和解码
print(en_code2.decode('gbk')) # 你好世界
print(en_code2.decode('utf-8')) # Error,编码和解码的格式必须相对应
socket
- socket套接字,是进程间通信的工具,也能不同主机间的网络通信
- 首先通过ip地址找到网络中对应的主机,然后通过传输协议和端口号来确定这个进程,使用socket完成数据传输
二、UDP协议
面向无连接型:无需确认接收端是否存在,发送端都可发送数据
特点:无连接,资源开销小,传输速度快,每个数据包最大是64k,适用于广播应用
缺陷:传输数据不可靠,容易丢包;没有流量控制,需要接受方及时接受数据,否则会写满缓冲区
- 首先保证UDP服务端正常启动,进入到recvfrom()模式,阻塞等待客户端发送数据
- 开启UDP客户端,校准IP地址,通过sendto()模块进行数据发送
- 当服务端接收到客户端发送来的数据,进行数据处理,并将应答数据发送给客户端
- 客户端接收到应答数据,可进行数据处理或重复发送数据,也可退出进程
server服务端
from socket import *
# 1. 创建套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 2. 绑定本地的相关信息,如果一个网络程序不绑定,系统对随机分配
# ip地址和端口号,如果不表明ip,则表示本机的任何一个ip
# 如果不指明端口号,则每次启动都是随机生成端口号
local_addr = ('', 12345)
udp_socket.bind(local_addr)
while True:
# 3. 阻塞等待接收客户端发送的数据
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
# 4. 显示接收的数据并解码
print(recv_data)
print(recv_data[0].decode('utf-8'))
# 5. 发送应答数据
addr = recv_data[1]
data = '信息已收到'
udp_socket.sendto(data.encode('utf-8'), addr)
client客户端
import socket
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 准备服务端地址和端口号
# 127.0.0.1代表自身ip地址,可向自身发送信息,也可指定ip地址发送信息
# 端口号随便填一个未被占用的端口即可
# Linux环境有65535个端口号,前1024个端口号是系统端口号,系统端口号不能直接使用
addr = ('127.0.0.1', 12345)
while True:
# 3. 从键盘获取数据
data = input('请输入数据:')
# 4. 发送数据到指定进程中
udp_socket.sendto(data.encode('utf-8'), addr)
# 5. 阻塞等待服务端返回的数据
recv_data = udp_socket.recvfrom(1024)
# 6. 处理应答数据
print(recv_data)
print(recv_data[0].decode('utf-8'))
三、TCP协议
面向有连接型:双方必须先建立连接才能进行数据传输
特点:
- 双方都必须为该连接分配系统内核资源
- 完成数据传输后,双方必须断开连接,释放系统资源
- TCP连接是一对一的,不适用于广播应用
- TCP提供可靠的数据传输,无差别、不丢失、不重复,且按序到达
- 相比于UDP,TCP数据传输速度慢,对系统资源要求较高
- TCP适用于发送大量数据,UDP适用于发送少量数据
- TCP有流量控制,UDP无流量控制
建立连接:三次握手
SYN(连接请求)、ACK(确认)、FIN(关闭连接)、seq(报文信号)、ack(确认信号)
- 第一次握手:client标志位SYN置1,随机产生一个报文信号seq=J,并将该数据包发送给server,client进入SYN_SENT状态,等待server确认
- 第二次握手:server收到数据包后由标志位SYM=1知道client请求建立连接,server将SYN和ACK都置1,ack=J+1,+1是加密,随机产生一个值seq=K,并将该数据包发送给client以确认连接请求,server进入SYN_RECV状态
- 第三次握手:client收到确认信号,检查ack是否为J+1(解密),如果正确则将标志位ACK置1,ack=K+1,并将该数据包发送给server。server检查ack是否为K+1,如果正确则建立连接成功,client和server同时进入ESTAVLISHED状态,完成三次握手,随后client和server之间可以进行数据传输
断开连接:四次挥手
- 第一次挥手:client发送一个FIN,用来关闭client到server的数据传输,客户端进入FIN_WAIT_1状态
- 第二次挥手:sercer收到FIN后,发送一个ACK给client,确认信号为收到序号+1,表示还有剩余数据为传送完,服务端进入CLOSE_WAIT状态
- 第三次挥手:server发送一个FIN,用来关闭server到client的数据传输,服务端进入LAST_ACK状态
- 第四次挥手:client收到FIN后,客户端进入TIME_WAIT状态,接着发送一个ACK给server,确认信号为收到信号+1,服务端进入进入CLOSED状态,完成四次挥手
四次挥手详细说明:
- client发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中FIN字段置为1,表示需要断开TCP连接。(FIN=1,seq=M)
- server回复客户端发送的TCP断开请求报文,产生ACK字段号,ACK字段数值是对client发送的seq序列号+1处理,以便client收到信息时,知道自己的TCP断开请求已经得到验证。(ACK=M+1)
- server在回复完client的TCP断开请求后,不会马上进行TCP连接的断开,server会先确保断开前所有传输到缓冲区的数据是否已经接收完毕,一旦确认数据接收完毕,就会将回复报文的FIN置1,并随机产生seq=N序列号。(FIN=1,seq=N)
- client收到server的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,验证ACK字段是否为M+1后,对server的seq=N序列号做ACK=N+1处理,最后将ACK=N+1发送给server,完成服务端请求的验证服务。(ACK=N+1)
11种状态名词解析
LISTEN:等待从任何远端TCP 和端口的连接请求。
SYN_SENT:发送完一个连接请求后等待一个匹配的连接请求。
SYN_RECEIVED:发送连接请求并且接收到匹配的连接请求以后等待连接请求确认。
ESTABLISHED:表示一个打开的连接,接收到的数据可以被投递给用户。连接的数据传输阶段的正常状态。
FIN_WAIT_1:等待远端TCP 的连接终止请求,或者等待之前发送的连接终止请求的确认。
FIN_WAIT_2:等待远端TCP 的连接终止请求。
CLOSE_WAIT:等待本地用户的连接终止请求。
CLOSING:等待远端TCP 的连接终止请求确认。
LAST_ACK:等待先前发送给远端TCP 的连接终止请求的确认(包括它字节的连接终止请求的确认)
TIME_WAIT:等待足够的时间过去以确保远端TCP 接收到它的连接终止请求的确认。
TIME_WAIT 两个存在的理由:
1.可靠的实现tcp全双工连接的终止;
2.允许老的重复分节在网络中消逝。
CLOSED:不在连接状态(这是为方便描述假想的状态,实际不存在)
server服务端
from socket import *
# 1. 创建TCP套接字
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 2. 设置socket选项,程序退出后,端口会自动释放
tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)
# 3. 本地信息
addr = ('', 12345)
# 4. 绑定地址
tcp_server_socket.bind(addr)
# 5. 设置监听,使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动
tcp_server_socket.listen(60) # 参数代表等待连接的时间最多60s
# 6. 如果有新的客户端来连接服务,就产生一个新的套接字,专门为这个客户端服务
# client_socket用来为这个客户端服务
# 原来的tcp_server_socket就继续等待其他新用户的连接
client_socket, client_addr = tcp_server_socket.accept()
# 7. 阻塞等待客户端发送的信息
recv_data = client_socket.recv(1024)
print('收到信息:', recv_data.decode('utf-8'))
# 8. 发送应答信息
string = '已收到信息'
client_socket.send(string.encode('utf-8'))
# 9. 关闭套接字
client_socket.close()
client客户端
import socket
# 1. 创建TCP套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 目标服务端地址
ip = input('请输入服务端ip:')
port = int(input('请输入服务端port:'))
addr = (ip, port)
# 3. 连接服务器
tcp_client_socket.connect(addr)
# 4. 输入发送的数据
data = input('请输入数据:')
# 5. 编码传输数据
tcp_client_socket.send(data.encode('utf-8'))
# 6. 接收服务端的应答数据
recv_data = tcp_client_socket.recv(1024)
print('应答数据:', recv_data.decode('utf-8'))
# 7. 关闭套接字
tcp_client_socket.close()