您的位置:首页 > 理论基础 > 计算机网络

python select网络编程模块详解

2017-07-08 18:48 531 查看
刚看了反应堆模式的原理,特意复习了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类型



2、socket函数

服务器端 Socket 函数



Socket 函数 描述



公共 Socket 函数



3、socket异常



二、socket编程

1、基于TCP(面向连接)的Socket编程(C++)

服务器端顺序:

(1. 加载套接字库

(2. 创建套接字(serversocket)

(3. 将套接字绑定到一个本地地址和端口上(bind)

(4. 将套接字设为监听模式,准备接收客户请求(listen)

(5. 等待客户请求的到来;当请求带来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)

(6. 用返回的套接字和客户端进行通信(send/recv)调用socket类的getOutputStream()和getInputStream()获取输出流和输入流

(7. 返回,等待另一个客户请求

(8. 关闭套接字(closesocket)

客户端程序:

(1. 加载套接字库

(2. 创建套接字(socket)

(3. 向服务器发送连接请求(connect)

(4. 和服务器端进行通信(send/receive) 调用socket类的getOutputStream()和getInputStream()获取输出流和输入流

(5. 关闭套接字(closesocket)

2、基于UDP(面向无连接)的socket编程(C++)

服务器端(接收端)程序:

(1. 加载套接字库

(2. 创建套接字(socket)

(3. 将套接字绑定到一个本地地址和端口上(bind)

(4. 等待接收数据(recvfrom)

(5. 关闭套接字(closesocket)

客户端(发送端)程序

(1. 加载套接字库

(2. 创建套接字(socket)

(3. 向服务器发送数据(sendto)

(4. 关闭套接字(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介绍

在python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。

select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。

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复用介绍和区别

sellect、poll、epoll三者的区别(多路IO复用都是同步的)

select

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: