-
IP地址
- 一个IP地址包括两部分:网络号和主机号,网络号确定局域网,主机号确定局域网内具体某个主机;
- A-E类地址的固定前缀逐渐增加(0->10->110->1110->11110);
- C类地址最常见,主机0-255,其中0、255不用做主机号;如果需一个局域网内有很多主机,则可以利用B/A类地址;DE类地址不常用,基本上是保留地址;
- 私有IP:本地局域网上的IP,内部使用
- 10.0.0.0 - 10.255.255.255
- 172.16.0.0 - 172.31.255.255
- 192.168.0.0 - 192.168.255.255
- 公有IP:全球访问
- 127.0.0.1 - 127.255.255.255 用于回路测试,常见的127.0.0.1即本机地址
- 子网掩码:用于区分网络号和主机号
- C类子网掩码:255.255.255.0(因为C类地址有3个字节的网络号);其他类推
- 端口号:用来标记和区分进程
- 一台主机多个进程,需要不同端口号以便识别这些进程;
- IP寻找主机,端口号寻找进程;
- 知名端口:0-1023,如80、22端口;动态端口:1024-65535,动态分配给进程
-
协议
- 四层/七层及协议簇
- 四层/七层及协议簇
-
socket
- 应用层-socket-传输层
- Socket对TCP/IP协议封装,提供API,面向C/S(client/server)
- 三个步骤:服务器监听,客户端请求,连接确认
- demo
import socket # AF_INET是IPV4协议,SOCK_STREAM(TCP协议,可靠/较慢) or SOCK_DGRAM(UDP,不可靠/较快) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- UDP — User Data Protocol,用户数据报协议
- 不建立连接,不保证对方收得到,没有超时重发,因此传输速度很快
- 常用于语音聊天、简单文件传输
- TCP — 传输控制协议
- 建立连接,三次对话(A ask B & B ack A & A confirm)
- 采用TCP协议的socket只能发给同样使用TCP协议的socket,UDP同理
- UDP 数据发送demo(使用网络调试助手)
from socket import * s = socket(AF_INET, SOCK_DGRAM) # s.bind(("", 8788)) # IP地址为接收IP地址,50000是自定义测试端口 s.sendto("hello".encode("gb2312"), ("192.168.1.103", 50000)) # 接收(一般要绑定一个用于接收的IP和端口) s.bind(("", 8788)) # ""不写默认本机;注意如果使用该设置,send时就要先绑定 recdata = s.recvfrom(2048) # 2048是可接收字节大小 print(recdata[0].decode("gb2312")) s.close()
- echo服务器:返回接收到的内容
from socket import * e_s = socket(AF_INET, SOCK_DGRAM) e_s.bind(("", 8585)) while True: rd = e_s.recvfrom(1024) # 接收消息,rd=( msg, (ip, port) ) s.sendto(rd[0], rd[1]) # 发送回去 e_s.close()
- 应用层-socket-传输层
-
TFTP:Trivial File Transfer Protocol 简单文件传输协议
- 可以实现简单文件的下载,tftp的端口为69
- TFTP示意图(图片来自尚学堂)
- TFTP格式要求
- 传输结束:客户端接收到 数据块长度小于516 字节的包(包长可能大于516);如果最后刚好是516,则服务器还会发一个0字节长的包;
- 如何构造 下载请求 数据包(图片来自尚学堂):利用struct包
- struct模块的使用:
- 构造下载请求
from socket import * import struct udpsock = socket(AF_INET, SOCK_DGRAM) server_ip = "" package = struct.pack("!H7sb5sb", 1, b"dog.jpg", 0, b"octet", 0) # 1、发送下载请求 udpsock.sendto(package, (server_ip, 69)) f = open("dog.jpg", "ab") while True: # 2、接收数据 rd = udpsock.recvfrom(1024) # 接收数据 # 3、分析数据(注意接收到的数据是516字节=2字节操作码+2字节块编号+512字节数据) caozuoma, ack_num = struct.unpack("!HH", rd[0][:4]) # 获取数据块编号 rand_port = rd[1][1] # 服务器当前发送数据的随机端口 # 4、判断状态是否异常 if int(caozuoma) == 5: print("文件不存在") break print("操作码:%d, ACK:%d,服务器随机端口:%d,数据长度:%d"%(caozuoma, ack_num, rand_port, len(rd[0]))) # 5、没有异常则可以写数据 f.write(rd[0][4:]) # 6、接收完毕则关掉 if len(rd[0]) < 516: break # 7、未接收完毕,返回确认 ack_data = struct.pack("!HH", 4, ack_num) udpsock.sendto(ack_data, (server_ip, rand_port))
-
广播:UDP数据包发送给某个子网内所有计算机
from socket import * # 发送给交换机(某个子网,由<broadcast>定义的广播地址,子网内所有开放8080端口的计算机都能收到) dest = ("<broadcast>", 8080) s = socket(AF_INET, SOCK_DGRAM) # socket默认不能发送广播消息,需要更改设置 s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) s.sendto(b"hi", dest) # 接收多个反馈消息 while True: s.recvfrom(1024)
-
TCP
-
三次握手
-
四次挥手
-
TCP服务器
from socket import * tcp_sock = socket(AF_INET, SOCK_STREAM) tcp_sock.bind("", 7788) # 服务器需绑定IP和端口 tcp_sock.listen(5) # 最大等待数5 tcp_sock_new, client_addr = tcp_sock.accept() # 等待连接 data = tcp_sock_new.recv(1024) # 接收数据 tcp_sock_new.send(b"收到") # send不用目标地址和端口,因为前面连接时4次握手建立可靠链接了 tcp_sock_new.close() tcp_sock.close() # =============================== # TCP客户端 from socket import * tcp_sock_cli = socket(AF_INET, SOCK_STREAM) # client tcp_sock_cli.connect(("", 7788)) # address(ip, port) tcp_sock_cli.send(b"你好") data = tcp_sock.recv(1024) print(data) tcp_sock_cli.close()
-
-
多进程服务器
from socket import * from multiprocessing import * from time import sleep # 处理客户端请求 def dealWithClient(sock_new, dest_addr): while True: data = sock_new.recv(1024) if len(data) > 0: print(f"receive from {dest_addr}: {data}") else: # 客户端发送完毕后,会发送0字节数据包 print(f"{dest_addr} 客户端已经关闭") break sock_new.close() # 注意要关闭 if __name__ == "__main__": sock_serv = socket(AF_INET, SOCK_STREAM) sock_serv.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 一般不允许重复使用同一个地址 sock_serv.bind("", 7788) sock_serv.listen(5) # sock_serv.accept() while True: sock_new, dest_addr = sock_serv.accept() # 新客户访问前,会阻塞 # 创建新进程为新的客户端服务 client = Process(target = dealWithClient, args=(sock_new, dest_addr)) client.start() # sock_new由client进程保存一份,当前进程的sock_new可以销毁 sock_new.close() finally: # 所有客户端服务完毕后,关闭 socke_serv.close()