套接字这部分不得不强调下tcp协议,这里只讲五层协议。
应用层 应用程序告诉操作系统,要给某ip port 发送bytes数据
传输层 建立端口到端口的通信
网络层 引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
数据链路层 单纯的电信号0和1无意义,定义了电信号的分组方式,将网络层传过来的数据分组,一组电信号构成一个数据包,叫做‘帧’每一数据帧分成:报头head和数据data两部分
物理层 将链路层传过来的数据用高低压的电流传输
tcp协议通信,为何建立链接需要三次握手,而断开链接却需要四次挥手?
A请求和B建立链接
1.A发送请求建立链接信息给B,syn=1&seq=X
2.B收到syn=1&seq=X,发送确认建立链接信息和请求建立链接信息,syn=1&ack=X+1&seq=Y
3.A接到syn=1&ack=X+1&seq=Y后,发送确认建立链接信息给B ack=Y+1&seq=Z
A请求和B断开链接
1.A发送请求断开链接信息给B,fin=1&seq=X
2.B收到fin=1&seq=X,发送确认断开链接信息ack=1&seq=X+1
3.B发送确认断开链接信息fin=1&seq=Y
4.A接到fin=1&seq=Y后,发送确认断开链接信息给B ack=Y+1&seq=Z
建立链接和断开链接的过程是一样的,只是被建立链接方把第二步的确认建立链接和第三步发送建立链接信息合并了
之所以断开链接有四次,是因为B不确定什么时候断开链接,也许A在发送断开请求的时候,B还有数据正在发送给A,此时可单方面断开A到B,还不能断开B到A。
什么是socket?
socket:一组接口,作用于应用程序于传输层之间,等于是将传输层做了一次封装,用户只要和socket简单交互,复杂的事情交给socket与传输层交互
通信流程:服务端:生成socket对象->绑定ip port->监听客户端链接->建立链接->收发数据
客户端:生成socket对象->链接ip port->建立链接->收发数据
什么是粘包?
粘包:将多次发送的数据粘到一起,致使接收方无法区分数据包
原因:1.发送方,有发送算法机制,会将短期内且数据包不大,会将多个包打成一个包进行发送
2.接收方,当一次接收字节小于包的大小时,未接收完的数据会存留在系统内存中,下次有数据包过来时会粘在一起
解决方法:每次发送数据包之前,告诉接收方此次发送的数据包的大小,接收方根据包大小接收指定长度的数据
接下来,我们来看下在python里怎么用socket模块
流式协议(tcp):数据像水流一样,源源不断传输,发送端和接收端都可以实时接收一定字节大小的数据
服务端代码
1 import socket,subprocess,struct,json 2 3 4 ''' 5 地址簇: 6 socket.AF_INET:典型的TCP/IP四层模型的通信过程,发送方、接收方依赖IP:Port来标识,即将本地的socket绑定到对应的IP端口上; 7 发送数据时,指定对方的IP端口,经过Internet,可以根据此IP端口最终找到接收方;接收数据时,可以从数据包中获取到发送方的IP端口。 8 socket.AF_UNIX:典型的本地IPC,类似于管道,依赖路径名标识发送方和接收方。即发送数据时,指定接收方绑定的路径名, 9 操作系统根据该路径名可以直接找到对应的接收方,并将原始数据直接拷贝到接收方的内核缓冲区中,并上报给接收方进程进行处理。 10 同样的接收方可以从收到的数据包中获取到发送方的路径名,并通过此路径名向其发送数据.(可实现进程间的通讯) 11 类型: 12 socket.SOCK_STREAM:流式socket , for TCP 13 socket.SOCK_DGRAM: 数据报式socket , for UDP 14 socket.SOCK_RAW: 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以; 15 SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 16 ''' 17 ip_port= ("127.0.0.1",8081) 18 max_connect = 1 # 最对只接收5个连接,超过时直接拒绝 19 20 # 实例化socket套接字对象,指定地址簇为socket.AF_INET;类型为socket.SOCK_STREAM 流式socket , for TCP 21 server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 22 # 端口复用,当程序重启时端口还是被占用的,加上这个可忽略端口冲突报错 23 server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 24 server_socket.bind(ip_port) # 绑定本机ip+port 25 server_socket.listen(max_connect) # 开始TCP监听,5代表在允许有一个连接排队,更多的新连接连进来时就会被拒绝 26 print("starting....") 27 while True: # 循环生成连接对象,当前连接断开马上进入下一次等待,知道有连接 28 try: # 当客户端暴力断开连接时,windows下会报错,所以用try防止报错导致程序奔溃 29 conn,client_addr = server_socket.accept() # #阻塞直到有连接为止,有了一个新连接进来后,就会为这个请求生成一个连接对象 30 while True: # 循环和客户端交互,收、发信息 31 rec = conn.recv(1024) # 最多接收1024个字节的数据 32 if not rec:break # 当客户端暴力断开连接时,linux下会进入死循环接收数据为空,所以当接收数据为空的时候跳出循环 33 34 # 执行客户端发送过来的shell命令,执行命令,并返回执行结果 35 res = subprocess.Popen(rec.decode("utf-8"), stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) 36 err = res.stderr.read() 37 out = res.stdout.read() 38 datas = err if err else out 39 40 header = {"file_size":len(datas)} # 数据头信息,记录这次需要发送的文件大小 41 header_bytes = bytes(json.dumps(header),encoding='utf-8') # 将数据头信息处理成bytes,便于发送 42 header_len_bytes = struct.pack("i",len(header_bytes)) # 规定发送4个字节记录数据头的大小 43 # 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完) 注:数据必须是bytes类型 44 conn.send(header_len_bytes) # 发送长度为4个字节经过struct打包的数据头长度信息 45 conn.send(header_bytes) # 发送数据头信息,客户端根据上面接收到的数据头长度,接收指定长度的数据头信息 46 conn.send(datas) #发送正式数据,客户端根据上面接收到数据头信息得到本次发送的数据长度,接收指定长度数据 47 48 conn.close() # 关闭连接套接字 49 except Exception as e: 50 print(e) 51 52 server_socket.close() # 关闭套接字
客户端代码
1 import socket,struct,json 2 3 4 server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 5 server_socket.connect(("127.0.0.1",8081)) # 主动初始化TCP服务器连接 6 while True: 7 msg = input(">>>>:").strip() 8 if not msg: continue 9 msg_bytes = msg.encode() 10 server_socket.send(msg_bytes) # 发送的数据必须是bytes类型 11 12 header_len_bytes = server_socket.recv(4) # 接收4个字节的数据头信息 13 header_len = struct.unpack("i",header_len_bytes)[0] # struct.unpack解压数据,得到数据头信息长度 14 header_str = server_socket.recv(header_len).decode("utf-8") # 根据上面的长度接收数据头信息 15 header = json.loads(header_str,encoding="utf-8") 16 file_size = header["file_size"] # 根据数据头信息得到本次要接收的数据大小 17 recv_size = 0 18 res = b'' 19 while recv_size < file_size: # 当接收到的数据小于本次数据长度时就一直接收 20 res += server_socket.recv(10) # 将每次接收到的数据拼接 21 recv_size = len(res) # 实时记录当前接收到的数据长度 22 res = res.decode("GBK") # 这里是windows,搜索一GBK格式打印返回数据 23 print(res) 24 25 server_socket.close()
基于UDP协议的socket编程
数据包协议(upd):一次发送就是一个数据包,不存在源源不断的发送,发送端和接收端都只能一个个包的接收,不能接收指定长度大小的数据
服务端
# udp协议不需要建立链接,直接发送和接收,接收方的地址放在发送函数的第二个位置参数上 # 缺点 不可靠,没有确保数据一定送达,它只做了一件事,发送数据,对方有没有收到,都不在发送。 # 优点 1.因为不需要建链接和确认数据收到,所以发送速度更快 2.不存在粘包现象 # 应用 QQ的消息发送就是用的udp,一般用于数据查询 import socket ip = "127.0.0.1" port = 8083 ip_port = (ip, port) soctet_server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) soctet_server.bind(ip_port) print("starting....") msg, addr = soctet_server.recvfrom(1024) soctet_server.sendto(msg.upper(), addr) print(msg.decode("utf-8"))
客户端
1 import socket 2 3 4 ip = "127.0.0.1" 5 port = 8083 6 ip_port = (ip, port) 7 socket_client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 8 socket_client.sendto(b"hello", ip_port) 9 msg, addr = socket_client.recvfrom(1024) 10 print(msg.decode("utf-8"), addr)