python socket server accpet 时间_python自动化编程-第八天 socketserver

python自动化编程-第八天 socketserver

一、复习Socket编程

通讯协议图

2019-01-27-%E7%A7%91%E6%9D%A5%E7%BD%91%E7%BB%9C%E9%80%9A%E8%AE%AF%E5%8D%8F%E8%AE%AE%E5%9B%BE.jpg

socket参数

Family address

AF.INET ipv4

AF.INET6

AF.UNIX LOCAL

Socket protocol type

Sock.SOCK_STREAM TCP/ip

Sock.SOCK_DGRAM UDP

socket方法

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6, AF_UNIX, AF_CAN or AF_RDS. The socket type should beSOCK_STREAM (the default), SOCK_DGRAM, SOCK_RAW or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN the protocol should be one of CAN_RAW or CAN_BCM. If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd(), fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().

socket.socketpair([family[, type[, proto]]])

Build a pair of connected socket objects using the given address family, socket type, and protocol number. Address family, socket type, and protocol number are as for the socket() function above. The default family is AF_UNIX if defined on the platform; otherwise, the default is AF_INET.

socket.create_connection(address[, timeout[, source_address]])

Connect to a TCP service listening on the Internet address (a 2-tuple (host, port)), and return the socket object. This is a higher-level function than socket.connect(): if host is a non-numeric hostname, it will try to resolve it for both AF_INET and AF_INET6, and then try to connect to all possible addresses in turn until a connection succeeds. This makes it easy to write clients that are compatible to both IPv4 and IPv6.

Passing the optional timeout parameter will set the timeout on the socket instance before attempting to connect. If no timeout is supplied, the global default timeout setting returned by getdefaulttimeout() is used.

If supplied, source_address must be a 2-tuple (host, port) for the socket to bind to as its source address before connecting. If host or port are ‘’ or 0 respectively the OS default behavior will be used.

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

获取要连接的对端主机地址

sk.bind(address)

s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5

这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

关闭套接字

sk.recv(bufsize[,flag])

接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

套接字的文件描述符

socket.sendfile(file, offset=0, count=None)

发送文件 ,但目前多数情况下并无什么卵用。

简单ssh

服务端:

import socket

server = Socket.socket(AF.INET,sock.SOCK_STREAM)

server.bind((localhost,9999))

server.listen()

while True: #为了能够接受多个客户端,但是同时只能够为一个客户端提供服务;

Conn.address = socket.accept() #阻塞

while True:

Print('new conn',addr)

Data = Conn.recv(1024) #官方建议不超过8192 ,recv默认是阻塞,

If not data:

Break #此时客户一断开,服务端就死循环了,conn.recv收到的就是空数据;为了避免死循环,

print(data)

conn.send(data.upper())

socket中recv函数的最大不要超过8192,

客户端:

Client = socket.socket()

client.connect(serverIP,9999)

Client.send(data)

Client.recv(1024)

三、SSH服务器 --- 发送的数据大于一次能够接收的数据量时

服务器端

import socket,os

server = socket.socket()

server.bind(('localhost',9999))

server.listen()

while True:

conn,addr = server.accept()

print('new conn',addr)

while True:

print('等待新指令')

data = conn.recv(1024)

if not data:

print('客户端已断开')

break

print('执行指令:',data)

cmd_res = os.popen(data.decode()).read() #接受字符串,执行结果也是字符串

print('before send',len(cmd_res))

if len(cmd_res) == 0:

cmd_res = "cmd has no output..."

conn.send(str(len(cmd_res.encode())).encode()) #先发大小给客户端

#此处的cmd_res.encode() 表示由于中文的原因,默认是一个字符,但是编程ascii时,就是3个字符了,而server端在发送时,也是使用bytes类型

#因此需要统一成bytes类型的来计算长度;

# time.sleep(0.5) # 笨办法,0.5秒基本上没有什么问题

client_ack = conn.recv(1024) #wait client to cinfirm 防止粘包

print('ack from client:',client_ack)

conn.send(cmd_res.encode())

print('send done')

conn.close()

socket发送数据的步骤

1、接收的

2、socket在send时,首先把数据存到缓冲区,若缓冲区满了,则肯定会发送,此时系统调用接口自动send;

3、若缓冲区没满,但是超时了也会send,此时就是手动send,相当于手动触发send,强制超时,不等缓冲区满;

需要注意的是在计算数据大小时,如果有中文,则需要先将数据转码成ascii在计算大小。否则数据大小就会不一致。

