scoket模块 粘包问题 tcp协议特点 重启服务器会遇到地址被占用问题

scoket()模块函数用法

import socket
socket.socket(socket_family,socket_type,protocal=0)

获取tcp/ip套接字
tcpsock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

获取udp/ip套接字
udpsock =socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

服务端套接字函数

s.bind()      绑定(主机,端口号)到套接字

s.listen()    开始tcp监听

s.accept()  被动接受tcp客户的连接,(阻塞式)等待连接的到来

客户端套接字函数

s.connect()   主动初始化tcp服务器连接

s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

s.recv()  接收tcp数据

s.send()  发送tcp数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)

s.recvfrom()  接收udp数据

s.sendto()   发送udp数据

s.close()    关闭套接字

1 用打电话的流程快速描述socket通信
2 服务端和客户端加上基于一次连接的循环通信
3 客户端发送发空,卡主,证明是从那一个位置卡的

服务端:
from socket import *

phone =socket(AF_INET,SOCK_STREAM)
phone.bind(('127.0.0.1',8081))
phone.listen(5)

conn,addr =phone.accept()
while True:
    data =conn.recv(1024)
    print('server===>')
    print(data)
    conn.send(data.upper())
conn.close()
phone.close()

客户端:
from socket import *

phone=socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8081))

while True:
    msg=input('>>: ').strip()
    phone.send(msg.encode('utf-8'))
    print('client===>')
    data=phone.recv(1024)
    print(data)

说明卡的原有:缓冲区为空recv就卡主,引出原理图

简单演示udp
服务端
from socket import *
phone =socket(AF_INET,SOCK_DGRAM)
phone.bind(('127.0.0.1',8082))

while True:
    msg,addr =phone.recvfrom(1024)
    phone.sendto(msg.upper(),addr)


# 客户端
from socket import *
phone =socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input('>>: ')
    phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082))
    msg,addr =phone.recvfrom(1024)
    print(msg)
  
udp客户端可以并发演示
udp客户端可以输入为空演示
socket 实验推演流程

基于TCP的套接字

  tcp是基于连接的,必须先启动服务端,然后再启动客户端去连接服务端

tcp服务端

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

tcp客户端

cs=socket()     #创建客户套接字
cs.connect()    #尝试连接服务器
comm_loop:      #通讯循环
    cs.send()/cs.recv() #对话(发送/接收)
cs.close()      #关闭客户套接字

socket 通信流程与打电话流程类似

基于tcp协议通信的套接字

import socket

server = socket.socket() #买手机
server.bind(('127.0.0.1',8080)) #插手机卡  bind((ip,port))

server.listen(5)  #开机  半整数池

conn , addr =server.accept()  #待机等待接电话
print(conn)  #双向通道
print(addr)  #地址  ('127.0.0.1', 52942)

data = conn.recv(1024)  #接听别人说话 只接收1024个字节 bytes

print(data)   #b'hello how much?'
conn.send(b'hello big baby!')  #跟别人说话

conn.close()   #关闭通信连接
server.close()  #关闭服务端
服务端
import socket

#t套接字对象
client = socket.socket()

client.connect(('127.0.0.1',8080))  #找服务器 拨电话

client.send(b'hello how much?')  #给服务器发信息
data = client.recv(1024)    #接收服务器发来的信息
print(data)

client.close()   #关闭客户端
客户端

加上连接循环与通信循环

import socket
ip_port=('127.0.0.1',8081)#电话卡
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机


while True:                         #新增接收链接循环,可以不停的接电话
    conn,addr=s.accept()            #手机接电话
    # print(conn)
    # print(addr)
    print('接到来自%s的电话' %addr[0])
    while True:                         #新增通信循环,可以不断的通信,收发消息
        msg=conn.recv(BUFSIZE)             #听消息,听话

        # if len(msg) == 0:break        #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生

        print(msg,type(msg))

        conn.send(msg.upper())          #发消息,说话

    conn.close()                    #挂电话

