0
点赞
收藏
分享

微信扫一扫

Python socket

诗远 2022-04-16 阅读 99
python

文章目录

一. socket 介绍

假设我们需要编写一个C/S架构的程序,实现数据交互,就需要使用到OSI七层协议,由于它的缺点是分层太多
增加了网络工作的复杂性,所以没有大规模应用。后来人们对 OSI 进行了简化,合并了一些层,最终只保留
了 4 层,从下到上分别是接口层、网络层、传输层和应用层,这就是 TCP/IP 模型。

在这里插入图片描述

1. 而socket(套接字)是在应用程序的传输层和应用层之间抽象出了一个层叫做socket抽象层
2. 可以理解为TCP/IP协议栈提供的对外的操作接口,即应用层通过网络协议进行通信的接口。
3. Socket其实就是一个门面,它把复杂的TCP/IP协议族隐藏在Socket接口后面,不需要自己处理每一层

在这里插入图片描述

1. 套接字分类

基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,
可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族

套接字家族的名字:AF_INET

AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,AF_INET6 则是 IPv6 的
还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,很少被使用,或者是根
本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只
关心网络编程,所以大部分时候我么只使用AF_INET

二. socket套接字使用

套接字Socket可以使用不同的网络协议进行端对端的通信,主要是TCP和UDP协议,使用的流程如下图所示

在这里插入图片描述

1. 代码实现

在python中socket就是一个模块。通过调用模块中的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器
上的一个应用程序。所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信

2. TCP

基于TCP协议的socket需要先建立链接,首先肯定是编写服务端再去考虑客户端。
基础代码

改进通信循环
改进链接循环

3. UDP

基础代码

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)

三. 半连接池

半连接池就是设置等待的最大链接个数。
在 listen(5) 括号里的数字中体现,括号里的数字就是等待链接的最大个数,用于节省资源
超出最大数量的客户端会报错,停止运行。只有前一个停止链接下一个才会接上

在这里插入图片描述

四. 黏包问题

1. struct 模块

我们可以利用 struct 模块的小特性来精准的知道数据的大小
struct.pack()
struct的 pack 可以将任意长度的数字打包成固定长度

代码示例
		
		import struct

		x = 'hello world'
		print('hello 的长度是:  ', len(x))
		y = struct.pack('i', len(x))  # 第一个参数是格式格式如下图
		print('打包后的长度是:  ', len(y))
		
		x1 = 'XWenXiang'
		print('\nXWenXiang 的长度是:  ', len(x1))
		y1 = struct.pack('i', len(x1))
		print('打包后的长度是:  ', len(y1))


输出结果
		hello 的长度是:   11
		打包后的长度是:   4
		
		XWenXiang 的长度是:   9
		打包后的长度是:   4


可以发现不同的长度被打包过后的长度都是一样的,也就是说 pack 可以将任意长度的数字打包成固定长度
需要注意的是,例如格式i都有它的表示范围。

在这里插入图片描述

struct.unpack()
struct.unpack()的作用就是将固定长度的数字解包成打包前真实的长度

代码示例
		import struct

		x = 'hello world'
		print('hello 的长度是:  ', len(x))
		y = struct.pack('i', len(x))
		print('打包后的长度是:  ', len(y))
		z = struct.unpack('i', y)
		print('解包后的长度:  ', z)

打印结果
		hello 的长度是:   11
		打包后的长度是:   4
		解包后的长度:   (11,)


解决方法一
发送方:利用struct模块对信息长度进行打包,并将打包的数据和真实信息发出
接收方:接收打包的数据并解包成真实信息长度,利用这个长度来接收真实信息。
这样接收的数据长度就是准确的了。

服务端代码

服务端代码

import socket
import struct

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

sock, addr = server.accept()
long_a = sock.recv(4)  # 获取客户端发送的第一条数据,也就是打包后的长度,固定为4
long_b = struct.unpack('i', long_a)[0]  # 将打包后固定的长度转换成真实的信息长度,由于是元组,通过索引取值。
data = sock.recv(long_b)  # 接收客户端发送的第二条数据,也就是真实的信息,由于recv中的数据是准确的,所以不会出错
print(data.decode('utf8'))  # 打印真实信息,将二进制转换

sock.close()
server.close()

客户端代码

客户端代码

import socket
import struct

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

str_a = 'XWenXiang'.encode('utf8')  # 必须为二进制形式

str_a_long = struct.pack('i', len(str_a))  # 将真实信息的长度打包得到一个固定长度的数据
client.send(str_a_long)  # 将固定长度的数据发送给服务端
client.send(str_a)  # 再将真正的信息发送出去

client.close()

解决方式二
如果信息数据太大,我们可以将它组成字典,通过字典不仅能存放数据的长度还能存放额外的信息。
下面的示例是从服务端传文件给客户端。

服务端代码

服务端代码

import socket
import struct
import os
import json

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

sock, addr = server.accept()
while True:
    data_dict = {  # 创建字典
        'file_name': 'abc.txt',
        'file_desc': '文件abc',
        'file_size': os.path.getsize('a/abc.txt'),  # 返回 path 的大小,以字节为单位
    }
    # 1. 打包字典
    dict_json = json.dumps(data_dict)  # 将字典转换成json格式的字符串
    dict_bytes = dict_json.encode('utf8')  # 将json格式的字符串转换成字节型
    dict_package_header = struct.pack('i', len(dict_bytes))  # 打包字典获取固定数据
    # 2. 发送报头
    sock.send(dict_package_header)  # 发送固定长度的数据
    # 3. 发送字典
    sock.send(dict_bytes)  # 发送真实字典
    # 4. 循环发送文件
    with open('a/abc.txt', 'rb') as f:
        for line in f :
            sock.send(line)
            

客户端代码

客户端代码

import socket
import struct
import json

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

dict_hader_len = client.recv(4)  # 接收服务端发送的第一条数据也就是打包字典后的固定长度数据
dict_real_len = struct.unpack('i', dict_hader_len)[0]  # 解包获得真实字典的长度
dict_date_bytes = client.recv(dict_real_len)  # 根据真实的字典长度获取字典的数据
dict_data = json.loads(dict_date_bytes)  # 将json格式的字典准换成普通字典
print(dict_data)

# 循环获取文件
recv_size = 0
with open(dict_data.get('file_name'), 'wb') as f:
    while recv_size < dict_data.get('file_size'):
        data = client.recv(1024)
        recv_size += len(data)
        f.write(data)

举报

相关推荐

python-socket

Python模块-socket

python,socket通信

Python(三、SOCKET)

Python socket模块

python socket 学习

python socket笔记

0 条评论