python select网络编程详细介绍
刚看了反应堆模式的原理,特意复习了socket编程,本文主要介绍python的基本socket使用和select使用,主要用于了解socket通信过程
一、socket模块
socket
— Low-level networking interface
This module provides access to the BSD socket interface. It is available on all modern Unix systems, Windows, MacOS, and probably additional platforms.
更多详细信息请看官方文档 https://docs.python.org/3/library/socket.html
1、Socket类型
socket 常量 |
描述 |
socket.AF_UNIX |
只能够用于单一的Unix系统进程间通信 |
socket.AF_INET |
服务器之间网络通信 |
socket.AF_INET6 |
IPv6 |
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_SEQPACKET |
可靠的连续数据包服务 |
创建TCP Socket: |
|
创建UDP Socket: |
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
2、socket函数
服务器端 Socket 函数
Socket 函数 | 描述 |
---|---|
s.bind(address) | 将套接字绑定到地址,在AF_INET下,以tuple(host, port)的方式传入,如s.bind((host, port)) |
s.listen(backlog) | 开始监听TCP传入连接,backlog指定在拒绝链接前,操作系统可以挂起的最大连接数,该值最少为1,大部分应用程序设为5就够用了 |
s.accpet() | 接受TCP链接并返回(conn, address),其中conn是新的套接字对象,可以用来接收和发送数据,address是链接客户端的地址。 |
客户端 Socket 函数
Socket 函数 | 描述 |
---|---|
s.connect(address) | 链接到address处的套接字,一般address的格式为tuple(host, port),如果链接出错,则返回socket.error错误 |
s.connect_ex(address) | 功能与s.connect(address)相同,但成功返回0,失败返回errno的值 |
公共 Socket 函数
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() | 创建一个与该套接字相关的文件 |
3、socket异常
Exception | 解释 |
socket.error |
由Socket相关错误引发 |
socket.herror | 由地址相关错误引发 |
socket.gaierror | 由地址相关错误,如getaddrinfo()或getnameinfo()引发 |
socket.timeout | 当socket出现超时时引发。超时时间由settimeout()提前设定 |
二、socket编程
1、基于TCP(面向连接)的Socket编程(C++)
(5. 关闭套接字(closesocket)
3、socket tcp 编程实例,c/s程序
#!/bin/env python # -*- coding:utf8 -*- """ server.py """ import socket host = ('10.1.32.80', 33333) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 网络通信, TCP流 s.bind(host) s.listen(5) # listen 5 client print "i'm waiting for connection..." while True: conn, addr = s.accept() # connection and ip address print 'connected by', addr while True: data = conn.recv(1024) print "receive from %s:%s" % (addr, data) conn.sendall("server receive your messages, good bye.") conn.close() break # s.close()
#!/bin/env python # -*- coding:utf8 -*- import socket """ client.py """ host = ('10.1.32.80', 33333) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 网络通信, TCP流 s.connect(host) while True: msg = raw_input("Please input message: ") try: s.sendall(msg) except socket.error: print "i'm die, bye bye~" break data = s.recv(1024) print data if "good bye" in data: break s.close()
三、基于select的网络编程
1、select介绍
2、使用select编程,聊天室程序如下。运行多个client,则可互相聊天,输入"exit"即可退出
服务器代码
#!/bin/env python #-*- coding:utf8 -*- """ server select """ import sys import time import socket import select import logging import Queue g_select_timeout = 10 class Server(object): def __init__(self, host='10.1.32.80', port=33333, timeout=2, client_nums=10): self.__host = host self.__port = port self.__timeout = timeout self.__client_nums = client_nums self.__buffer_size = 1024 self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.setblocking(False) self.server.settimeout(self.__timeout) self.server.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #keepalive self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口复用 server_host = (self.__host, self.__port) try: self.server.bind(server_host) self.server.listen(self.__client_nums) except: raise self.inputs = [self.server] #select 接收文件描述符列表 self.outputs = [] #输出文件描述符列表 self.message_queues = {}#消息队列 self.client_info = {} def run(self): while True: readable , writable , exceptional = select.select(self.inputs, self.outputs, self.inputs, g_select_timeout) if not (readable or writable or exceptional) : continue for s in readable : if s is self.server:#是客户端连接 connection, client_address = s.accept() #print "connection", connection print "%s connect." % str(client_address) connection.setblocking(0) #非阻塞 self.inputs.append(connection) #客户端添加到inputs self.client_info[connection] = str(client_address) self.message_queues[connection] = Queue.Queue() #每个客户端一个消息队列 else:#是client, 数据发送过来 try: data = s.recv(self.__buffer_size) except: err_msg = "Client Error!" logging.error(err_msg) if data : #print data data = "%s %s say: %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), self.client_info[s], data) self.message_queues[s].put(data) #队列添加消息 if s not in self.outputs: #要回复消息 self.outputs.append(s) else: #客户端断开 #Interpret empty result as closed connection print "Client:%s Close." % str(self.client_info[s]) if s in self.outputs : self.outputs.remove(s) self.inputs.remove(s) s.close() del self.message_queues[s] del self.client_info[s] for s in writable: #outputs 有消息就要发出去了 try: next_msg = self.message_queues[s].get_nowait() #非阻塞获取 except Queue.Empty: err_msg = "Output Queue is Empty!" #g_logFd.writeFormatMsg(g_logFd.LEVEL_INFO, err_msg) self.outputs.remove(s) except Exception, e: #发送的时候客户端关闭了则会出现writable和readable同时有数据,会出现message_queues的keyerror err_msg = "Send Data Error! ErrMsg:%s" % str(e) logging.error(err_msg) if s in self.outputs: self.outputs.remove(s) else: for cli in self.client_info: #发送给其他客户端 if cli is not s: try: cli.sendall(next_msg) except Exception, e: #发送失败就关掉 err_msg = "Send Data to %s Error! ErrMsg:%s" % (str(self.client_info[cli]), str(e)) logging.error(err_msg) print "Client: %s Close Error." % str(self.client_info[cli]) if cli in self.inputs: self.inputs.remove(cli) cli.close() if cli in self.outputs: self.outputs.remove(s) if cli in self.message_queues: del self.message_queues[s] del self.client_info[cli] for s in exceptional: logging.error("Client:%s Close Error." % str(self.client_info[cli])) if s in self.inputs: self.inputs.remove(s) s.close() if s in self.outputs: self.outputs.remove(s) if s in self.message_queues: del self.message_queues[s] del self.client_info[s] if "__main__" == __name__: Server().run()
客户端代码
#!/usr/local/bin/python # *-* coding:utf-8 -*- """ client.py """ import sys import time import socket import threading class Client(object): def __init__(self, host, port=33333, timeout=1, reconnect=2): self.__host = host self.__port = port self.__timeout = timeout self.__buffer_size = 1024 self.__flag = 1 self.client = None self.__lock = threading.Lock() @property def flag(self): return self.__flag @flag.setter def flag(self, new_num): self.__flag = new_num def __connect(self): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #client.bind(('0.0.0.0', 12345,)) client.setblocking(True) client.settimeout(self.__timeout) client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口复用 server_host = (self.__host, self.__port) try: client.connect(server_host) except: raise return client def send_msg(self): if not self.client: return while True: time.sleep(0.1) #data = raw_input() data = sys.stdin.readline().strip() if "exit" == data.lower(): with self.__lock: self.flag = 0 break self.client.sendall(data) return def recv_msg(self): if not self.client: return while True: data = None with self.__lock: if not self.flag: print 'ByeBye~~' break try: data = self.client.recv(self.__buffer_size) except socket.timeout: continue except: raise if data: print "%s\n" % data time.sleep(0.1) return def run(self): self.client = self.__connect() send_proc = threading.Thread(target=self.send_msg) recv_proc = threading.Thread(target=self.recv_msg) recv_proc.start() send_proc.start() recv_proc.join() send_proc.join() self.client.close() if "__main__" == __name__: Client('10.1.32.80').run()
四、多路IO复用介绍和区别