s.close()                       #手机关机
循环服务端
import socket
ip_port=('127.0.0.1',8081)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port)           #拨电话

while True:                             #新增通信循环,客户端可以不断发收消息
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)

    feedback=s.recv(BUFSIZE)                           #收消息,听话
    print(feedback.decode('utf-8'))

s.close()                                       #挂电话
循环客户端

 

重启服务器时可能会遇到

OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

或者 OSError:[Errno 48] Address already in use

这个是由于你的服务端仍然存在四次挥手的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.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
  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 时间

方法二
方法二

 

tcp协议特点

import socket

server = socket.socket()
server.bind(('127.0.0.1',8808))

server.listen(5)  #半连接池 等待连接的最大的客户端数

conn,addr = server.accept()

# data = conn.recv(1024)
# print(data)
 #b'hellohellohello' 接收的小于1024的 所有的客户端发来的信息

data = conn.recv(4)
print(data)
  #b'hell'

data = conn.recv(5)
print(data)
 #b'ohell'   #会先接收上次没接收完的信息

data = conn.recv(5)
print(data)
 #b'ohell'   #会先接收上次没接收完的信息
conn.close()

server.close()
服务端
import socket

client = socket.socket()

client.connect(('127.0.0.1',8808))


client.send(b'hello')
client.send(b'hello')
client.send(b'hello')
#tcp协议的优化特点 发送数据量小的间隔时间短 会一起发送
#会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给接收端

client.close()
客户端

struct模块

import  struct

data='sdkjfksdfjsdkfjksdjfkjdsk'

#服务端
res = struct.pack('i',len(data))

print(len(res))  #4 会把多个字符的字符串 打包成只有4个的对象

print(res)  #b'\x19\x00\x00\x00'

#客户端
ret=struct.unpack('i',res)  #解包成原来的 字符串个数 i 代表int 整形
print(ret)   #(25,)
print(ret[0])  #25

模拟ssh实现远程执行命令

import socket
import subprocess
import struct
import json

server =socket.socket()
server.bind(('127.0.0.1',8081))
server.listen(5) #半连接池

while True:
    conn ,addr =server.accept()  #等待电话
    while True:
        try:
            data = conn.recv(1024).decode('utf-8')
            if len(data) == 0:break
            obj = subprocess.Popen(data,shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stderr+stdout))

            header_dic = {
                'filename':'cls.av',
                'len':len(stdout+stderr)
            }

            header_bytes = json.dumps(header_dic).encode('utf-8')

            #制作报头
            header = struct.pack('i',len(header_bytes))

            conn.send(header)
            conn.send(header_bytes)

            conn.send(stdout)
            conn.send(stderr)
                #conn.send(stdout+stderr) 字符串拼接 从新拷贝一份占内存
                #  和单独发也是一样的 间隔时间短的接收到的也是一起的
        except  ConnectionResetError:
            break

    conn.close()
server.close()
服务端
import socket,struct,json

client = socket.socket()

client.connect(('127.0.0.1',8081))

while True:
    msg = input('>>>: ').encode('utf-8')
    if len(msg)==0:continue

    client.send(msg)

    header = client.recv(4)  #接收字典打包后的4个长度的字节 精确接收
    print(header,'这是报头的长度')

    head_len = struct.unpack('i',header)[0] #对这个头进行解包,获取原来的字典的字节数长度
    print(head_len,'这是字典的字节数')

    head_dic =json.loads(client.recv(head_len).decode('utf-8'))
               # 反序列化       接收字典的长度(精确接收)解码
                #得到字典
    print(head_dic,'这是字典')
    #对需要接受的数据 进行循环接收
    total_size = head_dic['len']
    recv_size=0
    res=b''
    while recv_size < total_size: #字符
        data = client.recv(1024)  #每次接收到的信息
        res +=data
        recv_size +=len(data)
    print(res.decode('gbk'))

client.close()
客户端

udp套接字

udp是无连接的,先启动那一端都不会报错

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()            #关闭客户套接字

udp套接字简单实例

服务端
import socket

