Socket网络编程
一、什么是Socket
二、Socket参数介绍
socket.
socket
(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) Socket Families(地址簇)
socket.
AF_UNIX unix本机进程间通信
socket.
AF_INET IPV4
socket.
AF_INET6 IPV6
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_SEQPACKET #废弃了
剩下的参数proto,fileno一般我们不用管。
三、socket实例
最简单的socket实例
import socket client = socket.socket() client.connect(('localhost',9999)) client.send(b'hello world') data = client.recv(1024) print('recv',data) client.close()
import socket server = socket.socket() server.bind(('localhost',9999)) server.listen() conn,addr = server.accept() data = conn.recv(1024) print('recv:',data) conn.send(data.upper()) server.close()
上面的代码的有一个问题, 就是SocketServer.py运行起来后, 接收了一次客户端的data就退出了。。。, 但实际场景中,一个连接建立起来后,可能要进行多次往返的通信。
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>请输入要发送的内容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res = client.recv(1024) print(res.decode()) client.close()
import socket server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要开始等电话了') conn,addr = server.accept() print('电话来了') while True: data = conn.recv(1024) print(data) if not data: print('client has lost') break conn.send(data.upper()) conn.close()
但是上面这种写法还是有缺陷的,虽然这种写法可以实现客户端和服务器端多次交互了,但是只要客户端断开了,服务器端也会断开,因为只有一个while循环,客户端一断开,服务器端收不到数据,就会直接break跳出循环,这样程序就结束了。这还不是我们想要的结果,我们想要的是,客户端断开了,服务器端可以接收下一个连进来的连接,然后和下一个连进来的客户端再次的进行多次的交互。
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>请输入要发送的内容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res_size = client.recv(1024) print(res_size) res_data = b'' recv_size = 0 while recv_size < int(res_size.decode()): data = client.recv(1024) recv_size += len(data) res_data += data else: print(recv_size) print(res_data.decode()) print(res_data) client.close()
import socket import os server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要开始等电话了') while True: conn,addr = server.accept() print('电话来了') while True: data = conn.recv(1024) if not data: print('client has lost') break res = os.popen(data.decode()).read() conn.send(str(len(res.encode())).encode()) conn.send(res.encode()) print('send done') conn.close()
输出结果:
看程序执行报错了, 我在客户端本想只接服务器端命令的执行结果大小,但实际上却连命令结果也跟着接收了一部分。
这里就引入了一个重要的概念,“粘包”, 即服务器端你调用时send 2次,但你send调用时,数据其实并没有立刻被发送给客户端,而是放到了系统的socket发送缓冲区里,等缓冲区满了、或者数据等待超时了,数据才会被send到客户端,这样就把好几次的小数据拼成一个大数据,统一发送到客户端了,这么做的目地是为了提高io利用效率,一次性发送总比连发好几次效率高。 但也带来一个问题,就是“粘包”,即2次或多次的数据粘在了一起统一发送了。就是我们上面看到的情况 。
我们在这里必须要想办法把粘包分开, 因为不分开,你就没办法取出来服务器端返回的命令执行结果的大小。首先你是没办法让缓冲区强制刷新把数据发给客户端的。 你能做的,只有一个。就是让缓冲区超时,超时了,系统就不会等缓冲区满了,会直接把数据发走,那么如何让缓冲区超时呢?
答案就是:
- time.sleep(0.5),经多次测试,让服务器程序sleep 至少0.5就会造成缓冲区超时。虽然我们觉得0.5s不多,但是对数据实时要求高的业务场景,比如股票交易等用这种方法肯定是不行的。
- 不用sleep,服务器端每发送一个数据给客户端,就立刻等待客户端进行回应,即调用 conn.recv(1024), 由于recv在接收不到数据时是阻塞的,这样就会造成,服务器端接收不到客户端的响应,就不会执行后面的conn.sendall(命令结果)的指令,收到客户端响应后,再发送命令结果时,缓冲区就已经被清空了,因为上一次的数据已经被强制发到客户端了。看下面代码实现。
import socket client = socket.socket() client.connect(('localhost',8888)) while True: data = input('>>请输入要发送的内容:') if len(data) == 0: continue client.send(data.encode('utf-8')) res_size = client.recv(1024) client.send(b'ok') print(res_size) res_data = b'' recv_size = 0 while recv_size < int(res_size.decode()): data = client.recv(1024) recv_size += len(data) res_data += data else: print(recv_size) print(res_data.decode()) client.close()
import socket import os server = socket.socket() server.bind(('localhost',8888)) server.listen(5) print('我要开始等电话了') while True: conn,addr = server.accept() print('电话来了') while True: data = conn.recv(1024) if not data: print('client has lost') break res = os.popen(data.decode()).read() conn.send(str(len(res.encode())).encode()) conn.recv(1024) conn.sendall(res.encode()) print('send done') conn.close()
SocketServer
socketserver一共有这么几种类型
1
|
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.
1
|
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
1
2
|
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 its handle() 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() or serve_forever() method of the server object to process one or many requests.
- Finally, call server_close() to close the socket.
最基本的socketserver代码实现
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): while True: try: self.data = self.request.recv(1024).strip() print(self.data) self.request.send(self.data.upper()) except Exception as e: print('链接断开',e) break if __name__ == "__main__": HOST, PORT = "localhost", 9999 server = socketserver.TCPServer((HOST, PORT), MyTCPHandler) server.serve_forever()
让你的socketserver并发起来, 必须选择使用以下一个多并发的类
class socketserver.
ForkingTCPServer
class socketserver.
ForkingUDPServer
class socketserver.
ThreadingTCPServer
class socketserver.
ThreadingUDPServer
so 只需要把下面这句
server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
换成下面这个,就可以多并发了,这样,客户端每连进一个来,服务器端就会分配一个新的线程来处理这个客户端的请求
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
当然,知道了socket的实现原理之后,我们还可以使用socket来完成很多事情,比如说用socket来完成一个FTP文件上传下载的功能也都可以。。。学无止境,慢慢加油吧。