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,数据不会丢失。

粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. 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

下面是一个解决粘包的实例

服务端

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3 """
 4 基于TCP实现远程执行命令,发送数据长度信息解决粘包问题,这是服务端
 5 """
 6 import socket,json,struct
 7 import subprocess
 8
 9 ip_port=(‘服务端IP‘,9000)
10 back_log=5
11 buffer_size=1024
12 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
13
14 #setsockopt解决重启服务端服务端仍然存在四次挥手的time_wait状态在占用地址
15 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #加入一条socket配置,重用ip和端口,
16
17 phone.bind(ip_port)#绑定(主机,端口号)到套接字
18
19 phone.listen(back_log)#开始监听
20
21 while True:                   #连接循环
22     conn,addr=phone.accept()
23     while True:               #通信循环
24         cmd=conn.recv(buffer_size)   #接收消息,recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
25         if not cmd:break      #cmd为空跳出循环
26         print(‘cmd:%s‘ %cmd)
27         res=subprocess.Popen(cmd.decode(‘utf-8‘),
28                              shell=True,
29                              stdout=subprocess.PIPE,
30                              stderr=subprocess.PIPE
31                              )#此函数将解码后的cmd给shell去解释,stdout输出参数,stderr报错参数
32         err=res.stderr.read() #读出报错
33         print(err)
34         if err:
35             back_msg=err
36         else:
37             back_msg=res.stdout.read()
38         #发送
39         headers={‘data_size‘:len(back_msg)} #包含数据长度信息的报头
40         head_json=json.dumps(headers) #将报头序列化用于传输
41         head_json_bytes=bytes(head_json,encoding=‘utf-8‘) #再字节化
42
43         #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
44         conn.send(struct.pack(‘i‘,len(head_json_bytes))) #先发报头长度,这4个字节里只包含了一个数字,该数字是报头的长度
45         conn.send(head_json_bytes) #再发报头
46         conn.sendall(back_msg) #再发真实内容
47         #s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
48      #s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
49     conn.close() #关闭套接字

客户端

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3 """
 4 基于TCP实现远程执行命令,发送数据长度信息解决粘包问题,这是客户端
 5 """
 6 import socket,json,struct
 7
 8 ip_port=(‘服务端IP‘,9000)
 9 back_log=5
10 buffer_size=1024
11 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
12
13 #s.connect()     主动初始化TCP服务器连接
14 #s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
15 client.connect_ex(ip_port)
16
17 while True:
18     cmd=input(‘>>: ‘)
19     if not cmd:continue #防止输入空值
20     if cmd == ‘quit‘:break
21     client.send(bytes(cmd,encoding=‘utf-8‘)) #将命令编码后转为字节发送
22
23     head=client.recv(4) #接收长度为4个字节的报头长度信息
24     head_json_len=struct.unpack(‘i‘,head)[0] #将报头长度信息解包,得到报头长度
25     head_json=json.loads(client.recv(head_json_len).decode(‘utf-8‘))
26     #利用报头长度取出报头并解码、反序列化,得到报头
27     data_len=head_json[‘data_size‘] #从报头里取出数据长度
28
29     recv_size=0
30     recv_data=b‘‘
31     while recv_size < data_len:
32         recv_data += client.recv(1024) #一次跨1024字节收数据
33         recv_size += len(recv_data) #计算已得到数据长度
34
35     #print(recv_data.decode(‘utf-8‘))
36     print(recv_data.decode(‘gbk‘)) #windows默认gbk编码

以上实现了客户端与服务器的连接并解决了粘包问题,但是不能实现并发,服务器端只能一对一服务,不能一对多服务

为了实现并发我们引入socketserver,以下代码只针对实现并发

并发服务端

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3
 4 import socketserver
 5
 6 class MyServer(socketserver.BaseRequestHandler):
 7     def handle(self):
 8         print(self.request) #conn
 9         print(self.client_address) #addr
10
11         while True:
12             try:
13                 #收消息
14                 data=self.request.recv(1024)
15                 print("收到消息",data)
16                 #发消息
17                 self.request.sendall(data.upper())
18             except Exception as e:
19                 print(e)
20                 break
21 if __name__ == ‘__main__‘:
22     s=socketserver.ThreadingTCPServer((‘192.168.1.106‘,9000),MyServer)
23     s.serve_forever()

客户端

 1 #!/usr/bin/python
 2 # -*- coding: utf-8 -*-
 3 import socket,json,struct
 4
 5 ip_port=(‘192.168.1.106‘,9000)
 6 back_log=5
 7 buffer_size=1024
 8 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 9
10 #s.connect()     主动初始化TCP服务器连接
11 #s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
12 client.connect_ex(ip_port)
13
14 while True:
15     cmd=input(‘>>: ‘)
16     if not cmd:continue #防止输入空值
17     if cmd == ‘quit‘:break
18     client.send(bytes(cmd,encoding=‘utf-8‘)) #将命令编码后转为字节发送
19
20     data=client.recv(buffer_size)
21     print(‘收到服务端发来的消息‘,data.decode(‘utf-8‘))
22
23 client.close()