客户端

import socket

client = socket.socket()

client.connect(('localhost',9999))

while True:

cmd = input('>>:').strip()

if len(cmd) == 0:

continue

client.send(cmd.encode())

cmd_res_size = client.recv(1024) #接收命令结果的长度

print('命令结果大小:',cmd_res_size)

client.send('准备好接收了,loser可以发了'.encode('utf-8'))

received_size = 0

received_data = b""

while received_size < int(cmd_res_size.decode()):

data = client.recv(1024)

received_data += len(data) #每次收到的有可能小于1024,所以必须用len判断

# print(data.decode())

# print(received_size)

received_data += data

else:

print('cmd res receive done....',received_size)

print(received_data.decode())

# print(cmd_res.decode())

client.close()

客户端需要注意的是,在接收数据计算大小时,一定要按照实际接收的大小来计算,否则可能出错。

四、FTP服务器 -- 发送大文件,一次接收的数据小于文件数据的大小;

设计思路:

1、读取文件名

2、检测文件是否存在

3、打开文件,边读边发--->否则占用内存太大

4、检测文件大小

5、发送文件大小给客户端,

6、等待客户端确认

7、开始边读边发数据

8、发送md5

代码如下:

服务器端

import socket,os,hashlib

server = socket.socket()

server.bind(('localhost',9999))

server.listen()

while True:

conn,addr = server.accept()

print('new conn',addr)

while True:

print('等待新指令')

data = conn.recv(1024)

if not data:

print('客户端已断开')

break

cmd,filename = data.decode().split()

print(filename)

if os.path.isfile(filename): #如果是目录,也是存在的,因此这里要用isfile

f = open(filename,'rb')

m = hashlib.md5()

file_size = os.stat(filename).st_size

# file_size = os.path.getsize(filename) #调用的就是stat().st_size

conn.send(str(file_size).encode()) #send file size

conn.recv(1024) #wait for ack 防止粘包

for line in f:

m.update(line)

conn.send(line)

print('file md5',m.hexdigest())

f.close()

conn.send(m.hexdigest().encode())

print('send done')

conn.close()

客户端

import socket,hashlib

client = socket.socket()

client.connect(('localhost',9999))

while True:

cmd = input('>>:').strip()

if len(cmd) == 0:

continue

if cmd.startswith('get'):

client.send(cmd.encode())

server_response = client.recv(1024)

print('server response',server_response)

client.send(b'ready to recv file')

file_total_size = int(server_response.decode())

received_size = 0

filename = cmd.split()[1]

f = open(filename + ".new",'wb')

m = hashlib.md5()

while received_size < file_total_size:

if file_total_size - received_size > 1024: # 要收不止一次

size = 1024

else: #最后一次,剩多少收多少

size = file_total_size - received_size

print('last received:',size)

data = client.recv(size)

received_size += len(data)

m.update(data)

f.write(data)

# print(file_total_size,received_size)

else:

new_file_md5 = m.hexdigest()

print("file recv done",received_size,file_total_size)

f.close()

server_file_md5 = client.recv(1024)

print('server file md5:',server_file_md5)

print('client file md5:',new_file_md5)

client.close()

此时FTP只能实现同时为一个用户提供服务,其他的用户只能接入,而服务器端无法提供服务;这时由于socket没有办法处理并发连接;但是SocketServer可以实现,

需要注意的是在发送文件时,为了避免内存空间浪费,建议使用循环的方式发送文件,服务器端,边读边发,客户端,边收边写;这样每次内存中只会保留一行数据,节约内存空间。

五、粘包

什么是粘包?

粘包就是在连续两次的send时,缓冲区会把两个send,结合成一条来发送,把两次的强制操作当做一次来操作,这时接收端就会发现接收的数据时粘在一次的;这样就是粘包了

以SSH的代码为例来说明:

2019-01-27-15262664868141.jpg

服务器端:

在发送了数据大小后,紧接着就发送了数据,这时,在客户端收到的数据就是数据大小和数据是在一起的,导致无法解读数据内容;

解决方法:

1、time.sleep(0.5),0.5秒一般情况都可以使得send超时。最笨方法,一般不使用,这样的结果就是程序变慢。

2、就是在两次send之间加上一次recv,阻塞主下一次的send;让客户端发送,确认收到第一次的send即可;这样两次send就会被分割

再看下FTP的代码:

2019-01-27-15262672854255.jpg

服务器端:

在发送文件大小后,紧接着就发送文件数据,并且在此之后,又发送了文件的md5码,这就是连续3次的send操作,为了防止粘包,

解决方法:

1、就是每两次send之间加上recv,

2、服务器在发送文件数据后,又发送文件的md5码时,这个可以在客户端解决;由于文件大小已知道,因此客户端在接收时,只接收与文件大小相同的数据即可;

六、SocketServer

创建一个SocketServer类

创建一个socketserver的步骤:

1、创建BaseRequestHandler 类的子类,并重构handle()方法用于处理请求处理程序类,handle()方法处理传入的请求;

必须通过对 BaseRequestHandler 类进行子分类并重写其 handle() 方法来创建请求处理程序类,此方法将处理传入的请求。

2、必须实例化一个tcpserver,并且传递server ip 和 上面创建的请求处理类,给这个tcpserver;

3、然后调用服务器对象的 handle_request() 或 serve_forever() 方法, 以处理一个或多个请求。

Server.handle_request() #只处理一个请求

Server.server_forever() # 处理多个请求,永远执行

4、最后, 调用 server_close() 以关闭套接字。

方法介绍:

The socketserver module simplifies the task of writing network servers.

There are four basic concrete server classes:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server. If bind_and_activate is true, the constructor automatically attempts to invoke server_bind() andserver_activate(). The other parameters are passed to the BaseServer base class.

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

This uses datagrams, which are discrete packets of information that may arrive out of order or be lost while in transit. The parameters are the same as for TCPServer.

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)

class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)

These more infrequently used classes are similar to the TCP and UDP classes, but use Unix domain sockets; they’re not available on non-Unix platforms. The parameters are the same as for TCPServer.

These four classes process requests synchronously; each request must be completed before the next request can be started. This isn’t suitable if each request takes a long time to complete, because it requires a lot of computation, or because it returns a lot of data which the client is slow to process. The solution is to create a separate process or thread to handle each request; the ForkingMixIn and ThreadingMixIn mix-in classes can be used to support asynchronous behaviour.

There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:

+------------+

| BaseServer |

+------------+

|

v

+-----------+ +------------------+

| TCPServer |------->| UnixStreamServer |

+-----------+ +------------------+

|

v

+-----------+ +--------------------+

| UDPServer |------->| UnixDatagramServer |

+-----------+ +--------------------+

Note that UnixDatagramServer derives from UDPServer, not from UnixStreamServer — the only difference between an IP and a Unix stream server is the address family, which is simply repeated in both Unix server classes.

class socketserver.ForkingMixIn

class socketserver.ThreadingMixIn

Forking and threading versions of each type of server can be created using these mix-in classes. For instance, ThreadingUDPServer is created as follows:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):

pass

The mix-in class comes first, since it overrides a method defined in UDPServer. Setting the various attributes also changes the behavior of the underlying server mechanism.

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer

class socketserver.ThreadingUDPServer

These classes are pre-defined using the mix-in classes.

Request Handler Objects

class socketserver.BaseRequestHandler

This is the superclass of all request handler objects. It defines the interface, given below. A concrete request handler subclass must define a new handle() method, and can override any of the other methods. A new instance of the subclass is created for each request.

setup()

Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

handle()

This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.

finish()

Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.

FTP多线程实例

服务器代码

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

def handle(self): #与客户端所有的交互都在handle中完成的

while True:

try:

self.data = self.request.recv(1024).strip()

print("{} wrote:".format(self.client_address[0]))

print(self.data)

# if not self.data: #服务端如果收到的数据为空,则表示客户端断开了

# print(self.client_address,'断开了')

# break

self.request.sendall(self.data.upper())

except ConnectionResetError as e: #表示客户端断开了,不用再写if not data了,socketserver中将客户端断开做成异常了

print('error',e)

break

if __name__ == '__main__':

HOST,PORT = 'localhost',9999

# server = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #单线程处理,

server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) #多并发处理,多线程

# server = socketserver.ForkingTCPServer((HOST,PORT),MyTCPHandler) # 多进程,windows不能使用这个,但是linux上可以使用

server.serve_forever()

在socket编程中,若服务器端接收的数据为空,则表示客户端断开连接,在客户端如果发送数据为空,则需要continue。

在socketserver编程中,所有的连接请求处理都在自动类中的handle方法中;

七、socket/socketserver的 address already in use 问题

在socket模块中服务器地址重用:

server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

在socketserver上:启动线程之前设置

socketserver.TCPServer.allow_reuse_address = True # 设置地址重用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值