socket套接字

socket套接字介绍

#socket是处于应用层与传输层之间的抽象层,他是一组操作起来非常简单的接口(接受数据)此接口接受数据之后,交由操作系统.
#为什么存在socket抽象层?
#如果直接与操作系统数据交互非常麻烦,繁琐,socket对这些繁琐的的操作高度的封装,简化.

recv的工作原理

# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
# 3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。recv空字符		串: 对方客户端关闭了,且服务端的缓冲区没有数据了,我再recv取到空bytes.

基于UDP协议的网络通信

##=====server端
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
server.bind(('192.168.14.198',9000))

while 1:

    from_client_data = server.recvfrom(1024)  # 阻塞,等待客户来消息
    print(f'33[1;35;0m来自客户端{from_client_data[1]}: {from_client_data[0].decode("utf-8")} 33[0m')
    to_client_data = input('>>>').strip()
    server.sendto(to_client_data.encode('utf-8'),from_client_data[1])

# 1. 基于udp协议的socket无须建立管道,先开启服务端或者客户端都行.
# 2. 基于udp协议的socket接收一个消息,与发送一个消息都是无连接的.
# 3. 只要拿到我的ip地址和端口就都可以给我发消息,我按照顺序接收消息.
#=======client端
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
while 1:
    to_server_data = input('>>>:').strip()
    client.sendto(to_server_data.encode('utf-8'),('127.0.0.1',9000))
    data,addr = client.recvfrom(1024)
    print(f'来自服务端{addr}消息:{data.decode("utf-8")}')

socket循环+链接通讯

#服务端
import socket
phone = socket.socket()
phone.bind(('127.0.0.1',8848))
phone.listen(2)
# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
while 1:
    conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    print(f'链接来了: {conn,addr}')
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 最多接受1024字节

            if from_client_data.upper() == b'Q':
                print('客户端正常退出聊天了')
                break
            print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
            to_client_data = input('>>>').strip().encode('utf-8')
            conn.send(to_client_data)
        except ConnectionResetError:
            print('客户端链接中断了')
            break
    conn.close()
phone.close()
#客户端
import socket

phone = socket.socket()

phone.connect(('127.0.0.1',8848))
while 1:
    to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    if not to_server_data:
        # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
        print('发送内容不能为空')
        continue
    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':
        break
    from_server_data = phone.recv(1024)  # 最多接受1024字节
    print(f'来自服务端消息:{from_server_data.decode("utf-8")}')

phone.close()

基于socketTCP协议的应用示例:远程执行指令

#服务端
import socket
import subprocess
phone = socket.socket()
phone.bind(('127.0.0.1',8848))
phone.listen(2)# listen: 2 允许有两个客户端加到半链接池,超过两个则会报错
while 1:
    conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
    print(f'链接来了: {conn,addr}')
    while 1:
        try:
            from_client_data = conn.recv(1024)  # 最多接受1024字节
            if from_client_data.upper() == 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()
            conn.send(result)
        except ConnectionResetError:
            print('客户端链接中断了')
            break
    conn.close()
phone.close()
# shell: 命令解释器,相当于调用cmd 执行指定的命令。
# stdout:正确结果丢到管道中。
# stderr:错了丢到另一个管道中。
# windows操作系统的默认编码是gbk编码。
#客户端
import socket
phone = socket.socket()
phone.connect(('127.0.0.1',8848))
while 1:
    to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    if not to_server_data:
        # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
        print('发送内容不能为空')
        continue
    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':
        break
    from_server_data = phone.recv(1024)  # 最多接受1024字节
    print(f'{from_server_data.decode("gbk")}')

phone.close()

粘包现象

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

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

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

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

1.I/O缓冲区在每个TCP套接字中单独存在;
2.I/O缓冲区在创建套接字时自动生成;
3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4.关闭套接字将丢失输入缓冲区中的数据
#须知:只有TCP有粘包现象,UDP永远不会粘包!

产生粘包现象的两种情况
#1,接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收	了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
#2,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)
粘包的解决方案
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总数按照固定字节发送给接收端后面跟上总数据,然后接收端先接收固定字节的总字节流,再来一个死循环接收完所有数据。
#字典形式的解决方式
#服务端
import socket
import subprocess
import struct
import json
phone=socket.socket()
phone.bind(('127.0.0.1',8896))

phone.listen(5)
while 1:
    conn,addr=phone.accept()
    while 1:
        try:
            cmd=conn.recv(1024)
            if cmd.upper()==b"Q":
                print('客户端正常退出')
                break
            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            result=obj.stdout.read()+obj.stderr.read()
            total_size=len(result)
            head_dic = {'filename': 'test','md5':'123456','total_size':total_size}
            head_dic_json=json.dumps(head_dic)
            head_dic_json_bytes=head_dic_json.encode('utf-8')
            len_head_dic_json_bytes= len(head_dic_json_bytes)
            total_head_size=struct.pack('i',len_head_dic_json_bytes)
            conn.send(total_head_size)
            conn.send(head_dic_json_bytes)
            conn.send(result)
        except ConnectionResetError:
            print('客户端退出了')
            break
    conn.close()
phone.close()
#客户端
import socket
import struct
phone=socket.socket()
phone.connect(('127.0.0.1',8896))
while 1:
    to_server_data=input('>>>')
    if not to_server_data.upper():
        print('输入不能为空')
        continue
    phone.send(to_server_data.encode('utf-8'))
    if to_server_data.upper()=='Q':
        print('退出')
        break
    total_size=phone.recv(4)
    head_size=struct.unpack('i',total_size)[0]
    total_data=b''
    while len(total_data)<head_size:
        total_data+=phone.recv(1024)
    print(total_data.decode('gbk'))
phone.close()
#low版
import socket
import subprocess
import struct
phone=socket.socket()
phone.bind(('127.0.0.1',8896))
phone.listen(5)
while 1:
    conn,addr=phone.accept()
    while 1:
        try:
            cmd=conn.recv(1024)
            if cmd.upper()==b"Q":
                print('客户端正常退出')
                break
            obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            result=obj.stdout.read()+obj.stderr.read()
            total_size=len(result)
            head_size=struct.pack('i',total_size)
            conn.send(head_size)
            conn.send(result)
        except ConnectionResetError:
            print('客户端退出了')
            break
    conn.close()
phone.close()
#客户端
import socket
import struct
import json
phone=socket.socket()
phone.connect(('127.0.0.1',8896))
while 1:
    to_server_data=input('>>>')
    if not to_server_data.upper():
        print('输入不能为空')
        continue
    phone.send(to_server_data.encode('utf-8'))
    if to_server_data.upper()=='Q':
        print('退出')
        break
    total_size=phone.recv(4)
    len_head_dic_json_bytes=struct.unpack('i',total_size)[0]
    head_dic_json_bytes=phone.recv(len_head_dic_json_bytes)
    head_dic_json=head_dic_json_bytes.decode('utf-8')
    head_dic=json.loads(head_dic_json)
    from_server_data_size=head_dic['total_size']
    total_data=b''
    while len(total_data)<from_server_data_size:
        total_data+=phone.recv(1024)
    print(total_data.decode('gbk'))
phone.close()

socketserver

#1、引入模块
import socketserver
#2、自己写一个类,类名自己随便定义,然后继承socketserver这个模块里面的BaseRequestHandler这个类
class MyServer(socketserver.BaseRequestHandler):
    # 3、写一个handle方法,必须叫这个名字
    def handle(self):
        # 6、self.request 相当于一个conn
        #self.request                            
        # 7、收消息
        self.request.recv(1024)                  
        msg = '亲,学会了吗'
        # 8、发消息
        self.request.send(bytes(msg,encoding='utf-8'))
        # 9、关闭连接
        self.request.close()                     

        # 拿到了我们对每个客户端的管道,那么我们自己在这个方法里面的就写我们接收消息发送消息的逻辑就可以了
if __name__ == '__mian__':
    # 4、使用socketserver的ThreadingTCPServer这个类,将IP和端口的元祖传进去,还需要将上面咱们自己定义的类传进去,得到一个对象,相当于我们通过它进行了bind、listen
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8090),MyServer)
    # 5、使用我们上面这个类的对象来执行serve_forever()方法,他的作用就是说,我的服务一直开启着,就像京东一样,不能关闭网站,对吧,并且serve_forever()帮我们进行了accept
    server.serve_forever()

#注意:
#有socketserver 那么有socketclient的吗?
#当然不会有,我要作为客户去访问京东的时候,京东帮我也客户端了吗,客户端是不是在我们自己的电脑啊,并且socketserver对客户端没有太高的要求,只需要自己写一些socket就行了。

原文地址:https://www.cnblogs.com/luckinlee/p/11623710.html