网络编程 - 总结
物理连接介质 + 互联网协议(OSI七层)
ip + mac 确定唯一的计算机
ip + port 确定唯一的应用程序
应用层(http/ftp/mail) <-> 传输层(tcp/udp) <-> 网络层(IP) <-> 数据链路层(ethernet) <-> 物理层(mac)
tcp 可靠协议,流失协议
-
三次握手:
1.客户端向服务端发起建立连接的请求 2.服务端收到请求后,如果同意则返回确认信息 3.客户端收到服务端可以连接的确认后,向服务端发送确认连接
-
四次挥手:
1.客户端向服务端发出要断开连接的请求 2.服务端收到请求后,向服务端发出等待的消息,清理通信的残余信息 3.服务端向处于等待状态的客户端发送可以断开连接的信息 4.客户端收到服务端信息后,向服务端发送断开连接确认 特点:服务端断开连接前在一段时间内多次向客户端继续发生没发送完的数据,直到客户端接收完毕,或这段时间到
TCP和UDP的区别:
tcp协议:面向连接,消息可靠,相对udp来讲,传输速度慢,消息是面向流的,无消息保护边界
udp协议:面向无连接,消息不可靠,传输速度快,消息是面向包的,有消息保护边
传输可靠性:
TCP:是面向连接、可靠的字节流服务,因为只要对方回了确认收到信息,才发下一个,如果没收到确认信息就重发
UDP:数据报协议,只是将消息发送出去,并不能保证它们能到达目的地,且没有超时重发等机制
socket URL
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用来实现 进程在网络中通信。
补充:
发送数据时:应用层将产生的数据交给socket,sokcet控制操作系统,数据由操作系统发送出去
接收数据时:数据先到达操作系统内存中,再到应用程序中
TCP协议的套接字通信流程
1.服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。 2.在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接 就建立了。 3.客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接, 一次交互结束
#-------server ------ import socket sk = socket.socket() # sk 是套接字对象,用来与客户端建连接的 sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字 sk.listen() #监听链接 conn,addr = sk.accept() #等待接受客户端链接 # conn 是套接字(双向通道通信)对象, addr是ip和port ret = conn.recv(1024) #等待接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 sk.close() #关闭服务器套接字(可选) #------client ------- import socket sk = socket.socket() # 创建客户套接字 sk.connect(('127.0.0.1',8898)) # 尝试连接服务器 sk.send(b'hello!') ret = sk.recv(1024) # 对话(发送/接收) print(ret) sk.close() # 关闭客户套接字
粘包问题
收发数据量不匹配造成
粘包现象分两种:
1.连续发送小的数据,间隔时间很短,有可能一次就接收到了这几个连续的拼接在一起的小数据.
2.当你一次接收的数据长度小于你一次发送的数据长度,那么一次接受完剩下的数据会在下一次接收数据的时候被一起接收
解决粘包问题
解决方案一: 将要发送的字节流总大小让接收端知晓,然后接收端发一个确认消息给发送端,然后发送端再发送过来后面
的真实内容,接收端再来一个死循环接收完所有数据。
解决方案二:
通过struck模块将需要发送的内容的长度进行打包,打包成一个4字节长度的数据发送到接收端,再发送真实内
容,
接收端取出前4个字节,然后对这四个字节的数据进行解包,拿到发送的内容的长度
然后通过这个长度来循环继续接收我们实际要发送的内容
# ------ server ---------- import socket import subprocess import struct server = socket.socket() ip_port = ("192.168.15.51",8002) server.bind(ip_port) server.listen(3) while 1: conn,addr = server.accept() flag=0 while not flag: #来自客户端的指令 from_client_cmd = conn.recv(1024).decode("utf-8") print(from_client_cmd) sub_obj = subprocess.Popen(from_client_cmd,shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE) server_cmd_msg = sub_obj.stdout.read() # server_cmd_err = sub_obj.stderr.read() #首先计算出你将要发送的数据的长度 cmd_msg_len = len(server_cmd_msg) #先对数据长度进行打包,打包成4个字节的数据,目的是为了和你将要发送的数据拼接在一起 msg_len_stru = struct.pack("i",cmd_msg_len) conn.send(msg_len_stru) #首先发送打包成功后的那4个字节的数据 conn.sendall(server_cmd_msg) #循环send数据,知道数据全部发送成功 conn.close() # ------ client ------ import socket import struct client = socket.socket() server_ip_port = ip_port = ("192.168.15.51",8002) client.connect(server_ip_port) flag = 0 while not flag: cmd = input("请输入要执行的指令>>>") client.send(cmd.encode("utf-8")) #先接收服务端要发送给我的信息长度,前四个字节,固定的 from_server_msglen = client.recv(4) unpack_len_msg = struct.unpack("i",from_server_msglen)[0] #接收数据长度统计,和服务端发给我的数据长度作比较,来确定跳出循环的条件 recv_msg_len = 0 #统计拼接接收到的数据,注意这个不是统计长度 all_msg =b'' while recv_msg_len < unpack_len_msg: every_recv_data = client.recv(1024) #将每次接收到的数据进行拼接和统计 all_msg += every_recv_data #对每次接收到的数据的长度进行累加 recv_msg_len +=len(every_recv_data) print(all_msg.decode("gbk")) client.close()