server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    data ,addr =server.recvfrom(1024)
    print(data)

    server.sendto(data.upper(),addr)
客户端
import socket

client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server_addr=('127.0.0.1',8080)  #服务端地址,通常写在配置文件中

client.sendto(b'hello',server_addr)

udp 实现简易版本的qq

import socket
server=socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    msg,addr =server.recvfrom(1024)
    print(addr,msg.decode('utf-8'))
    data=input('服务端回复:').encode('utf-8')
    server.sendto(data,addr)
服务端
import socket

client =socket.socket(type=socket.SOCK_DGRAM)

server_addr =('127.0.0.1',8080)

while True:
    msg = input('输入消息:')
    msg = '客服端1的消息:%s' % msg
    client.sendto(msg.encode('utf-8'),server_addr)
    data,addr =client.recvfrom(1024)
    print(data.decode('utf-8'))
客户端
import socket

client =socket.socket(type=socket.SOCK_DGRAM)

server_addr =('127.0.0.1',8080)

while True:
    msg = input('输入消息:')
    msg = '客服端2的消息:%s' % msg
    client.sendto(msg.encode('utf-8'),server_addr)
    data,addr =client.recvfrom(1024)
    print(data.decode('utf-8'))
客户端2
import socket

client =socket.socket(type=socket.SOCK_DGRAM)

server_addr =('127.0.0.1',8080)

while True:
    msg=input('输入消息:')
    msg='客服端3的消息:%s'%msg
    client.sendto(msg.encode('utf-8'),server_addr)
    data,addr =client.recvfrom(1024)
    print(data.decode('utf-8'))
客户端3

 

subprocess模块

import socket
import subprocess
import struct

'''
服务端必备三要素:
    1 要有固定的ip和port
    2 要能24小时提供服务
    3 能承受高并发
        并发:看上去像同时进行
'''
server=socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,addr =server.accept()
    while True:
        try:
            cmd =conn.recv(1024).decode('utf-8')
            if len(cmd)==0:break #针对linux mac系统
            obj=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()

            #制作固定报头
            header=struct.pack('i',len(stderr+stdout))
            #发送固定长度的报头
            conn.send(header)

            #发送真是数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()
服务端
import socket
import struct

client =socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    cmd =input('>>:').encode('utf-8')
    if len(cmd)==0:continue
    client.send(cmd)

    header=client.recv(4)
    #解析报头获取真是数据的长度
    total_size=struct.unpack('i',header)[0]
    recv_size=0
    data=b""
    while recv_size<total_size:
        res=client.recv(1024)
        recv_size+=len(res)
        data+=res

    print(data.decode('gbk'))
客户端

socketserver模块 实现并发  基于tcp的套接字,关键字就是两个循环,一个是连接循环,一个通信循环

socketserver模块中分两大类:server类(解决连接问题)和request类(解决通信问题)

基于tcp的socketserver自定义的类中的

  1 self.server即套接字对象

  2 self.request即一个连接

  3 self.client_address即客户端地址

基于udp的socketserver自定义的类中的

  1 self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),

  如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)

  2 self.client_address即客户端地址

import socket


client =socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    client.send(b'hello world')
    data= client.recv(1024)

    print(data)
tcp客户端
import socketserver


class MyBaby(socketserver.BaseRequestHandler):
    def handle(self):
        #通信循环
        while True:
            #self.request相当于你的conn通信对象
            data= self.request.recv(1024) #收消息
            print(data)
            self.request.send(data.upper())





if __name__ == '__main__':          #通道循环
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyBaby)
    server.serve_forever()
tcp服务端
import  socket
import time

client =socket.socket(type=socket.SOCK_DGRAM)
server_addr=('127.0.0.1',8080)

while True:
    client.sendto(b'hello world',server_addr)
    data,addr =client.recvfrom(1024)
    print(data,addr)
    time.sleep(1)
udp客户端
import socketserver

