网络编程(浅层面)
目录
TCP/IP(协议族)
协议:为了解决沟通障碍,规定了国际通用语言,这是一个规定,就是协议。
相对的,计算机遵循的网络通信协议叫做TCP/IP协议。
网络通信协议分层模型
TCP/IP四层:数据链路层、网络层、传输层、应用层
TCP/IP五层:物理层、数据链路层、网络层、传输层、应用层
OSI七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
端口
如果需要收发网络数据,那么就需要端口。
端口可以有65536(2的16次方)个,操作系统为了统一管理,进行了编号,这个编号就是端口号。
端口是通过端口号来标记的,端口号只有整数,范围从0-65535
知名端口
知名端口是众所周知的端口号,范围从0-1023
可以理解为一些常用的功能的号码:如电话号:10086,114,110等
动态端口
一般不固定分配某种服务,范围从1024-65535
动态分配只当一个系统进程应用进程需要网络通信时向主机申请一个端口
主机从可用的端口号分配一个可供他使用,当这个进程关闭,同时也就释放了所占有的端口号。
IP地址
IP地址:用来在网络中标记一台电脑的一串数字,在本地局域网络是唯一的。
IP地址的分类:
每个IP地址包括两部分:网络地址和主机地址
IP地址127.0.0.1-127.255.255.255用于回路测试
网络中进程的通信
- 首要解决的问题是如何表示一个进程,否则通信不可能实现
- 不过TCP/IP协议族已经解决了这个问题
- 网络层的IP地址可以唯一标识网络中的主机
- 传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)
套接字
socket(套接字)是进程间通信的一种方式。
创建socket
import socket
# socket方法有两个参数
# 1.address family:可以选择AF_INET(用于internet进程间通信)或者AF_UNIX(用于同一台机器进程间的通信)
# 2.Type:套接字类型,可以是SOCK_STREAM(流式套接字,主要用于TCP协议)或者SOCK_DGRAM(数据报套接字,主要用于UDP协议)
# 创建udp套接字
s1 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 创建tcp套接字
s2 = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
关于UDP
UDP==》用户数据报协议,是一个无连接的简单的面向数据的传输层协议。不提供可靠性(数据可能传送不到)。由于UDP在传输数据前不用再客户和服务器之间建立一个连接,且没有超时重发的机制,传输速度很快。
udp网络程序--发送数据
- 创建套接字
- 发送、接收数据
- 关闭套接字
在官网下载网络调试助手
from socket import *
# 1.创建udp套接字
# s1 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 2.准备接受对方的地址信息
sendaddr = ('192.168.1.112', 7777)
# 3.输入信息
send = input('请输入你要发送的信息:').encode('gbk') # 网络调试助手会通过gbk方式进行解码,所以编码时使用gbk方式
# 4.发送
# sendto有两个参数,第一个参数:发送的信息(经过编码),第二个参数:对方的地址(元组类型)
udp_socket.sendto(send, sendaddr)
# 5.接收对方数据
recv_data = udp_socket.recvfrom(1024) # 1024表示接收最大字节数
# recv_data会接收到连个信息,对方发送的数据和地址
print(recv_data)
print(recv_data[0].decode('gbk'))
# 关闭套接字
udp_socket.close()
地址信息按照这里输入:
得到:
注意:端口号输入自己的IP地址
关于TCP
可以看成服务端和客户端之间的通信,在传输数据之前需要建立连接(经历三次握手),经过数据来回传送后断开连接(四次挥手)。
- 创建socket套接字
- 绑定id和port
- 设置套接字的被动连接
- 等待客户端的连接
- 收发数据
tcp服务端
from socket import *
# 创建套接字
tcpSocket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
tcpSocket.bind(('', 1234))
# 使用listen方法,将其变成监听套接字
tcpSocket.listen(5) # 参数设置5个最大监听数(客户端的个数)
# 如果有新的客户端来连接服务端,就产生一个新的套接字专门为这个客户端服务
newSocket, clientAddr = tcpSocket.accept() # 返回两个值 一个是新的套接字,另一个是客户端的地址
print(clientAddr)
# 接收对方发送过来的数据
recvData = newSocket.recv(1024) # 表示最大字节数
print(recvData.decode('gbk'))
# 收发一些数据到客户端
newSocket.send('hello world')
# 关闭这个为客户端服务的套接字
newSocket.close()
# 关闭监听套接字,意味着整个程序不能再接收任何新的客户端的请求
tcpSocket.close()
tcp客户端
from socket import *
tcp_clisocket = socket(AF_INET, SOCK_STREAM)
# 连接服务器
tcp_clisocket.connect(('192.168.1.112', 1234))
# 提示客户端发送数据
send_data = input("请输入你要发送的内容:").encode("utf-8")
tcp_clisocket.send(send_data)
# 接收对方发来的数据
recvData = tcp_clisocket.recv(1024)
print(recvData)
# 关闭套接字
tcp_clisocket.close()
tcp的三次握手(建立连接)
术语:
- SYN:同步位 SYN=1表示进行一个连接请求
- ACK:确认位 ACK=1确认有效,ACK=0确认无效
- ack:确认号,对方发送序号+1
- seq:序号
- FIN:断开连接并会停止发送数据
抽象描述:
客户端:我要连接你了,服务器。
服务端:我知道了。我要连接你了,客户端。
客户端:我知道了。
4次挥手(断开连接)
因为中间有数据的交互,而停止之后数据就传不过去了
所以服务器要先回数据,再进行停止操作。
抽象描述:
客户端:服务器,我要断开了。
服务端:好的。
数据传输结束之后。。
服务端:客户端,我要断开了。
客户端:好的。
服务端与客户端的交互
服务端代码:
from socket import *
# 创建套接字
tcp_sever_socket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
tcp_sever_socket.bind(('', 1234))
# 使用listen方法,将其变成监听套接字
tcp_sever_socket.listen(5) # 参数设置5个最大监听数(客户端的个数)
while True:
# 如果有新的客户端来连接服务端,就产生一个新的套接字专门为这个客户端服务
newSocket, clientAddr = tcp_sever_socket.accept() # 返回两个值 一个是新的套接字,另一个是客户端的地址
while True:
# 接收对方发送过来的数据
recvData = newSocket.recv(1024) # 表示最大字节数
if len(recvData) > 0:
print(recvData.decode('gbk'))
else:
break
# 发送一些数据到客户端
send_data = input('请输入发送给客户端的信息').encode('gbk')
newSocket.send(send_data)
# 关闭这个为客户端服务的套接字
newSocket.close()
客户端代码:
from socket import *
tcp_cli_socket = socket(AF_INET, SOCK_STREAM)
# 连接服务器
serveraddr = ('192.168.1.112', 1234)
tcp_cli_socket.connect(serveraddr)
while True:
# 提示提示客户端发送数据
send_data = input("请输入你要发送的内容:").encode("gbk")
if len(send_data) > 0:
tcp_cli_socket.send(send_data)
else:
break
# 接受对方发送过来的数据
recvdata = tcp_cli_socket.recv(1024)
print(recvdata.decode('gbk'))
# 关闭套接字
tcp_cli_socket.close()
运行结果:
粘包
只有tcp有粘包现象,udp没有。
粘包问题主要因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。此外,发送方引起的粘包是由tcp协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段,若连续几次需要发送的程序都很少,通常TCP会根据优化把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
粘包现象解决方法
- 让接收端知道发送端将要发送的字节流长度(提前知道)
服务端代码:
from socket import *
ser = socket(AF_INET, SOCK_STREAM)
ser.bind(("192.168.1.112",8888))
ser.listen(5)
while True:
new, addr = ser.accept()
while True:
msg = new.recv(1024)
if not msg:
break
print(msg.decode("utf-8"))
str1 = ''
data_len = len(str1.encode('utf-8'))
# 服务器第一次响应客户端(发送的是长度)
new.send(str(data_len).encode('utf-8'))
data = new.recv(1024).decode('utf-8')
if data == 'r_ready':
new.sendall(str1.encode('utf-8'))
new.close()
客户端代码:
from socket import *
cli = socket(AF_INET, SOCK_STREAM)
cli.connect(('192.168.1.112', 8888))
while True:
msg = input('请输入数据:').strip()
if len(msg) == 0:
continue
if msg == 'quit':
break
cli.send(msg.encode('utf-8'))
# 拿到服务器将要发送的数据长度
length = int(cli.recv(1024).decode('utf-8'))
# 告诉服务端拿到了长度
cli.send('r_ready'.encode('utf-8'))
send_size = 0
recv_size = 0
data = b""
while recv_size < length:
data += cli.recv(1024)
recv_size = len(data)
print(data.decode('utf-8'))
运行结果: