本节内容:

1.socket的介绍

2.基于tcp的socket

3.基于tcp的问题分析

4.基于udp的socket

5.基于udp的问题分析

6.基于udp的ntp服务

7.基于tcp的远程执行命令服务

8.粘包

9.粘包的两种解决办法

1.socket的介绍

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求

注意的一点:socket 的应用程序在内存的用户空间中,而操作系统在内存空间中,socket的应用程序要想使用网卡(硬件) 必须通过操作系统间接去内存的缓冲区去收发数据。也就是说socket的  收 --》去缓冲区拿  发---》送到缓冲区,跟socket有关的就是缓冲区了。

2.基于tcp的socket

####server端######

ss = socket() #创建服务器套接字
ss.bind() #把地址绑定到套接字
ss.listen() #监听链接
inf_loop: #服务器无限循环
cs = ss.accept() #接受客户端链接
comm_loop: #通讯循环
cs.recv()/cs.send() #对话(接收与发送)
cs.close() #关闭客户端套接字
ss.close() #关闭服务器套接字(可选) ###client端#####
cs = socket() # 创建客户套接字
cs.connect() # 尝试连接服务器
comm_loop: # 通讯循环
cs.send()/cs.recv() # 对话(发送/接收)
cs.close() # 关闭客户套接字

3.tcp的一些问题分析

0:实现循环收发数据  (采用循环)

1:客户输了一个 回车 导致 都阻塞在recv阶段
分析:
收发都是去内存的缓存区去取数据,而你发送了空 取不到数据呀,自然就服务端就阻塞在了recv,
而对于客户端他发送了空数据后,下一步就接受来自服务端的数据。而你的服务端还没收到数据,
怎么给你客户端发数据,自然也阻塞住了。
解决办法:加上一个if 条件判断,当用户输入为空时,continue 2:客户端的强制关闭 导致服务端发生错误
分析:
我服务端recv得到的是data 和conn 你把conn干掉,服务端的recv肯定报错呀
解决办法:try except 当出现错误,就break退出收发循环,并关闭客户端的链接 3:客户端正常关闭close,导致了服务端一直循环
解决办法:服务端加个if not data:break 退出通信循环 4:现在的服务器只能接收一个accept 我要接收多个客户端。
解决办法:while true 循环接收accept 接收到的链接都被挂起来了。

4:有的同学在重启服务端时可能会遇到

这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)

解决方法:

方法一:

#加入一条socket配置,重用ip和端口

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf 编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30 然后执行 /sbin/sysctl -p 让参数生效。 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭; net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭; net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。 net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

方法二

4.基于UDP的socket

######udp服务端#######

ss = socket()   #创建一个服务器的套接字
ss.bind() #绑定服务器套接字
inf_loop: #服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close() ########udp客户端######## cs = socket() # 创建客户套接字
comm_loop: # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字

通过以上代码:我们可以看出服务端少了一个listen的过程,那么分析下为什么需要listen?

因为tcp是基于一个双向链接的协议,当多个客户端来之后 只会为其中的一个客户端提供服务,其他的客户端都处于链接挂起的状态。那udp没有链接啊,自然就没有listen 和 accept的过程。

5.udp套接字的一些问题分析:

1:接收和发送问题
收recvfrom() (收到的是一个元祖的格式,第一个是发送过来的数据 第二个又是个客户端的套接字元祖 );
发sendto (数据,服务端的套接字); [因为你udp没有链接了呀,所以每次发送数据需要带上服务端的套接字] 2:实现自由收发信息 (while循环) 3:udp 发送回车,可以接受到数据;(recv默认从缓存区拿不到数据就阻塞住,而recvfrom默认可以从缓存区拿到空数据) 问题保留?等会解答 4:tcp和udp一个服务端,对应多个客户端,udp可以实现并发
因为他本根不就需要建立连接。

6.基于udp的ntp服务实现

from socket import  *
import time
ip_port = ('127.0.0.1',9009)
buffer_size = 1024 sk_server = socket(AF_INET,SOCK_DGRAM)
sk_server.bind(ip_port) while True:
print("服务端开启..")
data,addr = sk_server.recvfrom(buffer_size)
print("客户端的地址:",addr) if not data:
back_data = time.strftime('%Y-%m-%d %H:%M:%S').encode('utf-8')
else:
data = str(data,'utf-8')
print("收到服务的命令:", data)
back_data = time.strftime(data).encode('utf-8')
sk_server.sendto(back_data,addr)

ntp_server

import  socket
ip_port = ('127.0.0.1',9009)
cs = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) while True:
input_data = input('请输入你的数据:').encode('utf-8')
if input_data == 'quit':break
cs.sendto(input_data,ip_port) data,addr = cs.recvfrom(1024)
data = data.decode('utf-8')
print("服务端返回的命令:",data)

ntp_client

客户端的输出:
请输入你的数据:
服务端返回的命令: 2018-03-13 11:04:02
请输入你的数据:%Y
服务端返回的命令: 2018
请输入你的数据:%Y-%m
服务端返回的命令: 2018-03
请输入你的数据:

7.基于tcp的远程执行命令服务

from socket import  *
import subprocess
import struct
ip_port = ('127.0.0.1',9014)
buffer_size = 1024 sk_server = socket(AF_INET,SOCK_STREAM)
sk_server.bind(ip_port)
sk_server.listen(5) while True:
print("服务端开启..")
conn,addr=sk_server.accept()
print("客户端的地址:",addr)
while True:
try:
data = conn.recv(buffer_size).decode('utf-8')
print("收到服务的命令:",data) back_cmd = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,
stderr=subprocess.PIPE,stdin=subprocess.PIPE) err_cmd = back_cmd.stderr.read() if not err_cmd: ##如果命令执行成功
cmd_back = back_cmd.stdout.read()
if not cmd_back: ##如果是执行命令cd .. 默认,客户端从缓冲区是拿不到数据的,给个默认的返回
cmd_back = '执行成功'.encode('gbk')
else:
cmd_back = back_cmd.stderr.read() ###直接发送 默认会两个一起发送,不过长度固定占4个字节
conn.send(struct.pack('i',len(cmd_back)))
conn.send(cmd_back) except Exception as e :
print(e);break
conn.close()

xshell_server

import  socket
import struct
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.connect(('127.0.0.1',9014)) while True:
input_data = input('请输入你的数据:')
if not input_data:continue
if input_data == 'quit':break ##发送命令
sk.send(input_data.encode('utf-8')) ##接收固定占的长度4个字节
cmd_len = sk.recv(4)
cmd_int_len = struct.unpack('i',cmd_len)[0] recv_size = 0
recv_msg = b''
while recv_size < cmd_int_len:
recv_msg += sk.recv(1024)
recv_size = len(recv_msg) print("服务端返回的命令:",recv_msg.decode('gbk'))

xshell_client

8.粘包

udp是不存在粘包的,那么粘包的产生是因为:

1:我们客户端定义了recv(1024) 默认接收1024个字节,
2:而服务端一次发送大于1024字节的数据 到客户端的缓冲区,
3:客户端从缓冲区一次只取1024字节,剩下的数据就只能留着下次再去,
4:这就是所谓的粘包

粘包的进一步产生原因分析:

粘包1:TCP有网络延迟,采用使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

粘包2:数据量发送的大,接收的少,再接收的话就是上次未接收完的数据。

为什么udp不存在粘包?

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

而udp是一个消息一个消息的发送,一次recvfrom 可以接收到数据加ip、端口的元组,
那么我只要找到ip_and_port的位置 到下一个消息ip_and_port ,中间的位置都是需要的数据,
这样我就知道了我一次需要提取多少个字节。因为消息跟消息之间存在了界限

9.解决粘包 

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

low版本的解决方法

服务端                    					客户端
发要要发送的数据长度 接收要接收数据的长度
接收一个ready【主要避免TCP的Nagle算法】 发送一个数据 ready
发送 要发送的数据 while 判断,接收数据
import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(ip_port)
s.listen(5) while True:
conn,addr=s.accept()
print('客户端',addr)
while True:
msg=conn.recv(1024)
if not msg:break
res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
stdin=subprocess.PIPE,\
stderr=subprocess.PIPE,\
stdout=subprocess.PIPE)
err=res.stderr.read()
if err:
ret=err
else:
ret=res.stdout.read()
data_length=len(ret)
conn.send(str(data_length).encode('utf-8')) ###发送数据长度
data=conn.recv(1024).decode('utf-8') ##接收客户端数据,为了解决粘包
if data == 'recv_ready':
conn.sendall(ret)
conn.close()

server

import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if len(msg) == 0:continue
if msg == 'quit':break s.send(msg.encode('utf-8'))
length=int(s.recv(1024).decode('utf-8'))
s.send('recv_ready'.encode('utf-8'))
send_size=0
recv_size=0
data=b''
while recv_size < length:
data+=s.recv(1024)
recv_size+=len(data) print(data.decode('utf-8'))

client

为何low:

程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

前戏:将命令的长度 转为固定的4个字节

关于:struct 模块的使用 移步:http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html

>>> import subprocess
>>> cmd_b = subprocess.Popen('ipconfig',shell=True,stdout=subprocess.PIPE,stderr
=subprocess.PIPE)
>>> data = cmd_b.stdout.read()
>>> len(data) ##获取执行命令结果的长度是多少
1799
>>> cmd_len = struct.pack('i',len(data)) ##将命令的长度转为对应的占4个字节
>>> cmd_len
b'\x07\x07\x00\x00'
>>> struct.unpack('i',cmd_len)
(1799,)
>>> struct.unpack('i',cmd_len)[0] ##将4个字节,转为int长度
1799
>>> len(data)
1799
>>>

实现:

from socket import  *
import subprocess
import struct
ip_port = ('127.0.0.1',9014)
buffer_size = 1024 sk_server = socket(AF_INET,SOCK_STREAM)
sk_server.bind(ip_port)
sk_server.listen(5) while True:
print("服务端开启..")
conn,addr=sk_server.accept()
print("客户端的地址:",addr)
while True:
try:
data = conn.recv(buffer_size).decode('utf-8')
print("收到服务的命令:",data) back_cmd = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,
stderr=subprocess.PIPE,stdin=subprocess.PIPE) err_cmd = back_cmd.stderr.read() if not err_cmd: ##如果命令执行成功
cmd_back = back_cmd.stdout.read()
if not cmd_back: ##如果是执行命令cd .. 默认,客户端从缓冲区是拿不到数据的,给个默认的返回
cmd_back = '执行成功'.encode('gbk')
else:
cmd_back = back_cmd.stderr.read() ###直接发送 默认会两个一起发送,不过长度固定占4个字节
conn.send(struct.pack('i',len(cmd_back)))
conn.send(cmd_back) except Exception as e :
print(e);break
conn.close()

server

 import  socket
import struct
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.connect(('127.0.0.1',9014)) while True:
input_data = input('请输入你的数据:')
if not input_data:continue
if input_data == 'quit':break ##发送命令
sk.send(input_data.encode('utf-8')) ##接收固定占的长度4个字节
cmd_len = sk.recv(4)
cmd_int_len = struct.unpack('i',cmd_len)[0] recv_size = 0
recv_msg = b''
while recv_size < cmd_int_len:
recv_msg += sk.recv(1024)
recv_size = len(recv_msg) print("服务端返回的命令:",recv_msg.decode('gbk'))

client

  

