SOCKET编程

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用打开,读写,关闭 模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

面向连接与无连接

面向连接

无论你使用哪一种的地址簇,套接字的类型只有两种,一种是面向连接的套接字,即在通信之前要建立一条连接,实现这种连接的主要协议就是传输控制协议(TCP),要创建TCP套接字就得在创建的时候指定套接字的类型为SOCK_STREAM.

无连接

无需建立连接就可以通讯,但是这时,数据到达的顺序,可靠性和不重复性就无法保证了。实现这种连接的协议就是用户数据报协议(UDP),要创建UDP套接字就得指定套接字的类型为SOCK_DGRAM

套接字对象(内建)方法

函数 描述
服务器端套接字函数
s.bind() 绑定地址(主机名,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()的扩展版本,出错时返回错误码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 完整发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址(TCP连接)
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设定指定套接字的参数
s.close() 关闭套接字
面向模块的套接字函数
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字的超时时间
s.gettimeout() 获得阻塞套接字的超时时间
面向文件的套接字函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字关连的文件对象

TCP连接过程图:

时间服务器(socket server) TCP

#!/usr/bin/env python3
# Author: Zhangxunan from socket import *
from time import ctime HOST = ''
PORT = 21567
BUF_SIZE = 1024 ADDR = (HOST, PORT) tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5) while True:
print('waiting for connection...')
tcpCliSock, addr = tcpSerSock.accept()
print('...connected from', addr) while True:
data = tcpCliSock.recv(BUF_SIZE)
if not data:
break
tcpCliSock.send(bytes('[%s] %s' % (ctime(), data.decode()), encoding='utf-8'))
tcpCliSock.close()
tcpSerSock.close()

客户端(socket client)TCP:

#!/usr/bin/env python3
# Author: Zhangxunan from socket import * HOST = 'localhost'
PORT = 21567
BUF_SIZE = 1024
ADDR = (HOST, PORT) tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR) while True:
data = input('> ')
if not data:
break
tcpCliSock.send(bytes(data, encoding='utf-8'))
data = tcpCliSock.recv(BUF_SIZE)
if not data:
break
print(data.decode()) tcpCliSock.close()

时间服务器(socket server)UDP:

#!/usr/bin/env python3
# Author: Zhangxunan from socket import *
from time import ctime HOST = ''
PORT = 21567
BUF_SIZE = 1024 ADDR = (HOST, PORT) udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR) while True:
print('waiting for message...')
data, addr = udpSerSock.recvfrom(BUF_SIZE)
udpSerSock.sendto(bytes('[%s] %s' % (ctime(), data.decode()), encoding='utf-8'), addr)
print('...received from and returned to', addr) udpSerSock.close()

客户端(socket client)UDP:

#!/usr/bin/env python3
# Author: Zhangxunan from socket import * HOST = 'localhost'
PORT = 21567
BUF_SIZE = 1024
ADDR = (HOST, PORT) udpCliSock = socket(AF_INET, SOCK_DGRAM) while True:
data = input('>')
if not data:
break
udpCliSock.sendto(bytes(data, encoding='utf-8'), ADDR)
data, ADDR = udpCliSock.recvfrom(BUF_SIZE)
if not data:
break
print(data.decode()) udpCliSock.close()

IO多路复用

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

Linux

Linux中的 select,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()时便得到通知。

Python

Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。

Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll

select方法

句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)

参数: 可接受四个参数(前三个必须)

返回值:三个列表

select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。

  1. 当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
  2. 当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
  3. 当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
  4. 当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化

    当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

服务器端:

import socket
import select s = socket.socket()
s.bind(('127.0.0.1', 9999,))
s.listen(5) inputs = [s,]
outputs = []
messages = {}
# del messages[tom]
# messages = {
# tom:[msg1, msg2]
# jerry:[msg1,msg2,]
# }
while True:
rlist,wlist,elist, = select.select(inputs, outputs,[s,],1)
print(len(inputs),len(rlist),len(wlist), len(outputs))
# 监听s(服务器端)对象,如果s对象发生变化,表示有客户端来连接了,此时rlist值为[s]
# 监听conn对象,如果conn发生变化,表示客户端有新消息发送过来了,此时rlist的之为 [客户端]
# rlist = [tom,]
# rlist = [jack,role]
# rlist = [s,]
for r in rlist:
if r == s:
# 新客户来连接
conn, address = r.accept()
# conn是什么?其实socket对象
inputs.append(conn)
messages[conn] = []
conn.sendall(bytes('hello', encoding='utf-8'))
else:
# 有人给我发消息了
print('=======')
try:
ret = r.recv(1024)
# r.sendall(ret)
if not ret:
raise Exception('断开连接')
else:
outputs.append(r)
messages[r].append(ret)
except Exception as e:
inputs.remove(r)
del messages[r] # 所有给我发过消息的人
for w in wlist:
msg = messages[w].pop()
resp = msg + bytes('response', encoding='utf-8')
w.sendall(resp)
outputs.remove(w)

客户端:

import socket

s = socket.socket()
s.connect(("127.0.0.1", 9999,))
data = s.recv(1024)
print(data)
while True:
send_data = input(">>>")
s.sendall(bytes(send_data,encoding='utf-8'))
print(s.recv(1024))
s.close()

socketserver模块

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

ThreadingTCPServer

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

1、ThreadingTCPServer基础

使用ThreadingTCPServer:

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer
#!/usr/bin/env python3

import socketserver

class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
# print self.request,self.client_address,self.server
conn = self.request
conn.sendall(bytes('欢迎致电 10086,请输入1xxx,0转人工服务.',encoding='utf-8'))
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall(bytes('您的通话可能会被录音...',encoding='utf-8'))
else:
conn.sendall(bytes('输入有误,请重新输入.',encoding='utf-8')) if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()

