2_socket套接字

Socket

socket概念

  • Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

  • 在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

套接字(socket)的使用

TCP协议中的使用

  • .socket() tcp协议的server , socket(type=socket.SOCK_DGRAM)
  • .bind 绑定一个id和端口
  • .listen 监听,代表socket服务的开启
  • .accept 等待,有客户端来访问和客户端建立连接
  • .send 直接通过连接发送消息,不需要写地址
  • .recv 只接受消息
  • .connect 客户端/tcp协议的方法,和server建立连接
  • .close 关闭服务/连接

sever端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)  #接收客户端信息
print(ret)       #打印客户端信息
conn.send(b'hi')        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)

client端

import socket
sk = socket.socket()           # 创建客户套接字
sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024)         # 对话(发送/接收)
print(ret)
sk.close()            # 关闭客户套接字

UDP协议中的使用

  • .socket(type=socket.SOCK_DGRAM) UDP协议的server
  • .sendto 需要写一个对方的地址
  • .recvfrom 接受消息和地址
  • .close 关闭服务/连接

server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字

client端

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

TCP、UDP协议多人通信

TCP协议的多人多次通信
  • 和一个人通信说多次话
  • 和一个人聊完再和其他人聊
UDP协议的多人多次通信
  • 和一个人通信说多次话
  • 和一个人聊完再和其他人聊

程序的几种阻塞方式

  • input() 阻塞,直到用户输入enter键
  • accept() 阻塞,由客户端和我建立完连接之后
  • recv() 阻塞,知道收到对方发过来的消息之后
  • recvfrom() 阻塞,知道收到对方发过来的消息之后
  • connect() 阻塞,知道server端结束了对一个client的服务,开始和当前client建立连接的时候

socket参数的详解

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
创建socket对象的参数说明
  • family 地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。(AF_UNIX 域实际上是使用本地 socket 文件来通信)
  • type 套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传送。 SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。
  • proto 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。fileno 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。与
  • socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。这可能有助于使用socket.close()关闭一个独立的插座。

socket的更多方法

服务端套接字函数
  • s.bind() 绑定(主机,端口号)到套接字
  • s.listen() 开始TCP监听
  • s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
  • s.connect() 主动初始化TCP服务器连接
  • s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
  • s.recv() 接收TCP数据
  • s.send() 发送TCP数据
  • s.sendall() 发送TCP数据
  • 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() 创建一个与该套接字相关的文件

粘包现象

出现原因

  • 只出现在tcp协议中,多条消息之间没有边界,并且有优化算法

  • 发送端:两条消息发送间隔短,数据小,由于优化机制就合并在一起了

  • 接受端:接受消息不及时,并且接收方缓存端堆在一起导致的粘包

本质

  • tcp协议的传输是流式传输,数据与数据之间没有边界

解决

方法一(自定义协议)

  • 通过自定义协议来解决问题,先发送固定长度的字节,通过接收知道数据的长度,按照数据长度,设置边界,不会黏在一起

Sever端

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

conn, addr = sk.accept()
str_1 = conn.recv(4)
print(str_1)
length = int(str_1.decode('utf-8'))
print(length)

msg1 = conn.recv(length)
msg2 = conn.recv(1024)

print(msg1.decode('utf-8'))
print(msg2.decode('utf-8'))

conn.close()
sk.close()

Client端

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

msg1 = input(">>> ")
msg2 = input(">>> ")

length = len(msg1.encode('utf-8'))
print(length)
str_1 = str(length).zfill(4)
print(str_1)

sk.send(str_1.encode('utf-8'))
sk.send(msg1.encode('utf-8'))
sk.send(msg2.encode('utf-8'))
sk.close()

方法二(通过struct模块解决)

struct模块
方法
  • struct.pack()方法:该模块可以把一个类型,如数字,转成固定长度的bytes
len1 = struct.pack('i', 1)
print(len1, len(len1))  # b'\x01\x00\x00\x00' 4

len2 = struct.pack('i', 123)
print(len2, len(len2))  # b'{\x00\x00\x00' 4

len3 = struct.pack('i', 12345)
print(len3, len(len3))  # b'90\x00\x00' 4

struct.pack('i',1111111111111)  # struct.error: argument out of range
# 抛出异常
# struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
  • struct.unpack()方法:将固定长度的bytes转换回一个类型,如数字
int1 = struct.unpack('i', len1)
print(int1, int1[0])  # (1,) 1

int2 = struct.unpack('i', len2)
print(int2, int2[0])  # (123,) 123

int3 = struct.unpack('i', len3)
print(int3, int3[0])  # (12345,) 12345

验证客户端的合法性

方法一:hashlib验证

Server端
KEY = '123456'

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()
conn, attr = sk.accept()

os_ran = os.urandom(32)
conn.send(os_ran)

sha = hashlib.sha1(KEY.encode('utf-8'))
sha.update(os_ran)

client_sha = conn.recv(1024)

print(client_sha, '---', sha.hexdigest())  # 352d3b92a1dc5b96e79a9b032dafd2eb5188f8bc --- 352d3b92a1dc5b96e79a9b032dafd2eb5188f8bc
Client端
KEY = '123456'

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

os_ran = sk.recv(32)

sha = hashlib.sha1(KEY.encode('utf-8'))
sha.update(os_ran)

ret = sha.hexdigest()

sk.send(ret.encode('utf-8'))

方法二:hmac模块验证(简易版)

Server端
KEY = '123456'

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

conn, attr = sk.accept()

os_ran = os.urandom(32)

conn.send(os_ran)

hm = hmac.new(KEY.encode('utf-8'), os_ran)

client_hm = conn.recv(1024)
print(client_hm, hm.digest())  # b'\x9e !\x82\x9c\xbb\xf0\x06\xf22\xe7\x86c$\xaa\x89'   b'\x9e!\x82\x9c\xbb\xf0\x06\xf22\xe7\x86c$\xaa\x89'
Client端
KEY = '123456'

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

os_ran = sk.recv(32)python

hm = hmac.new(KEY.encode('utf-8'), os_ran)
sk.send(hm.digest())

socketsever模块

  • socketsever是基于socket(底层模块)模块完成的
作用
  • 处理并发的客户端请求
Server端
import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.uppythonper())

if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999
    
    # 设置allow_reuse_address允许服务器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 创建一个server, 将服务地址绑定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 让server永远运行下去,除非强制停止程序
    server.serve_forever()
Client端
import socket

HOST, PORT = "127.0.0.1", 9999
data = "hello"

# 创建一个socket链接,SOCK_STREAM代表使用TCP协议

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))          # 链接到客户端
    sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据
    received = str(sock.recv(1024), "utf-8")# 从服务端接收数据

print("Sent:     {}".format(data))
print("Received: {}".format(received))

猜你喜欢

转载自www.cnblogs.com/q121211z/p/13396068.html