Day9 - Python基础9 socket基础、粘包的更多相关文章

  1. 【Python】TCP Socket的粘包和分包的处理

    Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...

  2. Socket的粘包处理

    Socket的粘包处理 当socket接收到数据后,会根据buffer的大小一点一点的接收数据,比如: 对方发来了1M的数据量过来,但是,本地的buffer只有1024字节,那就代表socket需要重 ...

  3. 网络编程基础【day09】:socket解决粘包问题之MD5(八)

    本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...

  4. python之socket编程------粘包

    一.粘包 什么是粘包 只有TCP只有粘包现象,UDP永远不会粘包 所谓粘包问题主要还是因为接收方不知道之间的界限,不知道一次性提取多少字节的数据所造成的 两种情况发生粘包: 1.发送端需要等缓冲区满才 ...

  5. Python学习 :socket基础

    socket基础 什么是socket? - socket为接口通道,内部封装了IP地址.端口.协议等信息:我们可以看作是以前的通过电话机拨号上网的年代,socket即为电话线 socket通信流程 我 ...

  6. c# socket 解决粘包,半包

    处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...

  7. Socket解决粘包问题1

    粘包是指发送端发送的包速度过快,到接收端那边多包并成一个包的现象,比如发送端连续10次发送1个字符'a',因为发送的速度很快,接收端可能一次就收到了10个字符'aaaaaaaaaa',这就是接收端的粘 ...

  8. Socket/TCP粘包、多包和少包, 断包

    转发: https://blog.csdn.net/pi9nc/article/details/17165171 为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个 ...

  9. Socket解决粘包问题2

    在AsynServer中对接收函数增加接收判断,如果收到客户端发送的请求信息,则发送10个测试包给发送端,否则继续接收,修改后的接收代码如下: private void AsynReceive() { ...

随机推荐

  1. C#中类的实例化过程

    创建某个类型的第一个实例时,所进行的操作顺序为:1.静态变量设置为02.执行静态变量初始化器3.执行基类的静态构造函数4.执行静态构造函数5.实例变量设置为06.执行衯变量初始化器7.执行基类中合适的 ...

  2. jieba的使用

    1. 分词 分词是自然语言处理中最基础的一个步骤.而jieba分词是中文分词的一个比较好的工具.下面看看可以怎么用jieba进行分词. import jieba # 全模式 seg_list1 = j ...

  3. simple go web application & 二维码生成 & 打包部署

    go语言简易web应用 & 二维码生成及解码 & 打包部署 转载请注明出处: https://www.cnblogs.com/funnyzpc/p/10801476.html 前言(闲 ...

  4. VSCode+C++环境搭建

    date: 2019-10-05 VSCode+C++环境搭建 其实并不完整,毕竟我也只是一个OIer,并不会很高深的东西.(众所周知,OIer主业是软件开发) 安装VSCode 下载安装包 这个很简 ...

  5. 如何下载B站哔哩哔哩(bilibili)弹幕网站上的视频呢?小白教你个简单方法

    对于90后.00后来说,B站肯定听过吧.小编有一个苦恼的地方,有时候想把哔哩哔哩(bilibili)上看到的视频保存到手机相册,不知道咋操作啊.网上百度了下,都是要下载电脑软件的,有些还得要付费的.前 ...

  6. 【转】开发一个这样的 APP 要多长时间?

    作者:蒋国刚 www.cnblogs.com/guogangj/p/4676836.html 呵呵. 这是一个“如有雷同,纯属巧合”的故事,外加一些废话,大家请勿对号入座.开始了…… 我有些尴尬地拿着 ...

  7. 20191214 Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4)

    概述 切了 ABCE,Room83 第一 还行吧 A - Happy Birthday, Polycarp! 题解 显然这样的数不会很多. 于是可以通过构造法,直接求出 \([1,10^9]\) 内所 ...

  8. 解决IDEA中的DashBoard 不显示端口号

    第一步:找到.idea目录下的workspace.xml文件 第二步:找到下图位置 并添加红色部分代码 添加代码如下: <option name="configurationTypes ...

  9. Sql 中常用时间处理函数

    1.Sql 中常用时间处理函数  GETDATE()  返回当前的日期和时间 DATEPART()  返回日期/时间的单独部分 DATEADD()   返回日期中添加或减去指定的时间间隔 DATEDI ...

  10. mysql中concat函数的使用相关总结

    concat(str1,str2) 返回结果为连接参数产生的字符串.如有任何一个参数为NULL ,则返回值为 NULL. mysql> select concat('11','22','33') ...