python学习之socket

   本文主要记录一下学习socket的过程

    socket主要通信流程如下

    

 

socket 常用一些方法

服务端套接字函数
s.bind()     绑定(主机,端口)
s.listen()    监听
s.accept()  阻塞等待连接

客户端套接字函数
s.connect()     主动初始化TCP服务器连接

服务端客户端公用
s.recv()            接收TCP数据
s.send()            发送TCP数据
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()       接收UDP数据
s.sendto()          发送UDP数据
s.close()            关闭套接字

 

简单tcp服务端和客户端例子

from socket import *

addr_port=('127.0.0.1',8081)
back_log=5
buffer_size=1024

sk=socket(AF_INET,SOCK_STREAM)
sk.bind(addr_port)
sk.listen(back_log)


print('服务端运行起来了')

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

    while True:
        #print(conn)
        #print(addr)
        try:
            data=conn.recv(buffer_size)
        except Exception as e:
            break
        if not data:  # 客户端如果退出(指正常sk.close(),而非异常退出),服务端将收到空消息,退出
            break
        print('客户端发来的消息是',data.decode('utf-8'))
        conn.send(data.upper())
    conn.close()
sk.close()
服务端
from socket import *

ip_port=('127.0.0.1',8081)
back_log=5
buffer_size=1024

sk=socket(AF_INET,SOCK_STREAM)
sk.connect(ip_port)

while True:
    msg=input('请输入需要发送信息:')
    if msg=='q':
        break
    sk.send(msg.encode('utf-8'))
    print('数据发送成功')
    data=sk.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))
sk.close()
客户端

 udp服务端和客户端例子

 udp是无连接的

from socket import *

ip_port=('127.0.0.1',8080)
buffer_size=1024

udp_sk=socket(AF_INET,SOCK_DGRAM)
udp_sk.bind(ip_port)
while True:
    data,addr=udp_sk.recvfrom(buffer_size)
    print(data)
    udp_sk.sendto(data.upper(), addr)
服务端
from socket import *

ip_port=('127.0.0.1',8080)
buffer_size=1024

udp_sk=socket(AF_INET,SOCK_DGRAM) #数据报

while True:
    msg=input('请输入数据>>: ').strip()
    udp_sk.sendto(msg.encode('utf-8'),ip_port)#udp发送数据时要指定server的ip和端口

    data,addr=udp_sk.recvfrom(buffer_size)
    print(data.decode('utf-8'))
客户端

 

粘包及解决方式

    为了更好理解粘包的原理,我们首先要大致明白数据收发的原理。服务端应用程序会先把数据发送到自己的缓存中(由于tcp的一些优化算法,会将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,再通过缓存发送出去),然后经过网卡传输出去,数据到达客户端后,也会先放在客户端的缓存中,客户端从自己的缓存中取数据。

   对tcp来说没有所谓的send一次,recv一次,可以sed多次,recv一次

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

两种情况下都会发生粘包
1、发送端在发送数据时,会等到缓冲区数据满了,才会向外发送,会导致粘包(发送数据时间间隔很短,数据了很小,会合到一起,就会导致不同是数据是一起发送出去的)
2、接收段接受数据的大小小于缓冲区的数据或者(即只接受了一部分数据,下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
解决粘包的方法

1、发送端在发送数据前,先发送数据的大小(类似报文封装一个头部),再发送真实数据,接收端也是先接收数据大小的数据,再循环接收真实的数据
2、使用struct模块

类似封装头部方式解决粘包

import socket
import subprocess



sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.bind(addr)
sk.listen(3)

while True:
    conn,addr = sk.accept()
    print(addr)
    while True:
        try: #捕捉客户端异常关闭(ctrl+c)
            data = conn.recv(1024)
        except Exception:
            break
        if not data:  #客户端如果退出,服务端将收到空消息,退出
            #conn.close()
            break
        print('执行命令为:',str(data,'utf8'))
        obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
        cmd_result=obj.stdout.read()
        result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小
        result_len=bytes(str(result_len),'utf8')
        print(result_len)
        #print(cmd_result)
        conn.sendall(result_len)
        conn.sendall(cmd_result)
服务端
import socket


sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.connect(addr)
while True:
    inp = input('>>>')
    if inp == 'q':
        break
    sk.send(bytes(inp,'utf8'))
    res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收
    print(res_len)
    data=bytes()
    while len(data)!=int(str(res_len,'utf8')):
        recv=sk.recv(1024)
        data+=recv
    print(str(data,'gbk'))
sk.close()
客户端

 使用struct

import socket
import subprocess
import struct

# sk = socket.socket()
# addr = ('127.0.0.1',9090)
# sk.bind(addr)
# sk.listen(3)
#
# while True:
#     conn,addr = sk.accept()
#     print(addr)
#     while True:
#         try: #捕捉客户端异常关闭(ctrl+c)
#             data = conn.recv(1024)
#         except Exception:
#             break
#         if not data:  #客户端如果退出,服务端将收到空消息,退出
#             #conn.close()
#             break
#         print(str(data,'utf8'))
#         inp = input('>>>>')
#         conn.send(bytes(inp,'utf8'))

#远程执行命令返回结果
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.bind(addr)
sk.listen(3)

while True:
    conn,addr = sk.accept()
    print(addr)
    while True:
        try: #捕捉客户端异常关闭(ctrl+c)
            data = conn.recv(1024)
        except Exception:
            break
        if not data:  #客户端如果退出,服务端将收到空消息,退出
            #conn.close()
            break
        print('执行命令为:',str(data,'utf8'))
        obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
        cmd_result=obj.stdout.read()
        result_len=len(cmd_result)#结果的长度客户端用来判断发送的是否全部接收,但是这样效率比较低,在大并发情况一下,每次发送数据前都要先发送数据的真实大小
        # result_len=bytes(str(result_len),'utf8')
        # print(result_len)
        # #print(cmd_result)
        # conn.sendall(result_len)
        # conn.sendall(cmd_result)
        data_length = struct.pack('i', result_len)
        conn.send(data_length)
        conn.send(cmd_result)
服务端
import socket
import struct
from functools import partial

# sk = socket.socket()
# addr = ('127.0.0.1',9090)
# sk.connect(addr)
# while True:
#     inp = input('>>>')
#     if inp == 'q':
#         break
#     sk.send(bytes(inp,'utf8'))
#     data = sk.recv(1024)
#     print(str(data,'utf8'))
# sk.close()

#远程执行命令返回结果
sk = socket.socket()
addr = ('127.0.0.1',9090)
sk.connect(addr)
while True:
    inp = input('>>>')
    if inp == 'q':
        break
    sk.send(bytes(inp,'utf8'))
    # res_len=sk.recv(1024)#接收的及服务端发来的数据的长度,命令结果的长度客户端用来判断发送的是否全部接收
    # print(res_len)
    # data=bytes()
    # while len(data)!=int(str(res_len,'utf8')):
    #     recv=sk.recv(1024)
    #     data+=recv
    # print(str(data,'gbk'))
    length_data = sk.recv(4)
    length = struct.unpack('i', length_data)[0]

    #recv_msg = ''.join(iter(partial(sk.recv, 1024), b''))

    recv_size = 0
    recv_data = b''
    while recv_size < length:
        recv_data += sk.recv(1024)
        recv_size = len(recv_data)
    print('命令的执行结果是 ', recv_data.decode('gbk'))

sk.close()
客户端

 

posted @ 2020-11-18 17:17  泉love水  阅读(149)  评论(0编辑  收藏  举报