怎样自己制作网站,软件程序定制开发,制作网页要钱,如何自己建设简单的手机网站首页Python之路#xff1a; socket篇 Socket 网络上的两个程序通过一个双向的通信连接实现数据的交换#xff0c;这个连接的一端称为一个socket#xff0c;作为BSD UNIX的进程通信机制#xff0c;通常也称做“套接字” #xff0c;是一个通信链的句柄#xff0c;实现不同程序… Python之路 socket篇 Socket 网络上的两个程序通过一个双向的通信连接实现数据的交换这个连接的一端称为一个socket作为BSD UNIX的进程通信机制通常也称做“套接字” 是一个通信链的句柄实现不同程序之间的发出请求和应答请求。对于文件用【打开】【读写】【关闭】模式操作。 socket就是该模式的实现即一种特殊的文件一些socket函数就是对其进行操作读/写IO、打开、关闭更多socket可以点击这里 socket解析图 socket和file的区别 file模块是针对某个指定文件进行【打开】【读写】【关闭】socket模块是针对服务端和客户端Socket进行【读】【写】【关闭】 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,9999) sk socket.socket() sk.bind(ip_port) sk.listen(5) while True: print server waiting... conn,addr sk.accept() client_data conn.recv(1024) print client_data conn.sendall(不要回答,不要回答,不要回答) conn.close() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,9999) sk socket.socket() sk.connect(ip_port) sk.sendall(请求占领地球)#发送数据 server_reply sk.recv(1024)#请求数据 print server_reply sk.close()#关闭socket WEB服务应用 #!/usr/bin/env python #coding:utf-8 import socket def handle_request(client): buf client.recv(1024) client.send(HTTP/1.1 200 OK\r\n\r\n) client.send(Hello, World)#发送 def main(): sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((localhost,8080)) sock.listen(5) while True: connection, address sock.accept() handle_request(connection) connection.close() if __name__ __main__: main() 更多功能 sk socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一地址簇 socket.AF_INET IPv4默认 socket.AF_INET6 IPv6 socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 参数二类型 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_RDM 是一种可靠的UDP形式即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问在需要执行某些特殊操作时使用如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。 socket.SOCK_SEQPACKET 可靠的连续数据包服务 参数三协议 0 默认与特定的地址家族相关的协议,如果是 0 则系统就会根据地址格式和套接类别,自动选择一个合适的协议 import socket ip_port (127.0.0.1,9999) sk socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) sk.bind(ip_port) while True: data sk.recv(1024) print data import socket ip_port (127.0.0.1,9999) sk socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp raw_input(数据).strip() if inp exit: break sk.sendto(inp,ip_port) sk.close() sk.bind(address) s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下以元组host,port的形式表示地址。 sk.listen(backlog) 开始监听传入连接。backlog指定在拒绝连接之前可以挂起的最大连接数量。 backlog等于5表示内核已经接到了连接请求但服务器还没有调用accept进行处理的连接个数最大为5 这个值不能无限大因为要在内核中维护连接队列 sk.setblocking(bool) 是否阻塞默认True如果设置False那么accept和recv时一旦无数据则报错。 sk.accept() 接收连接并返回conn,address,其中conn是新的套接字对象可以用来接收和发送数据。address是连接客户端的地址。 接收TCP客户的连接阻塞式等待连接的到来 sk.connect(address) 连接到address处的套接字。一般address的格式为元组hostname,port如果连接出错返回socket.error错误。 sk.connect_ex(address) 同上只不过会有返回值连接成功时返回0连接失败时候返回编码例如10061 sk.close() 关闭套接字 sk.recv(bufsize[,flag]) 接收套接字的数据。数据以字符串形式返回bufsize指定最多可以接收的数量。flag提供有关消息的其他信息通常可以忽略。 sk.recvfrom(bufsize[.flag]) 与recv()类似但返回值是data,address。其中data是包含接收数据的字符串address是发送数据的套接字地址。 sk.send(string[,flag]) 将string中的数据发送到连接的套接字。返回值是要发送的字节数量该数量可能小于string的字节大小。 sk.sendall(string[,flag]) 将string中的数据发送到连接的套接字但在返回之前会尝试所有数据。成功返回None失败则抛出异常。 sk.sendto(string[,flag],address) 将数据发送到套接字address是形式为ipaddrport的元组指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。 sk.settimeout(timeout) 设置套接字操作的超时期timeout是一个浮点数单位是秒。值为None表示没有超时期。一般超时期应该在刚创建套接字时设置因为它们可能用于连接的操作如 client 连接最多等待5s sk.getpeername() 返回连接套接字的远程地址。返回值通常是元组ipaddr,port。 sk.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port) sk.fileno() 套接字的文件描述符 import socket ip_port (127.0.0.1,9999) sk socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) sk.bind(ip_port) while True: data sk.recv(1024) print data import socket ip_port (127.0.0.1,9999) sk socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp raw_input(数据).strip() if inp exit: break sk.sendto(inp,ip_port) sk.close() 实例 只能机器人 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8888) sk socket.socket() sk.bind(ip_port) sk.listen(5) while True: conn,address sk.accept() conn.sendall(欢迎致电 10086请输入1xxx,0转人工服务.) Flag True while Flag: data conn.recv(1024) if data exit: Flag False elif data 0: conn.sendall(通过可能会被录音.balabala一大推) else: conn.sendall(请重新输入.) conn.close() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8005) sk socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data sk.recv(1024) print receive:,data inp raw_input(please input:) sk.sendall(inp) if inp exit: break sk.close() IO多路复用 I/O多路复用是指通过一种机制可以监视多个描述符一旦某个描述符就绪一般是读就绪或者是写就绪能够通知程序进行相应的读写操作。 Linux Linux中的selectpollepoll都是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 P樱桃红中有一个select模块其中提供了select、poll、epoll三个方法分别调用系统的delectpollepoll从而实现IO多路复用。 Windows Python 提供 select Mac Python 提供 select Linux Python 提供 select、poll、epoll 注网络操作、文件操作、终端操作等均属于IO操作对于windows只支持Socket操作其他系统支持其他IO操作但是无法检测普通文件操作自动上次读取是否已经变化。 对于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 秒之后返回三个空列表如果监听的句柄有变化则直接执行。 #!/usr/bin/env python # -*- coding:utf-8 -*- import select import threading import sys while True: readable, writeable, error select.select([sys.stdin,],[],[],1) if sys.stdin in readable: print select get stdin,sys.stdin.readline() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select sk1 socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk1.bind((127.0.0.1,8002)) sk1.listen(5) sk1.setblocking(0) inputs [sk1,] while True: readable_list, writeable_list, error_list select.select(inputs, [], inputs, 1) for r in readable_list: # 当客户端第一次连接服务端时 if sk1 r: print accept request, address r.accept() request.setblocking(0) inputs.append(request) # 当客户端连接上服务端之后再次发送数据时 else: received r.recv(1024) # 当正常接收客户端发送的数据时 if received: print received data:, received # 当客户端关闭程序时 else: inputs.remove(r) sk1.close() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8002) sk socket.socket() sk.connect(ip_port) while True: inp raw_input(please input:) sk.sendall(inp) sk.close() 此处的Socket服务端相比与原生的Socket他支持当某一个请求不再发送数据时服务器端不会等待二是可以去处理其他请求的数据。但是如果每个请求的耗时比较长时select版本的服务器端也无法完成同时操作。 SocketServer 模块 一、使用以源码剖析 对于默认Socket服务端处理客户端请求时按照阻塞方式依次处理请求SocketServer实现同时处理多个请求。 #!/usr/bin/env python # -*- coding:utf-8 -*- import SocketServer class MyServer(SocketServer.BaseRequestHandler): def handle(self): # print self.request,self.client_address,self.server conn self.request conn.sendall(欢迎致电 10086请输入1xxx,0转人工服务.) Flag True while Flag: data conn.recv(1024) if data exit: Flag False elif data 0: conn.sendall(通过可能会被录音.balabala一大推) else: conn.sendall(请重新输入.) if __name__ __main__: server SocketServer.ThreadingTCPServer((127.0.0.1,8009),MyServer) server.serve_forever() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8009) sk socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data sk.recv(1024) print receive:,data inp raw_input(please input:) sk.sendall(inp) if inp exit: break sk.close() 从上述源码执行流程对源码精简如下 import socket import threading import select def process(request, client_address): print request,client_address conn request conn.sendall(欢迎致电 10086请输入1xxx,0转人工服务.) flag True while flag: data conn.recv(1024) if data exit: flag False elif data 0: conn.sendall(通过可能会被录音.balabala一大推) else: conn.sendall(请重新输入.) sk socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.bind((127.0.0.1,8002)) sk.listen(5) while True: r, w, e select.select([sk,],[],[],1) print looping if sk in r: print get request request, client_address sk.accept() t threading.Thread(targetprocess, args(request, client_address)) t.daemon False t.start() sk.close() 如精简代码可以看出SocketServer之所以可以同时处理请求得益于 select 和 Threading 两个东西其实本质上就是在服务器端为每一个客户端创建一个线程当前线程用来处理对应客户端的请求所以可以支持同时n个客户端链接长连接。 #!/usr/bin/env python #coding:utf-8 import SocketServer import os class MyServer(SocketServer.BaseRequestHandler): def handle(self): base_path G:/temp conn self.request print connected... while True: pre_data conn.recv(1024) #获取请求方法、文件名、文件大小 cmd,file_name,file_size pre_data.split(|) # 防止粘包给客户端发送一个信号。 conn.sendall(nothing) #已经接收文件的大小 recv_size 0 #上传文件路径拼接 file_dir os.path.join(base_path,file_name) f file(file_dir,wb) Flag True while Flag: #未上传完毕 if int(file_size)recv_size: #最多接收1024可能接收的小于1024 data conn.recv(1024) recv_sizelen(data) #写入文件 f.write(data) #上传完毕则退出循环 else: recv_size 0 Flag False print upload successed. f.close() instance SocketServer.ThreadingTCPServer((127.0.0.1,9999),MyServer) instance.serve_forever() #!/usr/bin/env python #coding:utf-8 import socket import sys import os ip_port (127.0.0.1,9999) sk socket.socket() sk.connect(ip_port) container {key:,data:} while True: # 客户端输入要上传文件的路径 input raw_input(path:) # 根据路径获取文件名 file_name os.path.basename(path) # 获取文件大小 file_sizeos.stat(path).st_size # 发送文件名 和 文件大小 sk.send(file_name|str(file_size)) # 为了防止粘包将文件名和大小发送过去之后等待服务端收到直到从服务端接受一个信号说明服务端已经收到 sk.recv(1024) send_size 0 f file(path,rb) Flag True while Flag: if send_size 1024 file_size: data f.read(file_size-send_size) Flag False else: data f.read(1024) send_size1024 sk.send(data) f.close() sk.close() 对于大文件处理: send只会向缓冲区写一次传入的内容不一定能发完所以返回值是实际发送的大小。 例如 1023M send(1g数据) 那么实际是发送了 1023M其他 1M 就是漏发了 sendall内部调用send会一直向缓冲区写直到文件全部写完。 例如 sendall(1g数据) 第一次 send(1023M) 第二次 send(1M) 发送大文件时候不可能全部读1G内存需要open文件时一点一点读然后再发。 # 大文件大小 file_sizeos.stat(文件路径).st_size # 打开大文件 f file(文件路径,rb) # 已经发送的数据 send_size 0 while Flag: # 大文件只剩下 不到 1024 字节其他已经被发送。 if send_size 1024 file_size: # 从大文件中读取小于 1024字节可能是 10字节... data f.read(file_size-send_size) Flag False else: # 从大文件中读取 1024 字节 data f.read(1024) # 记录已经发送了多少字节 send_size 1024 # 将大文件中的数据分批发送到缓冲区每次最多发 1024 字节 sk.sendall(data) 二、select Linux中的 selectpollepoll 都是IO多路复用的机制。 I/O多路复用指通过一种机制可以监视多个描述符一旦某个描述符就绪一般是读就绪或者写就绪能够通知程序进行相应的读写操作。 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 select用于监听多个文件描述符 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket import threading import select def process(request, client_address): print request,client_address conn request conn.sendall(欢迎致电 10086请输入1xxx,0转人工服务.) flag True while flag: data conn.recv(1024) if data exit: flag False elif data 0: conn.sendall(通过可能会被录音.balabala一大推) else: conn.sendall(请重新输入.) s1 socket.socket(socket.AF_INET, socket.SOCK_STREAM) s1.bind((127.0.0.1,8020)) s1.listen(5) s2 socket.socket(socket.AF_INET, socket.SOCK_STREAM) s2.bind((127.0.0.1,8021)) s2.listen(5) while True: r, w, e select.select([s1,s2,],[],[],1) print looping for s in r: print get request request, client_address s.accept() t threading.Thread(targetprocess, args(request, client_address)) t.daemon False t.start() s1.close() s2.close() 服务端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8020) sk socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data sk.recv(1024) print receive:,data inp raw_input(please input:) sk.sendall(inp) if inp exit: break sk.close() #!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port (127.0.0.1,8021) sk socket.socket() sk.connect(ip_port) sk.settimeout(5) while True: data sk.recv(1024) print receive:,data inp raw_input(please input:) sk.sendall(inp) if inp exit: break sk.close() 三、threading 问答 应用程序、进程、线程关系为什么要使用多个CPU?为什么要使用多线程为什么要使用多进程java和C#中的多线程和python多线程的区别Python GIL?线程和进程的选择计算密集型和IO密集型程序。IO操作不占用CPU 1、Python线程 Threading用于提供线程相关的操作线程是应用程序中工作的最小单元。 #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time def show(arg): time.sleep(1) print threadstr(arg) for i in range(10): t threading.Thread(targetshow, args(i,)) t.start() print main thread stop 上述代码创建了10个“前台”线程然后控制器就交给了CPUCPU根据指定算法进行调度分片执行指令。 更多方法 start 线程准备就绪等待CPU调度setName 为线程设置名称getName 获取线程名称setDaemon 设置为后台线程或前台线程默认 如果是后台线程主线程执行过程中后台线程也在进行主线程执行完毕后后台线程不论成功与否均停止 如果是前台线程主线程执行过程中前台线程也在进行主线程执行完毕后等待前台线程也执行完成后程序停止 join 逐个执行每个线程执行完毕后继续往下执行...run 线程被cpu调度后执行此方法 2、线程锁 由于线程之间是进行随机调度并且每个线程可能只执行n条执行之后CPU接着执行其他线程。所以可能出现如下问题 未使用线程锁 #!/usr/bin/env python #coding:utf-8 import threading import time gl_num 0 lock threading.RLock() def Func(): lock.acquire() global gl_num gl_num 1 time.sleep(1) print gl_num lock.release() for i in range(10): t threading.Thread(targetFunc) t.start() 扩展进程 1、创建多进程程序 from multiprocessing import Process import threading import time def foo(i): print say hi,i for i in range(10): p Process(targetfoo,args(i,)) p.start() 注意由于进程之间的数据需要各自持有一份所以创建进程需要的非常大的开销。 2、进程共享数据 进程各自持有一份数据默认无法共享数据 #!/usr/bin/env python #coding:utf-8 from multiprocessing import Process from multiprocessing import Manager import time li [] def foo(i): li.append(i) print say hi,li for i in range(10): p Process(targetfoo,args(i,)) p.start() print ending,li #方法一Array from multiprocessing import Process,Array temp Array(i, [11,22,33,44]) def Foo(i): temp[i] 100i for item in temp: print i,-----,item for i in range(2): p Process(targetFoo,args(i,)) p.start() p.join() #方法二manage.dict()共享数据 from multiprocessing import Process,Manager manage Manager() dic manage.dict() def Foo(i): dic[i] 100i print dic.values() for i in range(2): p Process(targetFoo,args(i,)) p.start() p.join() 3、进程池 #!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process,Pool import time def Foo(i): time.sleep(2) return i100 def Bar(arg): print arg pool Pool(5) #print pool.apply(Foo,(1,)) #print pool.apply_async(func Foo, args(1,)).get() for i in range(10): pool.apply_async(funcFoo, args(i,),callbackBar) print end pool.close() pool.join()