socket网络编程python_python学习-socket网络编程

这篇博客介绍了如何使用Python实现一个多用户在线FTP程序,包括用户加密认证、磁盘配额、目录切换、文件上传下载及断点续传等功能。程序采用socket网络编程,并提供了详细的功能实现和代码结构说明。
摘要由CSDN通过智能技术生成

作业:多用户在线的FTP程序

要求:

用户加密认证

允许同时多用户登录

每个用户有自己的家目录 ,且只能访问自己的家目录

对用户进行磁盘配额,每个用户的可用空间不同

允许用户在ftp server上随意切换目录

允许用户查看当前目录下文件

允许上传和下载文件,保证文件一致性

文件传输过程中显示进度条

附加功能:支持文件的断点续传(仅下载)

README

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

### 功能实现

作业:开发一个支持多用户在线的FTP程序

要求:

用户加密认证

允许同时多用户登录

每个用户有自己的家目录 ,且只能访问自己的家目录

对用户进行磁盘配额,每个用户的可用空间不同

允许用户在ftp server上随意切换目录

允许用户查看当前目录下文件

允许上传和下载文件,保证文件一致性

文件传输过程中显示进度条

附加功能:支持文件的断点续传### 目录结构:

FTP

├── ftpclient#客户端程序

│ ├── __init__.py

│ └── ftpclient.py#客户端主程序

└── ftpserver #服务端程序

├── README.txt

├── ftpserver.py#服务端入口程序

├── conf #配置文件目录

│ ├── __init__.py

│ └── setting.py

├── modules#程序核心目录

│ ├── __init__.py

│ ├── auth_user.py#用户认证模块

│ └── sokect_server.py #sokectserver模块

├── database #用户数据库

│ ├── alex.db

│ ├── lzl.db

│ └── eric.db

├── home#用户宿主目录

│ ├── alex

│ ├── lzl

│ └── eric

└── log

├──__init__.py

└── log#待扩展....

### 功能实现

1、conf目录下settings.py模块记录可操作用户信息,根据用户信息生成用户字典和宿主目录,已经生成的不再新建2、每个用户的宿主目录磁盘空间配额默认为10M,可在settings.py模块里进行修改3、程序运行在windows系统上,程序要求全部实现,下面是具体命令操作4、切换目录:cd .. 返回上一级目录 cd dirname 进入dirname

用户登录后默认进入宿主目录,只可在宿主目录下随意切换5、创建目录:mkdir dirname

在当前目录下创建目录,如果目录存在则报错,不存在创建6、查看当前路径: pwd7、查看当前路径下的文件名和目录名: dir8、下载文件(可续传):get filename

①、服务端当前目录存在此文件,客户端不存在此文件,直接下载

②、服务端当前目录存在此文件,客户端存在此文件名,之前下载中断,文件可续传,进行续传

③、服务端当前目录存在此文件,客户端存在此文件名,大小与服务端一致,不下载9、上传文件:put filename

判断宿主目录磁盘空间是否够用,可以,上传文件;否则,报错### 状态码

400用户认证失败401命令不正确402文件不存在403创建文件已经存在404磁盘空间不够405不续传200用户认证成功201命令可以执行202磁盘空间够用203文件具有一致性205续传000系统交互码

别人家的作业:http://www.cnblogs.com/lianzhilei/p/5869205.html

README

ftp客户端

8f900a89c6347c561fdf2122f13be562.png ftpclient.py

ftp服务端

8f900a89c6347c561fdf2122f13be562.png ftpserver.py

conf配置文件

8f900a89c6347c561fdf2122f13be562.png settings.py

database用户数据库(系统初始化自动生成)

8f900a89c6347c561fdf2122f13be562.png lzl.db

8f900a89c6347c561fdf2122f13be562.png eric.db

8f900a89c6347c561fdf2122f13be562.png alex.db

modules目录

8f900a89c6347c561fdf2122f13be562.png auth_user.py

8f900a89c6347c561fdf2122f13be562.png sokect_server.py

一、运行原理

先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

2c1e244fc24f584835a2bf645bc807b7.png

socket()模块函数用法:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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

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

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

11 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。12 例如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() 创建一个与该套接字相关的文件

8f900a89c6347c561fdf2122f13be562.png socket实验推演流程

二、基于TCP的套接字

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

tcp服务端

48304ba5e6f9fe08f3fa1abda7d326ab.png