客户端:

#!/usr/bin/env python3

import socket

ip_port = ('127.0.0.1',8009)
s = socket.socket()
s.connect(ip_port)
s.settimeout(5) while True:
data = s.recv(1024)
print('receive:', data)
data = input('please input:')
s.sendall(types(data, encoding='utf-8'))
if data == 'exit':
break s.close()

ThreadingTCPServer原码解析

ThreadingTCPServer类继承关系如下图:

内部调用流程为:

启动服务端程序

  1. 执行 TCPServer.init 方法,创建服务端Socket对象并绑定 IP 和 端口
  2. 执行 BaseServer.init 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
  3. 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...

当客户端连接到达服务器:

  1. 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  2. 执行 ThreadingMixIn.process_request_thread 方法
  3. 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

Python之路第九天,高级(1)-网络编程的更多相关文章

  1. Python之路(第三十三篇) 网络编程:socketserver深度解析

    一.socketserver 模块介绍 socketserver是标准库中的一个高级模块,用于网络客户端与服务器的实现.(version = "0.4") 在python2中写作S ...

  2. Python之路(第三十篇) 网络编程:socket、tcp/ip协议

    一.客户端/服务器架构 1.硬件C/S架构(打印机) 打印机作为一个服务端,电脑连接打印机进行打印 2.软件C/S架构 互联网中处处是C/S架构 如谷歌网站是服务端,你的浏览器是客户端(B/S架构也是 ...

  3. Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象

    一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字 server.bind() #把地址绑定到套接字,网络地址加端口 server.lis ...

  4. 学习PYTHON之路, DAY 9 - Socket网络编程

    __import__ 两种方法,官方推荐下面的方法 Socket 参数介绍 sk.bind(address) 必会 s.bind(address) 将套接字绑定到地址.address地址的格式取决于地 ...

  5. python高级之网络编程

    python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及其源码分析 1.网络通信概念 说 ...

  6. 第六篇:python高级之网络编程

    python高级之网络编程   python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及 ...

  7. Python高手之路【十三】socket网络编程

    什么是客户/服务器架构? 什么是客户/服务器架构?不同的人有不同的答案.这要看你问的是什么人,以及指的是软件系统还是硬件系统了.但是,有一点是共通的:服务器是一个软件或硬件,用于提供客户需要的&quo ...

  8. 图解Python 【第七篇】:网络编程Socket

    本节内容一览图:  前言总结: Python 提供了两个基本的 socket 模块. 第一个是 Socket,它提供了标准的 BSD Sockets API. 第二个是 SocketServer, 它 ...

  9. Python Socket,How to Create Socket Cilent? - 网络编程实例

    文章出自:Python socket – network programming tutorial by Silver Moon 原创译文,如有版权问题请联系删除. Network programin ...

  10. Python全栈开发之10、网络编程

    网络编程就是如何在程序中实现两台计算机的通信,而两台计算机间的通信其实就是两个进程间的通信,进程间的通信主要通过socket(套接字)来描述ip地址(主机)和端口(进程)来实现的,因此我们学习网络编程 ...

随机推荐

  1. 【Nutch2.2.1基础教程之2.2】集成Nutch/Hbase/Solr构建搜索引擎之二:内容分析

    请先参见"集成Nutch/Hbase/Solr构建搜索引擎之一:安装及运行",搭建测试环境 http://blog.csdn.net/jediael_lu/article/deta ...

  2. 【转】nginx之逻辑运算

    nginx的配置中不支持if条件的逻辑与&& 逻辑或|| 运算 ,而且不支持if的嵌套语法,否则会报下面的错误:nginx: [emerg] invalid condition. 我们 ...

  3. 【Python学习】由于windows环境问题导致的不能安装某些需要VC编译的插件

    由于windows环境问题导致的不能安装某些需要VC编译的插件 下载地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/ 安装方法: 在CMD中输入 pip in ...

  4. CentOS6下编译安装Python2.7.6方法

    关于在CentOS6下编译安装Python2.7.6的方法非常的多了,小编以前也介绍过相关的文章了,下面一聚教程小编再来为各位介绍一下吧,希望文章能帮助到各位.   CentOS下面Python在升级 ...

  5. Stripies(POJ 1862 贪心)

    Stripies Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 14151   Accepted: 6628 Descrip ...

  6. Maven的使用--Eclipse在线安装Maven插件m2e

    我使用的Eclipse版本是3.7(Indigo) 通过Eclipse的help选项,点击“Install New Software...”弹出安装对话框, 点击add按钮,在Location里输入h ...

  7. windows 7 系统进程服务详解

    windows 7已经发布有段时间了,相信很多网友都已经换上了传说中非常完美的win7系统.win7不仅继承而且还超越了vista的美观界面,性能优化方面也下足了功力.还拥有强大的win xp兼容性, ...

  8. HttpApplication实战大文件上传 (第四篇)

    一.Asp.net中的文件上传 在Asp.net 1.1中,文件在上传过程中将被全部保存在内存中,对于大文件来说,会造成内存空间的过度使用,可能会招致恶意攻击.为了解决这个问题,Asp.net在配置文 ...

  9. BufferedStream类 - 缓冲流

    BufferedStream常用于对其他流的一个封装,它必须和其他流结合一起使用.MemoryStream将所有的内容都放入内存中,而BufferedStream不是.BufferedStream在基 ...

  10. 获取nginx ip地理信息

    filter { grok { match => { "message" => "%{IPORHOST:clientip} \[%{HTTPDATE:time ...