一.socket定义
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),基本上Socket 是任何一种计算机网络通讯中最基础的内容。例如当你在浏览器地址栏中输入 http://www.cnblogs.com/ 时,你会打开一个套接字,然后连接到 http://www.cnblogs.com/ 并读取响应的页面然后然后显示出来。而其他一些聊天客户端如 gtalk 和 skype 也是类似。任何网络通讯都是通过 Socket 来完成的。socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。
socket和file的区别:
1、file模块是针对某个指定文件进行【打开】【读写】【关闭】
2、socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
python提供了两个基本的socket模块:
1.socket提供了标准的BSD Socket API
2.SocketSverver提供了服务器的重心,可以简化网络服务器的开发
Python 官方关于 Socket 的函数请看 http://docs.python.org/library/socket.html
二.socket类型
套接字格式:socket(family,type[,protocal])使用给定的套接族,套接字类型,协议编号(默认为0)来创建套接字
socket.AF_UNIX:用于同一台机器上的进程通信(即本机通信)
socket.AF_INET:用于服务器和服务器之间的网络通信
socket.AF_INET6:基于IPV6方式的服务器和服务器之间的网络通信
socket.SOCK_STREAM:基于TCP式的socket通信
socket.SOCK_DGRAM:基于UDP的数据报式socket通信
socket.SOCK_RAM:原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的IPV4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头
socket.SOCK_SEQPACKET:可靠的连续数据包服务
三.Socket函数
- TCP发送数据时,已建立好TCP链接,所以不需要指定地址,而UDP是面向无连接的,每次发送都需要指定发送给谁。
- 服务器与客户端不能直接发送列表,元素,字典等带有数据类型的格式,发送的内容必须是字符串数据。
服务器端 Socket 函数
s.bind(address):将套接字绑定到地址,在AF_INET下,以tuple(host,port)方式传入,如s.bind((host,port))
s.listin(backlog):开始监听TCP传入连接,backlog指定在拒绝链接前,操作系统可以挂起的最大连接数,该值最少为大部分应用程序设为5就够用了
s.accept():接收TCP链接并返回(conn,address),其中conn是新的套接字对象,可以用来发送和接收数据,address是链接客户端的地址
客户端Socket函数
s.connect(address):链接到address处的套接字,一般address的格式为tuple(host,port),如果链接出错,则返回socket.error错误
s.connect_ex(address):功能与s.connect(address)相同,但成功返回0,失败返回error的值
公共 Socket 函数
s.recv(bufsize[, flag]):接受TCP套接字的数据,数据以字符串形式返回,buffsize指定要接受的最大数据量,flag提供有关消息的其他信息,通常可以忽略
s.send(string[, flag]):发送TCP数据,将字符串中的数据发送到链接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小
s.sendall(string[, flag]):完整发送TCP数据,将字符串中的数据发送到链接的套接字,但在返回之前尝试发送所有数据。成功返回None,失败则抛出异常
s.recvfrom(bufsize[, flag]):接受UDP套接字的数据u,与recv()类似,但返回值是tuple(data, address)。其中data是包含接受数据的字符串,address是发送数据的套接字地址
s.sendto(string[, flag], address):发送UDP数据,将数据发送到套接字,address形式为tuple(ipaddr, port),指定远程地址发送,返回值是发送的字节数
s.close():关闭套接字
s.getpeername():返回套接字的远程地址,返回值通常是一个tuple(ipaddr, port)
s.getsockname():返回套接字自己的地址,返回值通常是一个tuple(ipaddr, port)
s.setsockopt(level, optname, value):设置给定套接字选项的值
s.getsockopt(level, optname[, buflen]):返回套接字选项的值
s.settimeout(timeout):设置套接字操作的超时时间,timeout是一个浮点数,单位是秒,值为None则表示永远不会超时。一般超时期应在刚创建套接字时设置,因为他们可能用于连接的操作,如s.connect()
s.gettimeout():返回当前超时值,单位是秒,如果没有设置超时则返回None
s.fileno():返回套接字的文件描述
s.setblocking(flag):如果flag为0,则将套接字设置为非阻塞模式,否则将套接字设置为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile():创建一个与套接字相关的文件
四.Socket编程实例
建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。
客户端和服务端相互通信:
#!/usr/bin/python3 import socket HOST = 'localhost' PORT = 8001 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(5) print('Server start at: %s:%s' %(HOST, PORT)) print('wait for connection...') while True: conn, addr = s.accept() print('Connected by ',addr) while True: data = conn.recv(1024) print(data) conn.send(bytes("The server successfully receives your information.",encoding='utf-8')
#!/usr/bin/env python3 import socket HOST = 'localhost' PORT = 8001 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True: cmd = input("Please input msg:") s.send(bytes(cmd,encoding='utf-8')) data = s.recv(1024) print(data) if cmd == 'q':break
通过socket实现简单的ssh:
1 import socket,os,time 2 server=socket.socket() 3 server.bind(('localhost',9999)) 4 server.listen() 5 while True: 6 conn,addr = server.accept() 7 print("now conn",conn,addr) 8 while True: 9 print("等待新指令") 10 data = conn.recv(1024) 11 if not data: 12 print("客户端已断开") 13 break 14 print("执行指令",data) 15 cmd_res = os.popen(data.decode()).read() #接受和执行结果都是字符串 16 if len(cmd_res) ==0: 17 cmd_res="cmd has no output" 18 conn.send(str(len(cmd_res.encode())).encode("utf-8")) #注意中文解码成bytes再len统计,否则出现误差 19 #time.sleep(0.5) #此方法可以解决粘包问题,这种方法比较low 20 client_ack = conn.recv(1024) #接受客户端的回复,解决粘包 21 print("client_ack",client_ack.decode()) 22 conn.send(cmd_res.encode("utf-8")) 23 print("send done") 24 server.close()
1 import socket 2 client = socket.socket() 3 client.connect(('localhost',9999)) 4 while True: 5 cmd = input(">>>").strip() 6 if len(cmd) == 0:continue 7 client.send(cmd.encode('utf-8')) 8 cmd_res_size = client.recv(1024)#接受命令结果的长度 9 print("命令结果大小",cmd_res_size) 10 client.send("准备好接收,可以发了".encode('utf-8')) #给服务端发一条回复解决粘包问题 11 received_size = 0 12 received_data = b'' 13 while received_size != int(cmd_res_size.decode()): 14 data = client.recv(1024) 15 received_size += len(data) #接受到的可能小于1024,用len判断 16 received_data += data 17 else: 18 print("cmd_res receive done",received_size) 19 print(received_data.decode()) 20 client.close()
通过socket实现文件下载:
1 import socket,os,time,hashlib 2 server=socket.socket() 3 server.bind(('localhost',9999)) 4 server.listen() 5 while True: 6 conn,addr = server.accept() 7 print("now conn",conn,addr) 8 while True: 9 print("等待新指令") 10 data = conn.recv(1024) 11 if not data: 12 print("客户端已断开") 13 break 14 cmd,filename = data.decode().split() 15 print(filename) 16 if os.path.isfile(filename): 17 f = open(filename,"rb") 18 m = hashlib.md5() 19 file_size = os.stat(filename).st_size 20 conn.send(str(file_size).encode())#发送文件大小 21 conn.recv(1024) #wait for ack 22 for line in f: 23 m.update(line) 24 conn.send(line) 25 print("file md5",m.hexdigest()) 26 f.close() 27 conn.send(m.hexdigest().encode()) #send md5 28 print("send done") 29 server.close()
1 import socket,hashlib 2 client = socket.socket() 3 client.connect(('localhost',9999)) 4 while True: 5 cmd = input(">>>").strip() 6 if len(cmd) == 0:continue 7 if cmd.startswith("get"): 8 client.send(cmd.encode()) 9 server_response = client.recv(1024) 10 print("server response",server_response) 11 client.send(b"ready to recv file") 12 file_total_size = int(server_response.decode()) 13 received_size = 0 14 filename = cmd.split()[1] 15 f = open(filename + "new","wb") 16 m = hashlib.md5() 17 while received_size < file_total_size: 18 #判断是否是最后一次接受数据,主要解决粘包问题 19 if file_total_size - received_size > 1024: #要接受不止一次 20 size = 1024 21 else: #最后一次,有多少收多少 22 size = file_total_size - received_size 23 print("last receive",size) 24 data = client.recv(size) 25 received_size += len(data) 26 m.update(data) 27 f.write(data) 28 #print(file_total_size,received_size) 29 else: 30 new_file_md5 = m.hexdigest() 31 print("file recv done",file_total_size,received_size) 32 f.close() 33 service_file_md5 = client.recv(1024) 34 print("server file md5",service_file_md5) 35 print("client file md5",new_file_md5)
五.SocketServer
The socketserver
module simplifies the task of writing network servers.
socketserver一共有这么几种类型:
This uses the Internet TCP protocol, which provides for continuous streams of data between the client and server.
class socketserver.TCPServer(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.UDPServer(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
.
class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True) class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)
There are five classes in an inheritance diagram, four of which represent synchronous servers of four types:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
创建一个socketserver 至少分以下几步:
- First, you must create a request handler class by subclassing the
BaseRequestHandler
class and overriding itshandle()
method; this method will process incoming requests. - Second, you must instantiate one of the server classes, passing it the server’s address and the request handler class.
- Then call the
handle_request()
orserve_forever()
method of the server object to process one or many requests. - Finally, call
server_close()
to close the socket.1 import socket 2 client = socket.socket() 3 client.connect(('localhost',9999)) 4 while True: 5 cmd = input(">>>:").strip() 6 if len(cmd) == 0:continue 7 client.send(cmd.encode('utf-8')) 8 data = client.recv(10240) 9 print("recv",data.decode()) 10 client.clos
基本socketserver简单代码:
1 import socketserver 2 class MyTCPHandle(socketserver.BaseRequestHandler): 3 def handle(self): #接受处理客户端的请求 4 while True: 5 try: 6 self.data = self.request.recv(1024).strip() 7 print("{} wrote".format(self.client_address[0])) 8 print(self.data) 9 self.request.send(self.data.upper()) 10 except ConnectionResetError as e: 11 print("err",e) 12 break 13 14 if __name__ == "__main__": 15 HOST,PORT = "localhost",9999 16 server = socketserver.TCPServer((HOST,PORT),MyTCPHandle) 17 server.serve_forever()
1 import socket 2 client = socket.socket() 3 client.connect(('localhost',9999)) 4 while True: 5 cmd = input(">>>:").strip() 6 if len(cmd) == 0:continue 7 client.send(cmd.encode('utf-8')) 8 data = client.recv(10240) 9 print("recv",data.decode()) 10 client.close()
实现多并发:客户端每连进一个来,服务器端就会分配一个新的线程来处理这个客户端的请求。
server
=
socketserver.TCPServer((HOST, PORT), MyTCPHandler)
-----》 改为:
server
=
socketserver.TCPServer((HOST, PORT), MyTCPHandler)