1 ss = socket() #创建服务器套接字

2 ss.bind() #把地址绑定到套接字

3 ss.listen() #监听链接

4 inf_loop: #服务器无限循环

5 cs = ss.accept() #接受客户端链接

6 comm_loop: #通讯循环

7 cs.recv()/cs.send() #对话(接收与发送)

8 cs.close() #关闭客户端套接字

9 ss.close() #关闭服务器套接字(可选)

48304ba5e6f9fe08f3fa1abda7d326ab.png

tcp客户端

1 cs = socket() # 创建客户套接字

2 cs.connect() # 尝试连接服务器

3 comm_loop: # 通讯循环

4 cs.send()/cs.recv() # 对话(发送/接收)

5 cs.close() # 关闭客户套接字

socket通信流程与打电话流程类似,我们就以打电话为例来实现一个low版的套接字通信

8f900a89c6347c561fdf2122f13be562.png 服务端

8f900a89c6347c561fdf2122f13be562.png 客户端

加上链接循环与通信循环

8f900a89c6347c561fdf2122f13be562.png 服务端改进版

8f900a89c6347c561fdf2122f13be562.png 客户端改进版

问题:在重启服务端时可能会遇到:地址正在使用,这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)

解决方法:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#加入一条socket配置,重用ip和端口

phone=socket(AF_INET,SOCK_STREAM)

phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加

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

View Code

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,

vi/etc/sysctl.conf

编辑文件,加入以下内容:

net.ipv4.tcp_syncookies= 1net.ipv4.tcp_tw_reuse= 1net.ipv4.tcp_tw_recycle= 1net.ipv4.tcp_fin_timeout= 30然后执行/sbin/sysctl -p 让参数生效。

net.ipv4.tcp_syncookies= 1表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse= 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle= 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

方法二

View Code

三、粘包现象

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

只有TCP有粘包现象,UDP永远不会粘包

两种情况下会发生粘包。

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

8f900a89c6347c561fdf2122f13be562.png 服务端

8f900a89c6347c561fdf2122f13be562.png 客户端

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

8f900a89c6347c561fdf2122f13be562.png 服务端

8f900a89c6347c561fdf2122f13be562.png 客户端

1.解决粘包的方法

struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

>>> struct.pack('i',1111111111111)

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

280427d306bf9d708de9fa8f906160a7.png

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 importjson,struct2 #假设通过客户端上传1T:1073741824000的文件a.txt

3

4 #为避免粘包,必须自定制报头

5 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值

6

7 #为了该报头能传送,需要序列化并且转为bytes

8 head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输

9

10 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节

11 head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

12

13 #客户端开始发送

14 conn.send(head_len_bytes) #先发报头的长度,4个bytes

15 conn.send(head_bytes) #再发报头的字节格式

16 conn.sendall(文件内容) #然后发真实内容的字节格式

17

18 #服务端开始接收

19 head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式

20 x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度

21

22 head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式

23 header=json.loads(json.dumps(header)) #提取报头

24

25 #最后根据报头的内容提取真实的数据,比如

26 real_data_len=s.recv(header['file_size'])27 s.recv(real_data_len)

用法

发送时:

先发报头长度

再编码报头内容然后发送

最后发真实内容

接收时:

先手报头长度,用struct取出来

根据取出的长度收取报头内容,然后解码,反序列化

从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

8f900a89c6347c561fdf2122f13be562.png 服务端:定制稍微复杂一点的报头

8f900a89c6347c561fdf2122f13be562.png 客户端

四、认证客户端的链接合法性

如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现

8f900a89c6347c561fdf2122f13be562.png 服务端

8f900a89c6347c561fdf2122f13be562.png 客户端(合法)

8f900a89c6347c561fdf2122f13be562.png 客户端(非法:不知道加密方式)

8f900a89c6347c561fdf2122f13be562.png 客户端(非法:不知道secret_key)

五、socketserver实现并发

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)

ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active

找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中

执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)

在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)

上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

源码分析总结:

基于tcp的socketserver我们自己定义的类中的

self.server即套接字对象

self.request即一个链接

self.client_address即客户端地址

基于udp的socketserver我们自己定义的类中的

self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b'adsf', )

self.client_address即客户端地址

8f900a89c6347c561fdf2122f13be562.png FtpServer

8f900a89c6347c561fdf2122f13be562.png FtpClient

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值