此时可用多个客户端与服务器交互

原文地址:https://www.cnblogs.com/roygood/p/9773514.html

时间: 2024-10-10 04:20:01

socket编程(二)的相关文章

Python 基础之socket编程(二)

Python 基础之socket编程(二) 昨天只是对socket编程做了简单的介绍,只是把socket通信的框架搭建起来,要对其中的功能进行进一步的扩充,就来看看今天的料哈! 一.基于tcp的套接字 1. tcp的服务端 ss = socket() #创建服务器套接字 ss.bind() #把地址绑定到套接字 ss.listen() #监听链接 inf_loop: #服务器无限循环 cs = ss.accept() #接受客户端链接 comm_loop: #通讯循环 cs.recv()/cs.

Java网络编程二:Socket详解

Socket又称套接字,是连接运行在网络上两个程序间的双向通讯的端点. 一.使用Socket进行网络通信的过程 服务端:服务器程序将一个套接字绑定到一个特定的端口,并通过此套接字等待和监听客户端的连接请求. 客户端:客户端程序根据你服务器所在的主机名和端口号发出连接请求. 两者之间的通信是通过Socket完成的,我们可以认为Socket是两个城市之间的交通工具,有了它,就可以在两个城市之间穿梭了. Socket通信示例 主机A的应用程序和主机B的应用程序通信,必须通过Socket建立连接,而建立

JAVA Socket 编程学习笔记(二)

在上一篇中,使用了 java Socket+Tcp/IP  协议来实现应用程序或客户端--服务器间的实时双向通信,本篇中,将使用 UDP 协议来实现 Socket 的通信. 1. 关于UDP UDP协议(用户数据报协议)是无连接的.不可靠的.无序的,速度快,进行数据传输时,首先将要传输的数据定义成数据报(Datagram),大小限制在64k,在数据报中指明数据索要达到的Socket(主机地址和端口号),然后再将数据报发送出去,Java 对UDP 协议通信提供了两个主要的类,DatagramPac

Java网络编程基础(二)-- 基于TCP/IP的Socket编程

本节讲点: 客户端套接字:Socket:Socket的创建和使用方法,以及Socket选项和异常. 服务端套接字:ServerSocket:SeverSocket的创建和使用方法,以及ServerSocket选项 简单的Client/Server对话程序 支持多客户端的Client/Server服务响应程序 在学习JDK的网络编程之前先要了解一下网络基础知识和网络协议. TCP(传输控制协议)一种基于连接的通信协议.可靠传输 UDP(用户数据包协议)不稳定连接的通信协议 TCP和UDP的端口如下

iOS开发——网络编程OC篇&amp;Socket编程

Socket编程 一.网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象: 传输层.会话层.表示层和应用层则被称作主机层,是用户所面向和关心的内容. http协议   对应于应用层 tcp协议    对应于传输层 ip协议     对应于网络层 三者本质上没有可比性.  何况HTTP协议是基于TCP连接的. TCP/IP是传输层协议,主要

linux下socket编程-进程间通信

一.什么是Socket Socket接口是TCP/IP网络通信的API,Socket接口定义了许多函数或例程,可以用它们来开发TCP/IP网络上的应用程序. Socket类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM).流式是一种面向连接针对于面向连接的TCP服务应用:数据报式Socket是一种无连接针对无连接的UDP服务应用.sock通信的基本过程如下: 二.Socket建立 程序可以调用Socket函数建立socket,该函数返回一个类似

(转)Java Socket编程

原文出自:http://www.cnblogs.com/rocomp/p/4790340.html Socket是网络驱动层提供给应用程序编程接口和一种机制.可以把Socket比喻成一个港口码头,应用程序只要把货物放到港口码头上,就算完成了货物的运送.对于接收方应用程序也要创建一个港口码头,只需等待货物到达码头后将货物取走. InetAddress          InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址.           该类没有构造方法       

Python Socket编程

因需要做一个监测TCP数据内容的小插件,学习了局域网内部的封装好的传输软件Socket: 一.Socket的简介如下: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 建立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员 做网络开发所用的接口,这就是Socket编程接口:HTTP是轿车,提供了封装或者显示数据的具体形式:Socket是发动机, 提供了网络通信的能力.

Swift和C混合Socket编程实现简单的ping命令

这个是用Mac下的Network Utility工具实现ping命令,用Wireshark抓取的ICMP数据包: 发送ICMP数据包内容 接受ICMP数据包内容 一.icmp结构 要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议.ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方.ICMP协议是IP层的 一个协议,但是由于差错报告在发送给报文源发方时可能

socket编程——一个简单的例子(转)

原文地址:http://blog.csdn.net/wind19/article/details/6156339 从一个简单的使用TCP例子开始socket编程,其基本步骤如下: server                                                  client +++++++                                          ++++++++ 创建socket