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. Tomcat学习笔记 - 错误日志 - Tomcat安装版安装后第二次启动后闪退(转)-- javac不是内部或外部命令 -- 配置java环境教程

    如果安装成功并且安装完成第一次启动是成功的,第二次就闪退的话,原因之一是没有配置java的环境.在网上找的配制方法有很多错误,测试javac命令时候会提示不是内部或外部命令,找到一个正确的教程.如下, ...

  2. python运维开发(十八)----Django(二)

    内容目录 路由系统 模版 Ajax model数据库操作,ORM 路由系统 django中的路由系统和其他语言的框架有所不同,在django中每一个请求的url都要有一条路由映射,这样才能将请求交给对 ...

  3. shell之变量替换:临时替换

    ​${FILE:-word} 若变量为空,给变量FILE添加一个临时默认值word,FILE本身值并不变化eg: FILE1=${FILE:-word} 若FILE为空,则赋予FILE1值word $ ...

  4. python笔记之ZipFile模块

    python笔记之ZipFile模块 zipfile模块用来做zip格式编码的压缩和解压缩的,zipfile里有两个非常重要的class, 分别是ZipFile和ZipInfo, 在绝大多数的情况下, ...

  5. Python变量和数据类型

    十六进制用0x前缀和0-9 a-f表示   字符串是以''或""括起来的任意文本   一个布尔值只有True和False两种值   布尔值可以用and or not运算   空值是 ...

  6. Mysql服务启动问题

    mysql ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) u ...

  7. 三维地图(BFS)

    亡命逃窜 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 从前有个叫hck的骑士,为了救我们美丽的公主,潜入魔王的老巢,够英雄吧.不过英雄不是这么好当的.这个可怜的娃 ...

  8. Keil中Memory Model和Code Rom Size说明

    C51中定义变量时如果省略存储器类型,Keil C51编译系统则会按编译模式SMALL.COMPACT和LARGE所规定的默认存储器类型去指定变量的存储区域,无论什么存储模式都可以声明变量在任何的80 ...

  9. C# 内存泄露

    一.事件引起的内存泄露 1.不手动注销事件也不发生内存泄露的情况 我们经常会写EventHandler += AFunction; 如果没有手动注销这个Event handler类似:EventHan ...

  10. 搜索和搜索形式(SEARCHING and its forms)

    什么是搜索? 在计算机科学中,搜索就是在一个事物的集合中找到具有特定特征的一项的过程.这些集合中的元素可能是排好序的数据库中的记录,简单数组中的数据,文件中的文本,树中的节点,几何图形中的点和边或者是 ...