class MyBaby(socketserver.BaseRequestHandler):
    def handle(self):
        #通讯循环
        while True:
            #self.request 相当于conn通讯对象
            data,sock =self.request #收消息
            print(data)
            print(sock)
            sock.sendto(data.upper(),self.client_address)  #self.client_address 客户端地址

if __name__ == '__main__':
    server=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyBaby)
    server.serve_forever()
udp服务端

基于tcp文件上传

import socket
import struct
import json

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    conn, addr = server.accept()

    while True:
        try:
            # 接收报头
            header = conn.recv(4)
            # 解析报头获取字典长度
            header_len = struct.unpack('i', header)[0]

            # 接收字典
            header_bytes = conn.recv(header_len)
            header_dic = json.loads(header_bytes.decode('utf-8'))
            print(header_dic)

            # 循环接收文件存储到本地
            file_size = header_dic.get('file_size')
            file_name = header_dic.get('file_name')

            recv_size = 0
            # 文件操作
            with open(file_name, 'wb')as f:
                # 循环接收文件
                while recv_size < file_size:
                    data = conn.recv(1024)
                    f.write(data)
                    recv_size += len(data)
            print(header_dic.get('msg'))
        except ConnectionResetError:
            break
    conn.close()
服务端
import socket
import os
import json
import struct

client = socket.socket()
client.connect(('127.0.0.1', 8080))

# 文件大小
file_size = os.path.getsize(r'E:\代码存放位置\第八周\莫文蔚-偷情.mp3')

# 文件名字
file_name = '歌曲名:偷情- 歌手:莫文蔚.mp3'

# 定义一个字典
d = {
    "file_name": file_name,
    "file_size": file_size,
    "msg": '注意身体哦'

}

data_bytes = json.dumps(d).encode('utf-8')

# 制作字典的报头
header = struct.pack('i', len(data_bytes))

# 发送报头
client.send(header)

# 发送字典
client.send(data_bytes)

# 发送真实数据
with open(r'E:\代码存放位置\第八周\莫文蔚-偷情.mp3', 'rb')as f:
    for line in f:
        client.send(line)
客户端
import json

res = {'name': '他说大帅比'}
# print(json.dumps(res,ensure_ascii=False)) #{"name": "他说大帅比"}
print(json.dumps(res))  # {"name": "\u4ed6\u8bf4\u5927\u5e05\u6bd4"}

'''

    TCP
        三次握手
        可靠协议,流式协议
    
    粘包问题的产生:
        1.接收方:
            我不知道我要接受的数据的总长度
        2.发送方:
            由于TCP协议的内部优化算法negle
            1.会将数据量比较的小的并且时间间隔比较短的数据一次性打包发送
    
    
    UDP
    数据报协议
    没有双向通道
    
    1.UDP协议不存在粘包问题
    2.客户端可以发空
    3.udp可以实现并发的效果
    4.服务端不存在,也不影响客户端朝服务端发送数据
    
    
    TCP/UDP
        TCP:打电话
        UDP:发短信
    
    
    
SocketServer模块
    1.能够实现并发效果
        并发:看起来像同时运行就能称之位并发
    
    2.udp在使用的时候,多个客户端要有一些io操作
        不然容易卡死
    

并发编程
    操作系统发展史
    
    
    多道技术:
        - 空间上的复用(多个程序共一套硬件设备,它是多道技术实现时间上的复用的基础,
            不然还要去硬盘读数据)
            
        - 时间上的复用(单个cpu的电脑上,起多个应用程序。cpu快速切换,给人的感觉是同时运行)
        
        cpu两种情况下才会切换:(先保存当前程序的运行状态)
            - 一个任务占用cpu时间过长或被操作系统强行剥夺走cpu的执行权限(比起串行效率反而降低)
            - 一个任务执行过程中遇到io操作,也会被操作系统强行剥夺走cpu的执行权限(比起串行效率提高)
            
            
    并发:看上去像同时进行的
    并行:同时运行
    补充:单核的计算机不可能实现并行!
    '''

 

posted @ 2019-05-05 19:10  读自己  阅读(491)  评论(0编辑  收藏  举报