目录:
1、Socket语法及相关
2、SocketServer实现多并发
Socket语法及相关
socket概念
socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。
建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。
网络套接字是跨计算机网络的连接的端点。 今天,计算机之间的大多数通信都基于互联网协议; 因此大多数网络套接字都是Internet套接字。 更准确地说,套接字是一个句柄(抽象引用),
本地程序可以将其传递给网络应用程序编程接口(API)以使用该连接,例如“在此套接字上发送此数据”。 套接字在内部通常只是整数,它标识要使用的连接。
例如,发送“Hello,world!” 通过TCP到地址为1.2.3.4的主机的端口80,可以获得一个套接字,将其连接到远程主机,发送字符串,然后关闭套接字:
Socket socket = getSocket(type = "TCP")
connect(socket, address = "1.2.3.4", port = "80")
send(socket, "Hello, world!")
close(socket)
套接字API是一种应用程序编程接口(API),通常由操作系统提供,允许应用程序控制和使用网络套接字。 Internet套接字API通常基于Berkeley套接字标准。在Berkeley套接字标准中,套接字是文件描述符(文件句柄)的一种形式,
由于Unix哲学“一切都是文件”,以及套接字和文件之间的类比:你可以读,写,打开和关闭都。在实践中,差异意味着类比是紧张的,而一个在套接字上使用不同的接口(发送和接收)。
在进程间通信中,每个端口通常都有自己的套接字,但这些套接字可能使用不同的API:它们由网络协议抽象。
套接字地址是IP地址和端口号的组合,很像电话连接的一端是电话号码和特定分机的组合。套接字不需要有地址(例如仅用于发送数据),但如果程序将套接字绑定到地址,则套接字可用于接收发送到该地址的数据。
基于此地址,Internet套接字将传入的数据包传递到适当的应用程序进程或线程。
Socket Families(地址簇)
socket.
AF_UNIX unix本机进程间通信
socket.
AF_INET IPV4
socket.
AF_INET6 IPV6
这些常量表示用于socket()的第一个参数的地址(和协议)系列。 如果未定义AF_UNIX常量,则不支持此协议。 根据系统的不同,可能会有更多常量可用
Socket Types
socket.
SOCK_STREAM #for tcp
socket.
SOCK_DGRAM #for udp
socket.
SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.
SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
这些常量表示套接字类型,用于socket()的第二个参数。 根据系统的不同,可能会有更多常量可用。 (只有SOCK_STREAM和SOCK_DGRAM似乎通常很有用。)
Socket 方法
socket.
socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
使用给定的地址系列,套接字类型和协议号创建一个新套接字。 地址族应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 套接字类型应该是SOCK_STREAM(默认值),
SOCK_DGRAM,SOCK_RAW或者其他SOCK_常量之一。 协议号通常为零并且可以省略,或者在地址族是AF_CAN的情况下,协议应该是CAN_RAW或CAN_BCM之一。
如果指定了fileno,则忽略其他参数,从而返回具有指定文件描述符的套接字。 与socket.fromfd()不同,fileno将返回相同的套接字而不是重复。 这可能有助于使用socket.close()关闭分离的套接字
socket.
socketpair
([family[, type[, proto]]])
使用给定的地址系列,套接字类型和协议编号构建一对连接的套接字对象。 地址族,套接字类型和协议号与上面的socket()函数相同。 如果在平台上定义,则默认系列为AF_UNIX; 否则,默认为AF_INET。
socket.
create_connection
(address[, timeout[, source_address]])
连接到侦听Internet地址(2元组(主机,端口))的TCP服务,并返回套接字对象。 这是一个比socket.connect()更高级的函数:如果host是非数字主机名,它将尝试为AF_INET和AF_INET6解析它,
然后尝试依次连接到所有可能的地址,直到连接成功。 这样可以轻松编写与IPv4和IPv6兼容的客户端。
传递可选的timeout参数将在尝试连接之前设置套接字实例上的超时。 如果未提供超时,则使用getdefaulttimeout()返回的全局默认超时设置。
如果提供,则source_address必须是要连接的套接字的2元组(主机,端口)作为其源地址才能连接。 如果主机或端口分别为'或0,则将使用OS默认行为。
socket.
getaddrinfo
(host, port, family=0, type=0, proto=0, flags=0) #获取要连接的对端主机地址
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 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)
socket实例1
#先启动服务端,再启动客户端
#服务器端
import socket
server = socket.socket()#声明socket类型,同时生成socket连接对象
server.bind(('localhost',6969))#绑定要监听的端口
server.listen()#开始监听
print("我要等电话了:")
conn,addr = server.accept()#等电话打进来
#conn是客户端连接过来而在服务器端为其生成的一个连接实例
print(conn,addr)
print("电话来了")
data = conn.recv(1024)
print("recv:",data.decode())
conn.send(data.upper())
server.close()
#客户端
import socket
client = socket.socket()#声明socket类型,同时生成socket连接对象
client.connect(('localhost',6969))
#client.send(b'hello python!')#b只能发送Ascii对应的字符
client.send('鲁班大师智障250,oh,yeah!'.encode('utf-8'))
data = client.recv(1024)#接收返回值
print("recv:",data.decode())
client.close()#关闭客户端
运行结果:
#客户端
recv: 鲁班大师智障250,OH,YEAH!
#服务器端
我要等电话了:
<socket.socket fd=584, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr=('127.0.0.1', 51079)> ('127.0.0.1', 51079)
电话来了
recv: 鲁班大师智障250,oh,yeah!
socket实例2
#Author:Yun ''' 缓冲区: 1、当缓冲区满了后自动发 2、超时后触发发送 ''' import socket, os,time server = socket.socket() server.bind(('localhost', 998)) server.listen() while True: print("等待新的客户端:") conn,addr = server.accept() print("New conn:",addr) try: while True: data = conn.recv(1024) print("执行指令:",data) res_cmd = os.popen(data.decode()).read()#执行结果是字符串,接受结果也是字符串 if len(res_cmd) == 0: res_cmd = "cmd has no output!" conn.send(str(len(res_cmd.encode())).encode('utf-8'))#返回要出输给客户端的文件大小 client_ack = conn.recv(1024)#wait client to confirm(确认) conn.send(res_cmd.encode('utf-8'))#给客户端传输数据 print("send done!") except ConnectionResetError as e: print("客户端已关闭:",e) server.close()
#Author:Yun ''' 输入:dir 服务器会返回当前目录下的文件 ipconfig 服务器会返回计算机ip的配置信息 ''' import socket client = socket.socket() client.connect(('localhost',998)) while True: cmd = input('>>:').strip() if len(cmd) == 0:continue client.send(cmd.encode('utf-8')) res_cmd_size = client.recv(1024)#获取服务器返回数据的总大小 print("命令结果大小:",int(res_cmd_size.decode())) client.send("准备好接受了,可以发送了!".encode('utf-8')) recived_size = 0 recived_data = b'' while recived_size < int(res_cmd_size.decode()): cmd_res = client.recv(1024) recived_size += len(cmd_res)#每次接收到的有可能小于1024,必须要用len()判断 recived_data += cmd_res else: print("cmd res recive done...",recived_size) print(recived_data.decode()) client.close()
socket实例3
2、SocketServer实现多并发
SocketServer
socketserver模块简化了编写网络服务器的任务。
有四种基本的具体服务器类:
-
class
socketserver.
TCPServer
(server_address, RequestHandlerClass, bind_and_activate=True)
它使用Internet TCP协议,该协议在客户端和服务器之间提供连续的数据流。 如果bind_and_activate为true,则构造函数会自动尝试调用server_bind()和server_activate()。 其他参数将传递给BaseServer基类。
-
class
socketserver.
UDPServer
(server_address, RequestHandlerClass, bind_and_activate=True)
这使用数据报,这些数据报是可能无序到达或在传输过程中丢失的离散信息包。 参数与TCPServer相同。
-
class
socketserver.
UnixStreamServer
(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.
UnixDatagramServer
(server_address, RequestHandlerClass,bind_and_activate=True)
这些不经常使用的类类似于TCP和UDP类,但使用Unix域套接字; 它们不适用于非Unix平台。 参数与TCPServer相同。这四个类同步处理请求; 必须在下一个请求开始之前完成每个请求。 如果每个请求需要很长时间才能完成,这是不合适的,因为它需要大量计算,或者因为它返回了客户端处理速度慢的大量数据。 解决方案是创建一个单独的进程或线程来处理每个请求; ForkingMixIn和ThreadingMixIn混合类可用于支持异步行为。
继承图中有五个类,其中四个代表四种类型的同步服务器:
请注意,UnixDatagramServer派生自UDPServer,而不是来自UnixStreamServer - IP和Unix流服务器之间的唯一区别是地址族,这在两个Unix服务器类中都是重复的。
-
class
socketserver.
ForkingMixIn
class socketserver.
ThreadingMixIn
可以使用这些混合类创建每种类型服务器的分叉和线程版本。 例如,ThreadingUDPServer创建如下:
class ThreadingUDPServer(ThreadingMixIn, UDPServer):
pass
-
class
socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
这些类是使用混合类预定义的。
Request Handler Objects
class socketserver.BaseRequestHandler
这是所有请求处理程序对象的超类。 它定义了接口,如下所示。 具体的请求处理程序子类必须定义新的handle()方法,并且可以覆盖任何其他方法。 为每个请求创建子类的新实例。
setup
()
在handle()方法之前调用以执行所需的任何初始化操作。 默认实现什么都不做。
handle
()
此功能必须完成服务请求所需的所有工作。 默认实现什么都不做。 有几个实例属性可用; 请求以self.request的形式提供; 客户端地址为self.client_address; 并且服务器实例为self.server,以防它需要访问每服务器信息。
self.request的类型对于数据报或流服务是不同的。 对于流服务,self.request是一个套接字对象; 对于数据报服务,self.request是一对字符串和套接字。
finish
()
在handle()方法之后调用以执行所需的任何清理操作。 默认实现什么都不做。 如果setup()引发异常,则不会调用此函数。
socketserver.TCPServer
Example
server side
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
我们服务器的请求处理程序类。
每次连接到服务器时都会实例化一次,并且必须
覆盖handle()方法以实现与的通信
客户。
"""
def handle(self):
# self.request是连接到客户端的TCP套接字
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# 只是发回相同的数据,但大写
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# 创建服务器,绑定到端口9999上的localhost
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
# 激活服务器; 这会继续运行直到你
# 使用Ctrl-C中断程序
server.serve_forever()
client side
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
finally:
sock.close()
print("Sent: {}".format(data))
print("Received: {}".format(received))
上面这个例子你会发现,依然不能实现多并发,只需在server端做一下更改就可以了
把服务器端的
server
=
socketserver.TCPServer((HOST, PORT), MyTCPHandler)
server
=
socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
#Author:Yun import socketserver import json,os,sys,time class MyTCPHandler(socketserver.BaseRequestHandler): def put(self,*args): ''' 接收客户端文件 :return: ''' cmd_dic = args[0] print('cmd_dic',cmd_dic) filename = cmd_dic["filename"] file_size = cmd_dic["size"] if os.path.isfile(filename): f = open(filename + '.new','wb') else: f = open(filename,'wb') self.request.send(b'200 ok') received_size = 0 nn = [] h =0 while received_size < file_size: self.data = self.request.recv(1024) f.write(self.data) received_size += len(self.data) #进度显示 n = int(received_size / file_size * 100) if h == n:continue print(n,'%') h = n else: print("\033[33;1mfile [%s] has uploaded....\033[;0m"%filename) def get(self,*args): print(args[0]) #获取客户端要下载的文件名 file_name =args[0]['filename'] if os.path.isfile(file_name): file_size = os.stat(file_name).st_size self.request.send(str(file_size).encode('utf-8')) #防止粘包 self.request.recv(1024)#等待客户端确认 while True: f = open(file_name,'rb') for line in f: self.request.send(line) else: f.close() break print('file [%s] send done!'%file_name) def handle(self): while True: try: print('等待新命令:') self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) cmd_dict = json.loads(self.data.decode()) action = cmd_dict["action"] if hasattr(self,action): func = getattr(self,action) func(cmd_dict) except ConnectionResetError as e: print("Error:",e) break if __name__ == '__main__': HOST,PORT = 'localhost',9898 sever = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) sever.serve_forever()
#Author:Yun import socket import os,json class FtpClient(object): def __init__(self): self.client = socket.socket() def help(self): msg = ''' ls pwd cd ../.. get filename put filename ''' def connect(self,ip,port): self.client.connect((ip,port)) def interactive(self): while True: cmd = input((">>>:")).strip() if len(cmd) == 0:continue cmd_str = cmd.split()[0] #print(cmd_str) if hasattr(self,'cmd_%s'%cmd_str): func = getattr(self,'cmd_%s'%cmd_str) func(cmd) else: self.help() def cmd_put(self,*args): #print(args[0]) cmd_split = args[0].split()#返回分割后的字符串列表,默认以空格为分割符 print('cmd_split:',cmd_split) if len(cmd_split) >1:#如果长度大于1怎说明命令后面还有文件名 filename = cmd_split[1] #print(filename) if os.path.isfile(filename): file_size = os.stat(filename).st_size msg_dic = { "action":'put', "filename":filename, "size":file_size, "overridden":True } #print(msg_dic) self.client.send(json.dumps(msg_dic).encode('utf-8')) #防止粘包 server_response = self.client.recv(1024) f = open(filename,'rb') while True: for line in f: self.client.send(line) else: print("\033[32;1mfile update success!\033[;0m") f.close() break else: print(filename,"\033[31;1mis not exist!\033[;0m") def cmd_get(self,*args): print(args[0]) cmd_split = args[0].split()#返回分割后的字符串列表,默认以空格为分割符 #获取文件名 if len(cmd_split) > 1:#如果长度大于1,说明包含文件名 filename = cmd_split[1] msg_dic = { "action": 'get', "filename": filename, "overridden": True } print(msg_dic) # 向服务器发出请求 self.client.send(json.dumps(msg_dic).encode('utf-8')) # 获取文件大小 server_response = self.client.recv(1024) file_total_size = int(server_response.decode()) print('file_total_size:', file_total_size) # 防止粘包 self.client.send(b'Ready,you can send it. ') f = open(filename + '.in_client', 'wb') received_size = 0 h = 0 while received_size < file_total_size: if (file_total_size - received_size) > 1024: size = 1024 else: size = file_total_size - received_size data = self.client.recv(size) f.write(data) received_size += len(data) n = int(received_size / file_total_size * 100) if h == n: continue print(n, '%') h = n else: print('received_size:', received_size) f.close() print("your file download success!") ftp = FtpClient() ftp.connect('localhost',9898) ftp.interactive()