socket编程(二)
TCP下粘包问题
两种情况下会发生粘包。
1、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
发送方:AB #其实放在缓存里没发送
发送方:B #其实放在缓存里没发送
发送方:CD #缓存满了,发一波
接收方:ABBCD #及时从缓存里接收信息,我擦,发这是啥答案?
两同学传答案因粘包发生误会,后果严重
2、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
发送方:211 #其实放在缓存里没发送
发送方:12 #其实放在缓存里没发送
发送方:985 #缓存满了,发一波
接收方:21112985 #没有及时从缓存里接收信息,我擦第一题结果这么大?
两同学传答案因粘包发生误会,后果严重
拆包的发生情况
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
为何tcp是可靠传输,udp是不可靠传输
tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的
而udp发送数据,对端是不会返回确认信息的,因此不可靠
send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失。
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
- TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
为了解决粘包问题我们可以考虑发送消息时同时发送关于消息的长度信息,接收方安长度信息提取消息
发送方:(3)211 #其实放在缓存里没发送
发送方:(2)12 #其实放在缓存里没发送
发送方:(3)985 #缓存满了,发一波
接收方:(3)211(2)12(3)985 #没有及时从缓存里接收信息,但是收到了长度信息不用方,按长度信息读取得答案:211 12 985
下面是一个解决粘包的实例
服务端
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
基于TCP实现远程执行命令,发送数据长度信息解决粘包问题,这是服务端
"""
import socket,json,struct
import subprocess ip_port=('服务端IP',9000)
back_log=5
buffer_size=1024
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #setsockopt解决重启服务端服务端仍然存在四次挥手的time_wait状态在占用地址
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #加入一条socket配置,重用ip和端口, phone.bind(ip_port)#绑定(主机,端口号)到套接字 phone.listen(back_log)#开始监听 while True: #连接循环
conn,addr=phone.accept()
while True: #通信循环
cmd=conn.recv(buffer_size) #接收消息,recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
if not cmd:break #cmd为空跳出循环
print('cmd:%s' %cmd)
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)#此函数将解码后的cmd给shell去解释,stdout输出参数,stderr报错参数
err=res.stderr.read() #读出报错
print(err)
if err:
back_msg=err
else:
back_msg=res.stdout.read()
#发送
headers={'data_size':len(back_msg)} #包含数据长度信息的报头
head_json=json.dumps(headers) #将报头序列化用于传输
head_json_bytes=bytes(head_json,encoding='utf-8') #再字节化 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
conn.send(struct.pack('i',len(head_json_bytes))) #先发报头长度,这4个字节里只包含了一个数字,该数字是报头的长度
conn.send(head_json_bytes) #再发报头
conn.sendall(back_msg) #再发真实内容
#s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
#s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
conn.close() #关闭套接字
客户端
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
基于TCP实现远程执行命令,发送数据长度信息解决粘包问题,这是客户端
"""
import socket,json,struct ip_port=('服务端IP',9000)
back_log=5
buffer_size=1024
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #s.connect() 主动初始化TCP服务器连接
#s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
client.connect_ex(ip_port) while True:
cmd=input('>>: ')
if not cmd:continue #防止输入空值
if cmd == 'quit':break
client.send(bytes(cmd,encoding='utf-8')) #将命令编码后转为字节发送 head=client.recv(4) #接收长度为4个字节的报头长度信息
head_json_len=struct.unpack('i',head)[0] #将报头长度信息解包,得到报头长度
head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
#利用报头长度取出报头并解码、反序列化,得到报头
data_len=head_json['data_size'] #从报头里取出数据长度 recv_size=0
recv_data=b''
while recv_size < data_len:
recv_data += client.recv(1024) #一次跨1024字节收数据
recv_size += len(recv_data) #计算已得到数据长度 #print(recv_data.decode('utf-8'))
print(recv_data.decode('gbk')) #windows默认gbk编码
以上实现了客户端与服务器的连接并解决了粘包问题,但是不能实现并发,服务器端只能一对一服务,不能一对多服务
为了实现并发我们引入socketserver,以下代码只针对实现并发
并发服务端
#!/usr/bin/python
# -*- coding: utf-8 -*- import socketserver class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print(self.request) #conn
print(self.client_address) #addr while True:
try:
#收消息
data=self.request.recv(1024)
print("收到消息",data)
#发消息
self.request.sendall(data.upper())
except Exception as e:
print(e)
break
if __name__ == '__main__':
s=socketserver.ThreadingTCPServer(('192.168.1.106',9000),MyServer)
s.serve_forever()
客户端
#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket,json,struct ip_port=('192.168.1.106',9000)
back_log=5
buffer_size=1024
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #s.connect() 主动初始化TCP服务器连接
#s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
client.connect_ex(ip_port) while True:
cmd=input('>>: ')
if not cmd:continue #防止输入空值
if cmd == 'quit':break
client.send(bytes(cmd,encoding='utf-8')) #将命令编码后转为字节发送 data=client.recv(buffer_size)
print('收到服务端发来的消息',data.decode('utf-8')) client.close()
此时可用多个客户端与服务器交互
socket编程(二)的更多相关文章
- linux网络编程之socket编程(二)
今天继续对socket编程进行研究,这里会真正开如用socket写一个小例子,进入正题: TCP客户/服务器模型: 关于这个模型的流程这里就不多说了,比较容易理解,下面则利用这种模型来编写一个实际 ...
- python基础之socket编程(二)
ssh远程执行命令: 思路分析: 客户端给服务端发送命令,服务端返回一个输出结果传给客户端. #coding:utf-8 #买手机 import socket import struct import ...
- Python 基础之socket编程(二)
Python 基础之socket编程(二) 昨天只是对socket编程做了简单的介绍,只是把socket通信的框架搭建起来,要对其中的功能进行进一步的扩充,就来看看今天的料哈! 一.基于tcp的套接字 ...
- Linux下的C Socket编程 -- 获取对方IP地址
Linux下的C Socket编程(二) 获取域名对应的IP地址 经过上面的讨论,如果我们想要连接到远程的服务器,我们需要知道对方的IP地址,系统函数gethostbyname便能够实现这个目的.它能 ...
- Java Socket聊天室编程(二)之利用socket实现单聊聊天室
这篇文章主要介绍了Java Socket聊天室编程(二)之利用socket实现单聊聊天室的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在上篇文章Java Socket聊天室编程(一)之 ...
- Java网络编程二:Socket详解
Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点. 一.使用Socket进行网络通信的过程 服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接 ...
- Py修行路 python基础 (二十四)socket编程
socket编程 一.客户端/服务端架构 客户端/服务端架构 即C/S架构,包括:1.硬件C/S架构,2.软件C/S架构. 互联网中处处都是C/S架构,学习socket 就是为了完成C/S架构的开发. ...
- Linux学习之socket编程(二)
Linux学习之socket编程(二) 1.C/S模型——UDP UDP处理模型 由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,实际上有很多保证通讯可靠性的机制需要在应用层实 ...
- JAVA Socket 编程学习笔记(二)
在上一篇中,使用了 java Socket+Tcp/IP 协议来实现应用程序或客户端--服务器间的实时双向通信,本篇中,将使用 UDP 协议来实现 Socket 的通信. 1. 关于UDP UDP协 ...
- JAVA Socket编程(二)之TCP通信
基于TCP(面向连接)的socket编程,分为客户端和服务器端. 客户端的流程如下: (1)创建套接字(socket) (2)向服务器发出连接请求(connect) (3)和服务器端进行通信(send ...
随机推荐
- luogu 2592 区间dp
\(f_{i, j, a, b}\) 表示当前一共有 \(i\) 人排队, \(j\) 名男生,男生数目 - 女生数目为 \(a\), 女生数目 - 男生数目为 \(b\),\(a, b >= ...
- Codevs 1070 普通递归关系(矩阵乘法)
1070 普通递归关系 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 考虑以下定义在非负整数n上的递归关系 f(n) = f0 ...
- 红黑树 ------ luogu P3369 【模板】普通平衡树(Treap/SBT)
二次联通门 : luogu P3369 [模板]普通平衡树(Treap/SBT) 近几天闲来无事...就把各种平衡树都写了一下... 下面是红黑树(Red Black Tree) 喜闻乐见拿到了luo ...
- 2019暑期金华集训 Day7 动态规划
自闭集训 Day7 动态规划 LOJ6395 首先发现这个树的形态没啥用,只需要保证度数之和是\(2n-2\)且度数大于0即可. 然后设\(dp_{i,j}\)表示前\(i\)个点用了\(j\)个度数 ...
- 《挑战30天C++入门极限》新手入门:C/C++中枚举类型(enum)
新手入门:C/C++中枚举类型(enum) 如果一个变量你需要几种可能存在的值,那么就可以被定义成为枚举类型.之所以叫枚举就是说将变量或者叫对象可能存在的情况也可以说是可能的值一一例举出来. ...
- [SDOI2009][BZOJ 1226]学校食堂
Description 小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭.学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴.当然,不同的人口味也不一定相同,但每个人的口味都可以 ...
- C++2.0新特性(五)——<Rvalue_reference和move语义>
一.Rvalue_reference(右值引用)和move语义 1.左右值概念区分 左值:表达式结束后依然存在的对象,我们也叫做变量: 右值:表达式结束后就不存在的临时对象. 2.判断左值和右值 能对 ...
- encode(编码)和decode(解码)方法
JS对文字进行编码涉及3个函数:escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decodeURIComponent 1 ...
- PostgreSQL学习笔记(九) 用户、角色、权限管理
PostgreSQL是一个多用户数据库,可以为不同用户指定允许的权限. 角色PostgreSQL使用角色的概念管理数据库访问权限. 根据角色自身的设置不同,一个角色可以看做是一个数据库用户,或者一组数 ...
- CMU Database Systems - Storage and BufferPool
Database Storage 存储分为volatile和non-volatile,越快的越贵越小 那么所以要解决的第一个问题就是,如果尽量在有限的成本下,让读写更快些 意思就是,尽量读写volat ...