标签:七层 简单 close 端口 lis 系统 通过 条件 影响
一、客户端/服务器架构 即C/S架构,包括: 1、硬件C/S架构(打印机) 2、软件C/S架构(web架构) 互联网中处处是C/S架构(黄色网站是服务端,你的浏览器是客户端;腾讯作为服务端为你提供视频,你得下 腾讯视频客户端才能看视频) C/S架构与socket的关系;我们学习socket就是为了完成C/S架构的开发。
二、osi七层 引子: 须知一个完整的计算机系统是由硬件、操作系统、应用软件三者组成,具备了这三个条件, 一台计算机系统就可以自己跟自己玩了(打个单机游戏...) 如果你要跟别人一起玩,那你就需要上网了(访问黄色网站,发个朋友圈) 互联网的核心就是有一堆协议组成,协议就是标准,全世界的通讯标准是英语, 如果把计算机比作人,互联网协议就是计算机界的英语。所有的计算机都学会了互联网协议, 那所有的计算机都可以按照统一的标准去发收信息从而完成通讯了, 人们按照分工不同把互联网协议从逻辑上划分了层级。
为何学习socket一定要学习互联网协议: 1、首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件 2、其次:C/S架构1的软件(软件属于应用层)是基于网络进行通讯的。 3、然后:网络的核心即一堆协议,协议即标准,你想要开发一款基于网络通讯的软件,就必须遵守这些标准。 4、最后:就让我们从这些标准开始研究,开启我们的socket编程之旅。
三、socket层
四、socket是什么 socket是应用层与TCP/IP协议通讯的中间软件抽象层,它是一组接口,在设计模块中,socket其实就是一门模式 它把复杂TCP/IP协议隐藏在socket接口后面,对于用户来说,一组简单的接口就是全部,让socket去组织数据, 以符合指定的协议。所以我们无需深入理解TCP/UDP协议,socket已经为我们分装好了, 我们只需要遵循socket的规定去编程,写出的程序自然就是遵循TCP/UDP标准的。
五、套接字发展史及分类 套接字起源于20世纪70年代加利福尼大学伯克利分校版本的Unix,即人们所说的 BSD Unix,因此, 有些人也把套接字称为“伯利克套接字” 或 “BSD套接字”,一开始,套接字设计用在一台主机上多个应用程序 之间的通讯,或TPC,套接字有两种(或者称为有两个种族),分别是基于文件型和基于网络型 基于文件类型的套接字家族 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用就是底层文件系统来取数据,两个套接字进程运行在同一机器,可以通过 同一个文件系统间接完成通讯。 基于网络类型的套接字家族 套接字家族的名字:AF_INET (由于我们只关心网络编程,所以大部分时候我们只使用AF_INET)
六、套接字工作流程
七、基于TCP的套接字
# 创建一个服务器 import socket # 导入 socket 模块 # 创建 socket 对象 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 绑定IP地址和端口 phone.bind(("127.0.0.1",8080)) # 等待客户端连接 phone.listen(5) # 建立客户端连接 coon,addr = phone.accept() # 接受数据 coon.recv(1024) # 发送数据 coon.send("sb".encode("utf-8")) # 关闭连接 coon.close() phone.close()
# 创建客户端 import socket # 导入 socket 模块 # 创建 socket 对象 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 链接的IP地址和端口 phone.connect(("127.0.0.1",8080)) # 发送数据 phone.send("sb".encode("utf-8")) # 接收数据 a = phone.recv(1024) print(a) # 打印
# 关闭连接
phone.close()
服务器升级版
from socket import * # 导入socket模块 ip = ("127.0.0.1",8080) # 要绑定的ip和端口 back_log = 5 # 线程数 buffer_size = 1024 # 字节 a = socket(AF_INET,SOCK_STREAM) # 创建socket对象 a.bind(ip) # 绑定ip和端口 a.listen(back_log) # 线程 while True: # 循环 print("服务端开始运行了") # 打印信息 conn,addr = a.accept() # 建立客户端连接 while True: # 循环 try: # 处理异常 data = conn.recv(buffer_size) # 接收信息 print("客户端发来的信息:",data.decode("utf-8")) # 打印信息 conn.send(data.upper()) # 发送信息 except Exception: # 处理异常 break # 处理异常逻辑,结束循环 conn.close() # 关闭连接 a.close() # 关闭连接
客户端升级版
from socket import * # 导入socket模块 ip = ("127.0.0.1",8080) # 要绑定的ip和端口 buff_size = 1024 # 字节 a = socket(AF_INET,SOCK_STREAM) # 创建socket对象 a.connect(ip) # 链接的ip和端口 while True: # 循环 msg = input(">>>") # 等待用户输入 if not msg:continue # 判断 msg ,如果msg为空则跳出本次循环,进入下一次循环 a.send(msg.encode("utf-8")) # 发送信息 print("客户端已经发送信息") # 打印信息 data = a.recv(buff_size) # 接收信息 print("服务端发来的信息:",data.decode("utf-8")) # 打印信息
windows解决上面异常的方法:
# 在bind()前面加上 a.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 加入一条socket配置,重用ip和端口 from socket import * ip = ("127.0.0.1",8080) # 要绑定的ip地址和端口 back_log = 5 # 线程数 buffer_size = 1024 # 节字 a = socket(AF_INET,SOCK_STREAM) # 创建socket对象 a.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 加入一条socket配置,重用ip和端口 a.bind(ip) # 绑定ip和端口 a.listen(back_log) # 线程 while True: # 循环 print(‘服务端开始运行了‘) # 打印信息 conn,addr=a.accept() # 建立客户端连接 while True: # 循环 try: # 处理异常 data = conn.recv(buffer_size) # 接收信息 print("客户端收到的信息:", data.decode("utf-8")) # 打印信息 conn.send(data.upper()) # 发送信息 except Exception: # 处理异常 break # 结束循环 conn.close() # 关闭连接 a.close() # 关闭连接
Linux解决方法:
根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒,TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务 发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决, vi /etc/sysctl.conf http://www.server-cn.com/ 编辑文件,加入以下内容: 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 让参数生效。 h p://www.server-cn.com/ net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭; net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭; http://www.server-cn.com/ net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。 net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
八、基于UDP的套接字
# 服务端 import socket # 导入socket模块 a = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建socket对象 ip = ("127.0.0.1",8080) # ip和端口 a.bind(ip) # 绑定ip和端口 while True: # 通讯循环 data,addr = a.recvfrom(1024) # 接受数据 data是数据 addr是客户端ip和端口 print("收到服务端信息:%s %s"%(data,addr)) # 打印信息 a.sendto(data.upper(),addr) # 发送数据 a.close() # 关闭链接
# 客户端 import socket # 导入socket a = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建socket对象 ip = ("127.0.0.1",8080) # ip和端口 while True: # 通讯循环 msg = input(">>>:") # 等待用户输入 a.sendto(msg.encode("utf-8"),ip) # 发送数据 data,addr = a.recvfrom(1024) # 接受数据 data是数据 addr是服务端ip和端口 print("收到服务端信息:%s %s"%(data,addr)) # 打印信息 a.close() # 关闭链接
九、基于TCP的套接字,UDP的套接字实现远程执行命令
TCP服务端
import socket # 导入socket模块 import subprocess # 导入subprocess模块 a = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建socket对象 ip = ("127.0.0.1",8080) # ip和端口 a.bind(ip) # 绑定ip和端口 a.listen(5) # 线程数 while True: # 链接循环 conn,addr = a.accept() # 等待用户链接 print("接入链接%s"%conn) # 打印信息 while True: # 通讯循环 try: # 处理异常 data = conn.recv(1024) # 接收信息 print("接收到客户端的信息:%s"%data) # 打印信息 if not data:break # 判断 data为空则结束循环 eer = subprocess.Popen(data.decode("utf-8"),shell=True, #创建subprocess对象 stderr = subprocess.PIPE, # 异常 stdout = subprocess.PIPE, # 输出 stdin = subprocess.PIPE) # 输入 res = eer.stderr.read() # 读异常信息并赋值给res if res: # 判断res为真则执行 res_a = res # res为真则执行 else: # 否则执行else res_a = eer.stdout.read() if not res_a: # 判断res_a 为空则执行 res_a = ("执行成功".encode("utf-8")) # 打印信息 conn.send(res_a) # 发送信息 except Exception: # 处理异常 break # 结束循环 conn.close() # 关闭链接 a.close() # 关闭链接
TCP客户端
import socket # 导入socket模块 a = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建socket对象 ip = ("127.0.0.1",8080) # ip和端口 a.connect(ip) # 链接的ip和端口 while True: # 通讯循环 msg = input(">>>:") # 等待用户输入 if not msg:continue # 判断msg为空则跳出循环,重新循环 if msg == "quit":break # 判断msg == "quit" 则结束循环 a.send(msg.encode("utf-8")) # 发送信息 print("已发送信息") # 打印信息 data = a.recv(1024) # 接受信息 print("服务端发来的信息:%s"%data.decode("utf-8")) # 打印信息 # Linux "utf-8" wid "gbk" a.close() # 关闭链接
UDP服务端
import socket # 导入socket模块 import subprocess # 导入subprocess模块 a = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建socket对象 ip = ("127.0.0.1",8083) # ip和端口 a.bind(ip) # 绑定ip和端口 while True: # 通讯循环 data,addr = a.recvfrom(1024) # 接收信息 data是数据 addr是客户端ip和端口 print("收到客户端信息:%s"%data) # 打印信息 res = subprocess.Popen(data.decode("utf-8"),shell=True, # 创建subprocess对象 stderr = subprocess.PIPE, # 异常 stdout = subprocess.PIPE, # 输出 stdin = subprocess.PIPE) #输入 eer = res.stderr.read() # 读异常并赋值给eer if eer: # eer为真则执行,否则执行else res_a = eer else: res_a = res.stdout.read() if not res_a: # res_a 为空则执行 res_a = ("执行成功".encode("utf-8")) a.sendto(res_a, addr) # 发送数据 a.close() # 关闭链接
UDP客户端
import socket # 导入socket模块 a = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建socket对象 ip = ("127.0.0.1",8083) # ip和端口 while True: # 通讯循环 msg = input(">>>:") # 等待用户输入 if not msg:continue # msg为空则跳出本次循环,重新循环 if msg == "quit":break # msg == "quit" 则结束循环 a.sendto(msg.encode("utf-8"),ip) # 发送信息 data,addr = a.recvfrom(1024) # 接收信息 print("服务端发来的信息:%s"%data.decode("gbk")) # 打印信息 Linux "utf-8" wid "gbk" a.close() # 关闭链接
十、为什么TCP 会粘包
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
由于TCP无消息保护边界, 需要在消息接收端处理消息边界问题。也就是为什么我们以前使用UDP没有此问题。 反而使用TCP后,出现少包的现象
上面说了原理,但可能有人使用TCP通信会出现多包/少包,而一些人不会。那么我们具体分析一下,少包,多包的情况。
正常情况,发送及时每消息发送,接收也不繁忙,及时处理掉消息。像UDP一样.
发送粘包,多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包. 这种情况和客户端处理繁忙,接收缓存区积压,用户一次从接收缓存区多个数据包的接收端处理一样。
发送粘包或接收缓存区积压,但用户缓冲区大于接收缓存区数据包总大小。此时需要考虑处理一次处理多数据包的情况,但每个数据包都是完整的
发送粘包或接收缓存区积压, 用户缓存区是数据包大小的整数倍。 此时需要考虑处理一次处理多数据包的情况,但每个数据包都是完整的。
发送粘包或接收缓存区积压, 用户缓存区不是数据包大小的整数倍。 此时需要考虑处理一次处理多数据包的情况,同时也需要考虑数据包不完整。
我们的情况就属于最后一种,发生了数据包不完整的情况。
1、发送端需要等缓冲区满才发送出去,造成粘包
2、接收方不及时接收缓冲区的包,造成多个包接收
UDP不需要处理,免的忘记了。
十一、解决TCP粘包
TCP服务端
import socket # 导入socket模块 import subprocess # 导入subprocess模块 import struct # 导入struct模块 a = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建socket对象 ip = ("127.0.0.1",8080) # ip和端口 a.bind(ip) # 绑定ip和端口 a.listen(5) # 线程数 while True: # 链接循环 conn,addr = a.accept() # 等待用户链接 print("接入链接%s"%conn) # 打印信息 while True: # 通讯循环 try: # 处理异常 data = conn.recv(1024) # 接收信息 print("客户端发来信息:%s"%data) # 打印信息 if not data:break # 判断 data 为空则结束循环 eer = subprocess.Popen(data.decode("utf-8"),shell=True, # 创建subprocess对象 stderr = subprocess.PIPE, # 异常信息 stdout = subprocess.PIPE, # 输出信息 stdin = subprocess.PIPE) # 输入信息 res = eer.stderr.read() # 读异常信息并赋值给res if res: # 判断 res为真则执行 否则执行else res_a = res else: res_a = eer.stdout.read() if not res_a: # 判断 res_a 为空则执行 res_a = ("执行成功".encode("utf-8")) lenth = len(res_a) # 取res_a的长度并赋值给lenth lenht = struct.pack("i",lenth) # 以int类型打包lenth并赋值给lenht conn.send(lenht) # 发送信息 conn.send(res_a) # 发送信息 except Exception: # 处理异常 break # 结束循环 conn.close() # 关闭链接 a.close() # 关闭链接
TCP客户端
import socket # 导入socket模块 import struct # 导入struct模块 a = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建socket对象 a.connect(("127.0.0.1",8080)) # 链接ip和端口 while True: # 通讯循环 msg = input(">>>:") # 等待用户输入 if not msg:continue # 判断 msg 为空则跳出本次循环,重新循环 if msg == "quit":break # 判断 smg == "quit"则结束循环 a.send(msg.encode("utf-8")) # 发送信息 print("客户端已发送信息") # 打印信息 data = a.recv(4) # 接收信息 lenth = struct.unpack("i",data)[0] # 以int类型解包并赋值给lenth res_size = 0 # 设置变量 res_msg = b"" # 设置变量 while res_size < lenth: # res_size 小于 lenth 则循环 res_msg += a.recv(1024) # res_msg = res_msg + a.recv(1024) res_size = len(res_msg) # res_size = res_msg的长度 print("服务端发来信息:%s"%res_msg.decode("utf-8")) # 打印信息 Linux "utf-8" wid "gbk" a.close() # 关闭链接
标签:七层 简单 close 端口 lis 系统 通过 条件 影响
原文地址:https://www.cnblogs.com/linglinglingling/p/11221050.html