码迷,mamicode.com
首页 > 编程语言 > 详细

python socket编程

时间:2019-10-04 17:06:26      阅读:97      评论:0      收藏:0      [点我收藏+]

标签:七层   简单   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() # 关闭链接

 

python socket编程

标签:七层   简单   close   端口   lis   系统   通过   条件   影响   

原文地址:https://www.cnblogs.com/linglinglingling/p/11221050.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!