Socket 简介
在网络上的两个程序通过一个双向的通信连接实现数据的交换,这个链接的一端称为一个 Socket(套接字),用于描述 IP 地址和端口。
建立网络通信连接至少要一对端口号(Socket),Socket 本质是编程接口(API),对 TCP/IP 的封装,提供了网络通信能力。
每种服务都打开一个 Socket,并绑定到端口上,不同的端口对应不同的服务,就像 http 对应 80 端口。
Socket 是面向 C/S(客户端 / 服务器)模型设计,客户端在本地随机申请一个唯一的 Socket 号,服务器拥有公开的 socket,任何客户端都可以向它发送连接请求和信息请求。
比如:用手机打电话给 10086 客服,你的手机号就是客户端,10086 客服是服务端。必须在知道对方电话号码前提下才能与对方通讯。
Socket 数据处理流程如图:
17.1 socket
在 Python 中提供此服务的模块是 socket 和 SocketServer,下面是 socket 常用的类、方法:
方法 | 描述 |
socket.socket([family[, type[, proto]]]) | socket 初始化函数,(地址族,socket 类型,协议编号)协议编号默认 0 |
socket.AF_INET | IPV4 协议通信 |
socket.AF_INET6 | IPV6 协议通信 |
socket.SOCK_STREAM | socket 类型,TCP |
socket.SOCK_DGRAM | socket 类型,UDP |
socket.SOCK_RAW | 原始 socket,可以处理普通 socker 无法处理的报文,比如 ICMP |
socket.SOCK_RDM | 更可靠的 UDP 类型,保证对方收到数据 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
socket.socket() 对象有以下方法:
accept() | 接受连接并返回 (socket object, address info),address 是客户端地址 |
bind(address) | 绑定 socket 到本地地址,address 是一个双元素元组(host,port) |
listen(backlog) | 开始接收连接,backlog 是最大连接数,默认 1 |
connect(address) | 连接 socket 到远程地址 |
connect_ex(address) | 连接 socket 到远程地址,成功返回 0,错误返回 error 值 |
getpeername() | 返回远程端地址 (hostaddr, port) |
gettimeout() | 返回当前超时的值,单位秒,如果没有设置返回 none |
recv(buffersize[, flags]) | 接收来自 socket 的数据,buffersize 是接收数据量 |
send(data[, flags]) | 发送数据到 socket,返回值是发送的字节数 |
sendall(data[, flags]) | 发送所有数据到 socket,成功返回 none,失败抛出异常 |
setblocking(flag) | 设置 socket 为阻塞(flag 是 true)或非阻塞(flag 是 flase) |
温习下 TCP 与 UDP 区别:
TCP 和 UDP 是 OSI 七层模型中传输层提供的协议,提供可靠端到端的传输服务。
TCP(Transmission Control Protocol,传输控制协议),面向连接协议,双方先建立可靠的连接,再发送数据。适用于可靠性要求高的应用场景。
UDP(User Data Protocol,用户数据报协议),面向非连接协议,不与对方建立连接,直接将数据包发送给对方,因此相对 TCP 传输速度快 。适用于可靠性要求低的应用场景。
17.1.1 TCP 编程
下面创建一个服务端 TCP 协议的 Socket 演示下。
先写一个服务端:
- # ! /usr/bin / python# - *- coding: utf - 8 - *-import socket HOST = '' # 为空代表所有可用的网卡PORT = 50007 # 任意非特权端口s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) # 最大连接数conn,
- addr = s.accept() # 返回客户端地址print 'Connected by',
- addr
- while 1 : data = conn.recv(1024) # 每次最大接收客户端发来数据1024字节
- if not data:
- break # 当没有数据就退出死循环 print "Received: ",
- data # 打印接收的数据 conn.sendall(data) # 把接收的数据再发给客户端conn.close()
再写一个客户端:
- # ! /usr/bin / python# - *- coding: utf - 8 - *-import socket HOST = '192.168.1.120' # 远程主机IP PORT = 50007 # 远程主机端口s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) s.sendall('Hello, world') # 发送数据data = s.recv(1024) # 接收服务端发来的数据s.close() print 'Received: ',
- data
写好后,打开一个终端窗口执行:
- # python socket - server.py监听中...# 直到客户端运行会接收到下面数据并退出Connected by ('192.168.1.120', 37548) Received: Hello,
- world
再打开一个终端窗口执行:
# 如果端口监听说明服务端运行正常
- # netstat - antp | grep 50007 tcp 0 0 0.0.0.0 : 50007 0.0.0.0 : * LISTEN 72878 / python# python socket - client.py Received: Hello,
- world
通过实验了解搭到 Socket 服务端工作有以下几个步骤:
1)打开 socket
2)绑定到一个地址和端口
3)监听进来的连接
4)接受连接
5)处理数据
17.1.2 UDP 编程
服务端:
- import socket HOST = '' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((HOST, PORT)) while 1 : data,
- addr = s.recvfrom(1024) print 'Connected by',
- addr print "Received: ",
- data s.sendto("Hello %s" % repr(addr), addr) conn.close()
客户端:
- import socket HOST = '192.168.1.99' PORT = 50007 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.sendto(data, (HOST, PORT)) data = s.recv(1024) s.close() print 'Received: ',
- data
运行方式与 TCP 编程一样。
使用 UDP 协议时,服务端就少了 listen() 和 accept(),不需要建立连接就直接接收客户端的数据,也是把数据直接发送给客户端。
客户端少了 connect(),同样直接通过 sendto() 给服务器发数据。
而 TCP 协议则前提先建立三次握手。
17.1.3 举一个更直观的 socket 通信例子
客户端发送 bash 命令,服务端接收到并执行,把返回结果回应给客户端。
服务端:
- # ! /usr/bin / python# - *- coding: utf - 8 - *-import sys import subprocess import socket HOST = '' PORT = 50007
- try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(1) except socket.error as e: s.close() print e sys.exit(1) while 1 : conn,
- addr = s.accept() print 'Connected by',
- addr
- while 1 : # 每次读取1024字节 data = conn.recv(1024)
- if not data: # 客户端关闭服务端会收到一个空数据 print repr(addr) + " close." conn.close()
- break print "Received: ",
- data cmd = subprocess.Popen(data, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True) result_tuple = cmd.communicate()
- if cmd.returncode != 0 or cmd.returncode == None: result = result_tuple[1] # result = cmd.stderr.read()
- else: result = result_tuple[0] # result = cmd.stdout.read() # 读不到标准输出,不知道为啥,所以不用
- if result: conn.sendall(result)
- else: conn.sendall("return null") s.close()
客户端:
- # ! /usr/bin / python# - *- coding: utf - 8 - *-import sys import socket HOST = '192.168.1.120' PORT = 50007
- try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) except socket.error as e: s.close() print e sys.exit(1) while 1 : cmd = raw_input("Please input command: ")
- if not cmd:
- continue s.sendall(cmd) recv_data = s.recv(1024) print 'Received: ',
- recv_data s.close()
查看运行效果,先运行服务端,再运行客户端:
- # python socket - server.py Connected by ('192.168.1.120', 45620) Received: ls Received: touch a.txt Received: ls# python socket - client.py Please input command: ls Received: socket - client.py socket - server.py Please input command: touch a.txt Received:
- return null Please input command: ls Received: a.txt socket - client.py socket - server.py Please input command:
我想通过上面这个例子你已经大致掌握了 socket 的通信过程。
再举一个例子,通过 socket 获取本机网卡 IP:
- >>> socket.gethostname()'ubuntu' >>> socket.gethostbyname(socket.gethostname())'127.0.1.1' >>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) >>> s.connect(('10.255.255.255', 0)) >>> s.getsockname()('192.168.1.120', 35765) >>> s.getsockname()[0]'192.168.1.120'
博客地址:http://lizhenliang.blog.51cto.com
QQ 群:323779636(Shell/Python 运维开发群)
17.2 SocketServer
ScoketServer 是 Socket 服务端库,比 socket 库更高级,实现了多线程和多线程,并发处理多个客户端请求。
下面是几个常用的类:
(, , ) | 服务器类,TCP 协议 |
(, , ) | 服务器类,UDP 协议 |
(, ) | 这个是所有服务器对象的超类。它定义了接口,不提供大多数方法,在子类中进行。 |
| 这个是所有请求处理对象的超类。它定义了接口,一个具体的请求处理程序子类必须定义一个新的 handle() 方法。 |
SocketServer.StreamRequestHandler | 流式 socket,根据 socket 生成读写 socket 用的两个文件对象,调用 rfile 和 wfile 读写 |
SocketServer.DatagramRequestHandler | 数据报 socket,同样生成 rfile 和 wfile,但 UDP 不直接关联 socket。这里 rfile 是由 UDP 中读取的数据生成,wfile 则是新建一个 StringIO,用于写数据 |
SocketServer.ForkingMixIn/ThreadingMixIn | 多进程(分叉)/ 多线程实现异步。混合类,这个类不会直接实例化。用于实现处理多连接 |
- SocketServer
- .
- BaseServer()对象有以下方法:
fileno() | 返回一个整数文件描述符上服务器监听的套接字 |
handle_request() | 处理一个请求 |
serve_forever(poll_interval=0.5) | 处理,直至有明确要求 shutdown() 的请求。轮训关机每 poll_interval 秒 |
shutdown() | 告诉 serve_forever() 循环停止并等待 |
server_close() | 清理服务器 |
address_family | 地址族 |
server_address | 监听的地址 |
RequestHandlerClass | 用户提供的请求处理类 |
socket | socket 对象上的服务器将监听传入的请求 |
allow_reuse_address | 服务器是否允许地址的重用。默认 False |
request_queue_size | 请求队列的大小。 |
socket_type | socket 类型。socket.SOCK_STREAM 或 socket.SOCK_DGRAM |
timeout | 超时时间,以秒为单位 |
finish_request() | 实际处理通过实例请求 RequestHandleClass 并调用其 handle() 方法 |
get_request() | 必须接受从 socket 的请求,并返回 |
handle_error(request, client_address) | 如果这个函数被条用 handle() |
process_request(request, client_address) | ? |
server_activate() | ? |
server_bind() | 由服务器构造函数调用的套接字绑定到所需的地址 |
verify_request(request, client_address) | 返回一个布尔值,如果该值是 True,则该请求将被处理,如果是 False,该请求将被拒绝。 |
创建一个服务器需要几个步骤:
1)创建类,继承请求处理类(BaseRequestHandler),并重载其 handle() 方法,此方法将处理传入的请求
2)实例化服务器类之一,它传递服务器的地址和请求处理程序类
3)调用 handle_request() 或 serve_forever() 服务器对象的方法来处理一个或多个请求
4)调用 server_close() 关闭套接字
17.2.1 TCP 编程
服务端:
- # ! /usr/bin / python# - *- coding: utf - 8 - *import SocketServer class MyTCPHandler(SocketServer.BaseRequestHandler) : """
- 请求处理程序类。
- 每个连接到服务器都要实例化一次,而且必须覆盖handle()方法来实现与客户端通信
- """ def handle(self) : # self.request 接收客户端数据 self.data = self.request.recv(1024).strip() print "%s wrote:" % (self.client_address[0]) print self.data # 把接收的数据转为大写发给客户端 self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST,
- PORT = "localhost",
- 9999 # 创建服务器并绑定本地地址和端口 server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) # 激活服务器,会一直运行,直到Ctrl - C中断 server.serve_forever()
另一个请求处理程序类,利用流(类文件对象简化通信提供标准文件接口):
- class MyTCPHandler(SocketServer.StreamRequestHandler) : def handle(self) : # self.rfile创建的是一个类文件对象处理程序,就可以调用readline()而不是recv() self.data = self.rfile.readline().strip() print "%s wrote:" % (self.client_address[0]) print self.data # 同样,self.wfile是一个类文件对象,用于回复客户端 self.wfile.write(self.data.upper())
客户端:
- import socket import sys HOST,
- PORT = "localhost",
- 9999 data = " ".join(sys.argv[1 : ]) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((HOST, PORT)) sock.sendall(data + "\n") received = sock.recv(1024)
- finally: sock.close() print "Sent: %s" % data print "Received: %s" % received
服务端结果:
- # python TCPServer.py 127.0.0.1 wrote: hello 127.0.0.1 wrote: nice
客户端结果:
- # python TCPClient.py hello Sent: hello Received: HELLO# python TCPClient.py nice Sent: nice Received: NICE
17.2.2 UDP 编程
服务端:
- import SocketServer class MyTCPHandler(SocketServer.BaseRequestHandler) : def handle(self) : self.data = self.request[0].strip() self.socket = self.request[1] print "%s wrote:" % (self.client_address[0]) print self.data self.socket.sendto(self.data.upper(), self.client_address) if __name__ == "__main__": HOST,
- PORT = "localhost",
- 9999 server = SocketServer.UDPServer((HOST, PORT), MyTCPHandler) server.serve_forever()
客户端:
- import socket import sys HOST,
- PORT = "localhost",
- 9999 data = " ".join(sys.argv[1 : ]) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(data + "\n", (HOST, PORT)) received = sock.recv(1024) print "Sent: %s" % data print "Received: %s" % received
与 TCP 执行结果一样。
17.2.3 异步混合
创建异步处理,使用 ThreadingMixIn 和 ForkingMixIn 类。
ThreadingMixIn 类的一个例子:
- # ! /usr/bin / python# - *- coding: utf - 8 - *import socket import threading import SocketServer class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler) : def handle(self) : data = self.request.recv(1024) cur_thread = threading.current_thread() response = "%s: %s" % (cur_thread.name, data) self.request.sendall(response) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer) : pass def client(ip, port, message) : sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port))
- try: sock.sendall(message) response = sock.recv(1024) print "Received: %s" % response
- finally: sock.close() if __name__ == "__main__": # 端口0意味着随机使用一个未使用的端口 HOST,
- PORT = "localhost",
- 0 server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) ip,
- port = server.server_address # 服务器启动一个线程,该线程将开始。每个线程处理每个请求 server_thread = threading.Thread(target = server.serve_forever) # 作为守护线程 server_thread.daemon = True server_thread.start() print "Server loop running in thread:",
- server_thread.name client(ip, port, "Hello World 1") client(ip, port, "Hello World 2") client(ip, port, "Hello World 3") server.shutdown() server.server_close()
- # python socket - server.py Server loop running in thread: Thread - 1 Received: Thread - 2 : Hello World 1 Received: Thread - 3 : Hello World 2 Received: Thread - 4 : Hello World 3
来源: http://www.bubuko.com/infodetail-1861334.html