1.socket模块中的TCP协议
- socket模块在osi模型中的虚拟位置
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。 也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序 而程序的pid是同一台机器上不同进程或者线程的标识
a:格式:sk = socket.socket()
b:参数:sk = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
c:基于TCP协议的套接字的流程:
#用TCP协议循环对话:
#client客户端 import socket sk = socket.socket() sk.connect(('127.0.0.1',65534)) while 1: mes = input(">>>:") sk.send(mes.encode('utf-8')) if mes.upper() == 'Q': break msg = sk.recv(1024).decode('utf-8') if msg.upper() == 'Q': break print(msg) sk.close()
#serve服务器端 import socket sk = socket.socket() sk.bind(('127.0.0.1',65534)) sk.listen() while 1: conn,addr = sk.accept() conn指的就是连接对象 while 1: message = conn.recv(1024).decode('utf-8') print(message) if message.upper() == 'Q': break mes = input(">>>:") conn.send(mes.encode('utf-8')) if mes.upper() == 'Q': break conn.close() sk.close()
#
tcp_sk = socket.socket()
ret = tcp_sk.connect_ex(('127.0.1.1',8091))
tcp_sk.send('nihao'.encode('utf-8'))
tcp_sk.close()
***connect_ex跟connect的区别,connect_ex错误的时候有返回值,返回数字10061,然后往下执行;报的是下一行的错
但是connect直接在本行报错
#这两段代码中牵扯到一个tcp的三次握手和四次挥手:
三次握手:
- 一定是client先发起请求
- a 客户端发起请求连接服务器
- b 服务器返回 : 接收到请求,并要求连接客户端
- c 客户端回复 : 可以连接
四次挥手:
- 谁先发起断开连接的请求都可以
- a 客户端发起断开连接的请求:我想和你断开连接,我没有数据要继续发送了,但是如果你有数据需要发送,我可以继续接收
- b 服务器回复 : 我接收到你的请求了
- c 服务器发送 : 我已经准备好断开连接了
- d 客户端回复 : 收到你的信息,断开连接
几个专有名词:
- ACK:确认收到
- SYN:请求连接的这么一个标识
- FIN:请求断开的这么一个标识
- 断开连接时,因为双方有数据产生了,所以服务端接到请求时,三次握手的中间一步必须分成两步来写了!
关于操作系统和端口如果重复使用时,会被报错,可能操作系统没有及时清理端口信息,你重开服务器时,会遇到的错误
解决方案:
#加入一条socket配置,重用ip和端口 phone=socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加 phone.bind(('127.0.0.1',8080))
2.UDP协议及编码
a:sk = socket.socket(type=socket.SOCK_DGRAM)
b:回环地址:每个计算机都有,只能用于本机,不能作为其他计算机解析这个ip地址;
c:udp跟多个对象聊天代码:
#serve服务器 import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) udp_sk.bind(('127.0.0.1',65534)) while 1: msg,addr = udp_sk.recvfrom(1024) if msg.decode('utf-8').upper() == "Q": break print(msg.decode('utf-8')) msg_send = input('>>>:') udp_sk.sendto(msg_send.encode('utf-8'),addr) if msg_send.upper() == "Q": break udp_sk.close()
#client客户端 import socket ip_sk = ('127.0.0.1',65534) udp_sk = socket.socket(type=socket.SOCK_DGRAM) while 1: msg_send = input('>>>:') udp_sk.sendto(msg_send.encode('utf-8'),ip_sk) if msg_send.upper() == "Q": break back_msg,addr = udp_sk.recvfrom(1024) print(back_msg.decode('utf-8')) if back_msg.decode('utf-8').upper() == 'Q': break udp_sk.close()
3.粘包问题
1.问题引出:看下面的两端程序
服务器import socket sk = socket.socket() sk.bind(('127.0.0.1',8888)) sk.listen() conn,addr = sk.accept() conn.send(b'hello') conn.send(b'world') conn.close() sk.close() #客户端 import socket sk = socket.socket() sk.connect_ex(('127.0.0.1',8888)) msg1 = sk.recv(1024) print('msg1:',msg1) msg2 = sk.recv(1024) print('msg2:',msg2) sk.close() #这段代码中可能会出现两个语句合并成一个语句打印出来 b'halloworld
- 这种造成数据混乱的现象就叫粘包
- 粘包现象只用于TCP,UDP永远不会粘包;
现象:基于tcp协议传输数据时
- 所以只有tcp会发生粘包现象,因为接收端不知道发送端每次发送的数据流是多少,因为数据流没有边界,不能计算
- 在tcp协议中,有一个合包机制(Nagle算法):针对的是小的数据,每一次send进行打包成一块数据传送,客户端和服务端都会进行三次握手和四次挥手确认信息完整,如果丢失数据,则报错!
- tcp协议中还有一个拆包,针对的大数据,因为受到网卡的MTU限制,会将大的超过MTU限制的数据,进行拆分,拆分成多个小的数据,进行传输,当传输目标主机的操作系统层时,会重新将多个小的数据合成原本的数据;
- UDP在实际传输中的过程,基于udp协议时,一个sendto只能对应一个revrfrom,每一个数据相当于带了一个链接地址和内容,这时下一个sendto发送过来的数据和自动接到上一个sendto的后面,所以第一个sendto如果不接收完,就不会接收第二个;
- 所以以前都是基于udp协议的传输,发送端只管发送,接收端不作出回应,所以一旦中间有个数据丢失,那这个链接的数据就会断开;一个recvfrom(x)必须对唯一一个sendto(y),收完x个字节的数据就算完成,若y>x数据就会丢失,
- tcp协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,已端总是在收到ack时才会清除缓存区内容,数据是可靠的,但是会粘包
针对 使用udp协议发送数据,一次收发大小究竟多少合适?
1)udp不会发生粘包,udp协议本层对一次收发数据大小的限制是:
- 65535 - ip包头(20) - udp包头(8) = 65507
2)站在数据链路层:
- 因为网卡的MTU一般被限制在了1500,所以对于数据链路层来说,一次收发数据的大小被限制在:
- 1500 - ip包头(20) - udp包头(8) = 1472
3)得到结论:
- 如果sendto(num)
- num > 65507 报错
- 1472 < num < 65507 会在数据链路层拆包,而udp本身就是不可靠协议,所以一旦拆包之后,造成的多个小数据包在网络传输中,如果丢任何一个,那么此次数据传输失败
- num < 1472 是比较理想的状态