百万年薪python之路 -- socket()模块的用法

socket()模块的用法:

import socket
socket.socket(socket_family,socket_type,protocal=0)
 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。

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

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

由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
例如tcpSock = socket(AF_INET, SOCK_STREAM)
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)



s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

第一版,单个客户端与服务端通信(low版)

服务端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127.0.0.1",8848))     # 绑定IP+port
sk.listen(5)        # 设置同时最大监听客户端数
conn , addr = sk.accept()   # 阻塞接收


recv_msg = conn.recv(1024)  # 接收消息
print(recv_msg)
conn.send("zdr".encode("UTF-8"))    # 发送消息


conn.close()    # 连接断开
sk.close()  # 客户端断开
客户端
import socket

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.connect(("127.0.0.1",8848))  # 建立连接



sk.send("zcy".encode("UTF-8"))  # 发送消息

recv_msg = sk.recv(1024)    # 接收消息
print(recv_msg)

sk.close()  # 客户端断开

第二版,通信循环

服务端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127.0.0.1",8848))
sk.listen(5)


while 1:
    conn, addr = sk.accept()
    recv_msg = conn.recv(1024)
    print(recv_msg)
    conn.send("zdr".encode("UTF-8"))


conn.close()
sk.close()
客户端
import socket

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.connect(("127.0.0.1", 8848))




msg = input(">>>")
sk.send(msg.encode("UTF-8"))

recv_msg = sk.recv(1024)
print(recv_msg)

sk.close()

第三版,通信,连接循环

服务端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127.0.0.1",8848))
sk.listen(5)


while 1:
    conn, addr = sk.accept()
    print(conn)
    print(addr)
    print("客户端连接成功!")
    while 1:
        try:
            recv_msg = conn.recv(1024)
            print(recv_msg)
            conn.send(recv_msg + "haha".encode("UTF-8"))
        except ConnectionResetError:
            print("客户端已断开!")
            break

conn.close()
sk.close()
客户端
import socket

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.connect(("127.0.0.1", 8848))


while 1:

    msg = input(">>>")
    sk.send(msg.encode("UTF-8"))

    recv_msg = sk.recv(1024)
    print(recv_msg)

sk.close()

详解recv的工作原理

'''
源码解释:
Receive up to buffersize bytes from the socket.
接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.
对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.
当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.
关闭远程端并读取所有数据后,返回空字符串。
'''
----------服务端------------:
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(2)
print(from_client_data1)
from_client_data2 = conn.recv(2)
print(from_client_data2)
from_client_data3 = conn.recv(1)
print(from_client_data3)
conn.close()
phone.close()

# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data = conn.recv(1024)
print(from_client_data)
print(111)
conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
print(222)

conn.close()
phone.close()


# 3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(1024)
print(from_client_data1)
from_client_data2 = conn.recv(1024)
print(from_client_data2)
from_client_data3 = conn.recv(1024)
print(from_client_data3)
conn.close()
phone.close()
------------客户端------------
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()



# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()

# 3,验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
phone.close()

img

远程执行命令:
服务端
import socket
import subprocess

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

while 1 : # 循环连接客户端
    conn, client_addr = phone.accept()
    print(client_addr)
    
    while 1:
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()
            conn.send(correct_msg + error_msg)
        except ConnectionResetError:
            break

conn.close()
phone.close()
客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话
phone.connect(('127.0.0.1',8080))  # 与客户端建立连接, 拨号


while 1:
    cmd = input('>>>')
    phone.send(cmd.encode('utf-8'))
    from_server_data = phone.recv(1024)
    print(from_server_data.decode('gbk'))

phone.close()  # 挂电话

socket之粘包

img

socket缓存区的详细解释
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

这些I/O缓冲区特性可整理如下:

1.I/O缓冲区在每个TCP套接字中单独存在;
2.I/O缓冲区在创建套接字时自动生成;
3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4.关闭套接字将丢失输入缓冲区中的数据。

输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:

1.unsigned optVal;
2.int optLen = sizeof(int);
3.getsockopt(servSock, SOL_SOCKET, SO_SNDBUF,(char*)&optVal, &optLen);
4.printf("Buffer length: %d\n", optVal);

socket缓冲区解释

socket缓存区的详细解释
代码查看缓冲区大小
import socket
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 重用ip地址和端口
server.bind(('127.0.0.1',8010))
server.listen(3)
print(server.getsockopt(socket.SOL_SOCKET,socket.SO_SNDBUF))  # 输出缓冲区大小
print(server.getsockopt(socket.SOL_SOCKET,socket.SO_RCVBUF))  # 输入缓冲区大小

只有在TCP才有粘包现象,UDP永远都不会!!!!!!!

粘包的两种情况:

1. 接收方没有及时接收缓冲区的数据报,造成多个数据报接收(客户端发送了一段数据,服务端只是接收一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留下来的数据,从而产生了粘包)
服务端
import socket
import subprocess

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.bind(('127.0.0.1', 8080))

phone.listen(5)

while 1:  # 循环连接客户端
    conn, client_addr = phone.accept()
    print(client_addr)

    while 1:
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()
            conn.send(correct_msg + error_msg)
        except ConnectionResetError:
            break

conn.close()
phone.close()
客户端
import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)  # 买电话

phone.connect(('127.0.0.1',8080))  # 与客户端建立连接, 拨号


while 1:
    cmd = input('>>>')
    phone.send(cmd.encode('utf-8'))

    from_server_data = phone.recv(1024)

    print(from_server_data.decode('gbk'))

phone.close() 

# 由于客户端发的命令获取的结果大小已经超过1024,那么下次在输入命令,会继续取上次残留到缓存区的数据。
2. 发送端需要等缓冲区满才能发送出去,产生了粘包现象(发送数据时间间隔很短,数据也很小,会合成一块,产生了粘包)
服务端
import socket


phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

phone.bind(('127.0.0.1', 8080))

phone.listen(5)

conn, client_addr = phone.accept()

frist_data = conn.recv(1024)
print('1:',frist_data.decode('utf-8'))  # 1: helloworld
second_data = conn.recv(1024)
print('2:',second_data.decode('utf-8'))


conn.close()
phone.close()
客户端
import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  

phone.connect(('127.0.0.1', 8080)) 

phone.send(b'hello')
phone.send(b'world')

phone.close()  

# 两次返送信息时间间隔太短,数据小,造成服务端一次收取

粘包解决方案

方案一: low版
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再来一个死循环接收完所有数据。
先介绍以下struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes

img

import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))

# 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1), len(ret1))


# 但是通过struct 处理不能处理太大

ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret))  # 报错
服务端
import socket
import subprocess
import struct

sk = socket.socket()

sk.bind(("127.0.0.1", 8080))

sk.listen(5)
while 1:
    conn, addr = sk.accept()
    print(f"链接来了:{conn, addr}")

    while 1:
        try:

            from_client_data = conn.recv(1024)
            if from_client_data.lower() == b"q":
                print("用户正常退出!")
                break
            obj = subprocess.Popen(from_client_data.decode("UTF-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )

            result = obj.stdout.read() + obj.stderr.read()
            data_size = len(result)
            print(f"发送的总数据大小为{data_size}")
            conn.send(struct.pack("i", data_size))
            conn.send(result)
        except ConnectionResetError:
            print("客户端中断了!")
            break
    conn.close()
sk.close()
客户端
while 1:
    to_server_data = input(">>>").strip().encode("UTF-8")
    if to_server_data == b"":
        print("输入不能为空!")
        continue
    sk.send(to_server_data)
    if to_server_data.lower() == b"q":
        break
    data_size = struct.unpack("i", sk.recv(4))[0]
    from_server_data = b""
    while data_size > len(from_server_data):
        from_server_data += sk.recv(1024)
    print(f"最终的得到的数据大小为{len(from_server_data)}")
    print(from_server_data.decode('gbk'))

sk.close()

但是low版本有问题:
1,报头不只有总数据大小,而是还应该有MD5数据,文件名等等一些数据。
2,通过struct模块直接数据处理,不能处理太大。

转载于:https://www.cnblogs.com/zhangchaoyin/p/11379142.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值