本地的进程间通讯(IPC)有不少种方式,但能够总结为下面4类:php
但这些都不是本文的主题!咱们要讨论的是网络中进程之间如何通讯?首要解决的问题是如何惟一标识一个进程,不然通讯无从谈起!在本地能够经过进程PID来惟一标识一个进程,可是在网络中这是行不通的。其实TCP/IP协议族已经帮咱们解决了这个问题,网络层的“ip地址”能够惟一标识网络中的主机,而传输层的“协议+端口”能够惟一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就能够标识网络的进程了,网络中的进程通讯就能够利用这个标志与其它进程进行交互。html
使用TCP/IP协议的应用程序一般采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通讯。就目前而言,几乎全部的应用程序都是采用socket,而如今又是网络时代,网络中进程通讯是无处不在,这就是我为何说“一切皆socket”。python
网络编程对全部开发语言都是同样的,Python也不例外。用Python进行网络编程,就是在Python程序自己这个进程内,链接别的服务器进程的通讯端口进行通讯。linux
(1) IP、TCP和UDP
当您编写socket应用程序的时候,您能够在使用TCP仍是使用UDP之间作出选择。它们都有各自的优势和缺点。
TCP是流协议,而UDP是数据报协议。换句话说,TCP在客户机和服务器之间创建持续的开放链接,在该链接的生命期内,字节能够经过该链接写出(而且保证顺序正确)。然而,经过 TCP 写出的字节没有内置的结构,因此须要高层协议在被传输的字节流内部分隔数据记录和字段。
另外一方面,UDP不须要在客户机和服务器之间创建链接,它只是在地址之间传输报文。UDP的一个很好特性在于它的包是自分隔的(self-delimiting),也就是一个数据报都准确地指出它的开始和结束位置。然而,UDP的一个可能的缺点在于,它不保证包将会按顺序到达,甚至根本就不保证。固然,创建在UDP之上的高层协议可能会提供握手和确认功能。
对于理解TCP和UDP之间的区别来讲,一个有用的类比就是电话呼叫和邮寄信件之间的区别。在呼叫者用铃声通知接收者,而且接收者拿起听筒以前,电话呼叫不是活动的。只要没有一方挂断,该电话信道就保持活动,可是在通话期间,他们能够自由地想说多少就说多少。来自任何一方的谈话都按临时的顺序发生。另外一方面,当你发一封信的时候,邮局在投递时既不对接收方是否存在做任何保证,也不对信件投递将花多长时间作出有力保证。接收方可能按与信件的发送顺序不一样的顺序接收不一样的信件,而且发送方也可能在他们发送信件是交替地接收邮件。与(理想的)邮政服务不一样,没法送达的信件老是被送到死信办公室处理,而再也不返回给发送。
编程
(2)对等方、端口、名称和地址
除了TCP和UDP协议之外,通讯一方(客户机或者服务器)还须要知道的关于与之通讯的对方机器的两件事情:IP地址或者端口。IP地址是一个32位的数据值,为了人们好记,通常用圆点分开的4组数字的形式来表示,好比:64.41.64.172。端口是一个16位的数据值,一般被简单地表示为一个小于65536的数字。大多数状况下,该值介于10到100的范围内。一个IP地址获取送到某台机器的一个数据包,而一个端口让机器决定将该数据包交给哪一个进程/服务(若是有的话)。这种解释略显简单,但基本思路是正确的。
上面的描述几乎都是正确的,但它也遗漏了一些东西。大多数时候,当人们考虑Internet主机(对等方)时,咱们都不会记忆诸如64.41.64.172这样的数字,而是记忆诸如gnosis.cx这样的名称。为了找到与某个特定主机名称相关联的IP地址,通常都使用域名服务器(DNS),可是有时会首先使用本地查找(常常是经过/etc/hosts的内容)。对于本教程,咱们将通常地假设有一个IP地址可用,不过下面讨论编写名称查找代码。
windows
(3)主机名称解析
命令行实用程序nslookup能够被用来根据符号名称查找主机IP地址。实际上,许多常见的实用程序,好比ping或者网络配置工具,也会顺便作一样的事情。可是以编程方式作这样的事情很简单。设计模式
======================TCP/IP======================
应用层: 它只负责产生相应格式的数据 ssh ftp nfs cifs dns http smtp pop3
-----------------------------------
传输层: 定义数据传输的两种模式:
TCP(传输控制协议:面向链接,可靠的,效率相对不高)
UDP(用户数据报协议:非面向链接,不可靠的,但效率高)
-----------------------------------
网络层: 链接不一样的网络如以太网、令牌环网
IP (路由,分片) 、ICMP、 IGMP
ARP ( 地址解析协议,做用是将IP解析成MAC )
-----------------------------------
数据链路层: 以太网传输
-----------------------------------
物理层: 主要任务是规定各类传输介质和接口与传输信号相关的一些特性
-----------------------------------
数组
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。浏览器
TCP socket 因为在通向前须要创建链接,因此其模式较 UDP socket 负责些。缓存
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。如图:
UDP Socket图:
UDP socket server 端代码在进行bind后,无需调用listen方法。
TCP/IP协议族包括运输层、网络层、链路层,
而socket所在位置如图,Socket是应用层与TCP/IP协议族通讯的中间软件抽象层。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,均可以用“打开open –> 读写write/read –> 关闭close”模式来操做。Socket就是该模式的一个实现,socket便是一种特殊的文件,一些socket函数就是对其进行的操做(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通讯的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来讲,一组简单的接口就是所有,让Socket去组织数据,以符合指定的协议。
注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,咱们大量用的都是经过socket实现的。
Socket是网络编程的一个抽象概念。一般咱们用一个Socket表示“打开了一个网络连接”,而打开一个Socket须要知道目标计算机的IP地址和端口号,再指定协议类型便可。
咱们知道tcp创建链接要进行“三次握手”,即交换三个分组。大体流程以下:
只有就完了三次握手,可是这个三次握手发生在socket的那几个函数中呢?请看下图:
图一、socket中发送的TCP三次握手
从图中能够看出,当客户端调用connect时,触发了链接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到链接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1以后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,链接创建。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
上面介绍了socket中TCP的三次握手创建过程,及其涉及的socket函数。如今咱们介绍socket中的四次握手释放链接的过程,请看下图:
图二、socket中发送的TCP四次握手
图示过程以下:
这样每一个方向上都有一个FIN和ACK。
Python 提供了两个级别访问的网络服务。:
Socket又称"套接字",应用程序一般经过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间能够通信。
socket和file的区别:
服务器端先初始化Socket,而后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端链接。在这时若是有个客户端初始化一个Socket,而后链接服务器(connect),若是链接成功,这时客户端与服务器端的链接就创建了。客户端发送数据请求,服务器端接收请求并处理请求,而后把回应数据发送给客户端,客户端读取数据,最后关闭链接,一次交互结束。
Python 中,咱们用 socket()函数来建立套接字,语法格式以下:
socket.socket([family[, type[, proto]]])
SOCK_STREAM
或SOCK_DGRAM
咱们使用 socket 模块的 socket 函数来建立一个 socket 对象。socket 对象能够经过调用其余函数来设置一个 socket 服务。
如今咱们能够经过调用 bind(hostname, port) 函数来指定服务的 port(端口)。
接着,咱们调用 socket 对象的 accept 方法。该方法等待客户端的链接,并返回 connection 对象,表示已链接到客户端。
完整代码以下:
#!/usr/bin/python3 # 文件名:server.py # 导入 socket、sys 模块 import socket import sys # 建立 socket 对象 serversocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM) # 获取本地主机名 host = socket.gethostname() port = 9999 # 绑定端口 serversocket.bind((host, port)) # 设置最大链接数,超事后排队 serversocket.listen(5) while True: # 创建客户端链接 clientsocket,addr = serversocket.accept() print("链接地址: %s" % str(addr)) msg='欢迎访问python教程!'+ "\r\n" clientsocket.send(msg.encode('utf-8')) clientsocket.close()
接下来咱们写一个简单的客户端实例链接到以上建立的服务。端口号为 12345。
socket.connect(hosname, port ) 方法打开一个 TCP 链接到主机为 hostname 端口为 port 的服务商。链接后咱们就能够从服务端后期数据,记住,操做完成后须要关闭链接。
完整代码以下:
#!/usr/bin/python3 # 文件名:client.py # 导入 socket、sys 模块 import socket import sys # 建立 socket 对象 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 获取本地主机名 host = socket.gethostname() # 设置端口好 port = 9999 # 链接服务,指定主机和端口 s.connect((host, port)) # 接收小于 1024 字节的数据 msg = s.recv(1024) s.close() print (msg.decode('utf-8'))
先执行server端,而后打开client端就能看到结果
大多数链接都是可靠的TCP链接。建立TCP链接时,主动发起链接的叫客户端,被动响应链接的叫服务器。
举个例子,当咱们在浏览器中访问新浪时,咱们本身的计算机就是客户端,浏览器会主动向新浪的服务器发起链接。若是一切顺利,新浪的服务器接受了咱们的链接,一个TCP链接就创建起来的,后面的通讯就是发送网页内容了。
因此,咱们要建立一个基于TCP链接的Socket,能够这样作:
# 导入socket库: import socket # 建立一个socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建链接: s.connect(('www.sina.com.cn', 80))
建立Socket
时,AF_INET
指定使用IPv4协议,若是要用更先进的IPv6,就指定为AF_INET6
。SOCK_STREAM
指定使用面向流的TCP协议,这样,一个Socket
对象就建立成功,可是尚未创建链接。
客户端要主动发起TCP链接,必须知道服务器的IP地址和端口号。新浪网站的IP地址能够用域名www.sina.com.cn
自动转换到IP地址,可是怎么知道新浪服务器的端口号呢?
答案是做为服务器,提供什么样的服务,端口号就必须固定下来。因为咱们想要访问网页,所以新浪提供网页服务的服务器必须把端口号固定在80
端口,由于80
端口是Web服务的标准端口。其余服务都有对应的标准端口号,例如SMTP服务是25
端口,FTP服务是21
端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,能够任意使用。
所以,咱们链接新浪服务器的代码以下:
s.connect(('www.sina.com.cn', 80))
注意参数是一个tuple
,包含地址和端口号。
创建TCP链接后,咱们就能够向新浪服务器发送请求,要求返回首页的内容:
# 发送数据: s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
TCP链接建立的是双向通道,双方均可以同时给对方发数据。可是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。
发送的文本格式必须符合HTTP标准,若是格式没问题,接下来就能够接收新浪服务器返回的数据了:
接收数据时,调用recv(max)
方法,一次最多接收指定的字节数,所以,在一个while循环中反复接收,直到recv()
返回空数据,表示接收完毕,退出循环。
当咱们接收完数据后,调用close()
方法关闭Socket,这样,一次完整的网络通讯就结束了:
# 关闭链接: s.close()
接收到的数据包括HTTP头和网页自己,咱们只须要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:
header, html = data.split(b'\r\n\r\n', 1) print(header.decode('utf-8')) # 把接收的数据写入文件: with open('sina.html', 'wb') as f: f.write(html)
如今,只须要在浏览器中打开这个sina.html
文件,就能够看到新浪的首页了。
和客户端编程相比,服务器编程就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其余客户端的链接。若是某个客户端链接过来了,服务器就与该客户端创建Socket链接,随后的通讯就靠这个Socket链接了。
因此,服务器会打开固定端口(好比80)监听,每来一个客户端链接,就建立该Socket链接。因为服务器会有大量来自客户端的链接,因此,服务器要可以区分一个Socket链接是和哪一个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来惟一肯定一个Socket。
可是服务器还须要同时响应多个客户端的请求,因此,每一个链接都须要一个新的进程或者新的线程来处理,不然,服务器一次就只能服务一个客户端了。
咱们来编写一个简单的服务器程序,它接收客户端链接,把客户端发过来的字符串加上Hello
再发回去。
首先,建立一个基于IPv4和TCP协议的Socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
而后,咱们要绑定监听的地址和端口。服务器可能有多块网卡,能够绑定到某一块网卡的IP地址上,也能够用0.0.0.0
绑定到全部的网络地址,还能够用127.0.0.1
绑定到本机地址。127.0.0.1
是一个特殊的IP地址,表示本机地址,若是绑定到这个地址,客户端必须同时在本机运行才能链接,也就是说,外部的计算机没法链接进来。
端口号须要预先指定。由于咱们写的这个服务不是标准服务,因此用9999
这个端口号。请注意,小于1024
的端口号必需要有管理员权限才能绑定:
# 监听端口: s.bind(('127.0.0.1', 9999))
紧接着,调用listen()
方法开始监听端口,传入的参数指定等待链接的最大数量:
s.listen(5) print('Waiting for connection...')
接下来,服务器程序经过一个永久循环来接受来自客户端的链接,accept()
会等待并返回一个客户端的链接:
while True: # 接受一个新链接: sock, addr = s.accept() # 建立新线程来处理TCP链接: t = threading.Thread(target=tcplink, args=(sock, addr)) t.start()
每一个链接都必须建立新线程(或进程)来处理,不然,单线程在处理链接的过程当中,没法接受其余客户端的链接:
def tcplink(sock, addr): print('Accept new connection from %s:%s...' % addr) sock.send(b'Welcome!') while True: data = sock.recv(1024) time.sleep(1) if not data or data.decode('utf-8') == 'exit': break sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8')) sock.close() print('Connection from %s:%s closed.' % addr)
链接创建后,服务器首先发一条欢迎消息,而后等待客户端数据,并加上Hello
再发送给客户端。若是客户端发送了exit
字符串,就直接关闭链接。
要测试这个服务器程序,咱们还须要编写一个客户端程序:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建链接: s.connect(('127.0.0.1', 9999)) # 接收欢迎消息: print(s.recv(1024).decode('utf-8')) for data in [b'Michael', b'Tracy', b'Sarah']: # 发送数据: s.send(data) print(s.recv(1024).decode('utf-8')) s.send(b'exit') s.close()
咱们须要打开两个命令行窗口,一个运行服务器程序,另外一个运行客户端程序,就能够看到效果了:
TCP是创建可靠链接,而且通讯双方均可以以流的形式发送数据。相对TCP,UDP则是面向无链接的协议。
使用UDP协议时,不须要创建链接,只须要知道对方的IP地址和端口号,就能够直接发数据包。可是,能不能到达就不知道了。
虽然用UDP传输数据不可靠,但它的优势是和TCP比,速度快,对于不要求可靠到达的数据,就可使用UDP协议。
咱们来看看如何经过UDP协议传输数据。和TCP相似,使用UDP的通讯双方也分为客户端和服务器。服务器首先须要绑定端口:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定端口: s.bind(('127.0.0.1', 9999))
建立Socket时,SOCK_DGRAM
指定了这个Socket的类型是UDP。绑定端口和TCP同样,可是不须要调用listen()
方法,而是直接接收来自任何客户端的数据:
print 'Bind UDP on 9999...' while True: # 接收数据: data, addr = s.recvfrom(1024) print 'Received from %s:%s.' % addr s.sendto('Hello, %s!' % data, addr)
recvfrom()
方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()
就能够把数据用UDP发给客户端。
注意这里省掉了多线程,由于这个例子很简单。
客户端使用UDP时,首先仍然建立基于UDP的Socket,而后,不须要调用connect()
,直接经过sendto()
给服务器发数据:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in ['Michael', 'Tracy', 'Sarah']: # 发送数据: s.sendto(data, ('127.0.0.1', 9999)) # 接收数据: print s.recv(1024) s.close()
从服务器接收数据仍然调用recv()
方法。
UDP的使用与TCP相似,可是不须要创建链接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口能够各自绑定。
Python 提供了两个级别访问的网络服务。:
函数 | 描述 |
---|---|
服务器端套接字 | |
s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 |
s.listen() | 开始TCP监听。backlog指定在拒绝链接以前,操做系统能够挂起的最大链接数量。该值至少为1,大部分应用程序设为5就能够了。 |
s.accept() | 被动接受TCP客户端链接,(阻塞式)等待链接的到来 |
客户端套接字 | |
s.connect() | 主动初始化TCP服务器链接,。通常address的格式为元组(hostname,port),若是链接出错,返回socket.error错误。 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数 | |
s.recv() | 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其余信息,一般能够忽略。 |
s.send() | 发送TCP数据,将string中的数据发送到链接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 |
s.sendall() | 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到链接的套接字,但在返回以前会尝试发送全部数据。成功返回None,失败则抛出异常。 |
s.recvform() | 接收UDP数据,与recv()相似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
s.sendto() | 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
s.close() | 关闭套接字 |
s.getpeername() | 返回链接套接字的远程地址。返回值一般是元组(ipaddr,port)。 |
s.getsockname() | 返回套接字本身的地址。一般是一个元组(ipaddr,port) |
s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
s.settimeout(timeout) | 设置套接字操做的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。通常,超时期应该在刚建立套接字时设置,由于它们可能用于链接的操做(如connect()) |
s.gettimeout() | 返回当前超时期的值,单位是秒,若是没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 若是flag为0,则将套接字设为非阻塞模式,不然将套接字设为阻塞模式(默认值)。非阻塞模式下,若是调用recv()没有发现任何数据,或send()调用没法当即发送数据,那么将引发socket.error异常。 |
s.makefile() | 建立一个与该套接字相关连的文件 |
server端:
#!/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() socket server
client:
#!/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 client
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()
Socket 对象(内建)方法:
服务器端 s.bind() # 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 s.listen() # 开始TCP监听。backlog指定在拒绝链接以前,操做系统能够挂起的最大链接数量。该值至少为1,大部分应用程序设为5就能够了。 s.accept() # 被动接受TCP客户端链接,(阻塞式)等待链接的到来 客户端 s.connect() # 主动初始化TCP服务器链接,。通常address的格式为元组(hostname,port),若是链接出错,返回socket.error错误。 s.connect_ex() # connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 公共用途的函数 s.recv() # 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其余信息,一般能够忽略。 s.send() # 发送TCP数据,将string中的数据发送到链接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 s.sendall() # 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到链接的套接字,但在返回以前会尝试发送全部数据。成功返回None,失败则抛出异常。 s.close() # 关闭套接字 s.recvform() # 接收UDP数据,与recv()相似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 s.sendto() # 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 s.getpeername() # 返回链接套接字的远程地址。返回值一般是元组(ipaddr,port)。 s.getsockname() # 返回套接字本身的地址。一般是一个元组(ipaddr,port) s.setsockopt(level,optname,value) # 设置给定套接字选项的值。 s.getsockopt(level,optname[.buflen]) # 返回套接字选项的值。 s.settimeout(timeout) # 设置套接字操做的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。通常,超时期应该在刚建立套接字时设置,由于它们可能用于链接的操做(如connect()) s.gettimeout() # 返回当前超时期的值,单位是秒,若是没有设置超时期,则返回None。 s.fileno() # 返回套接字的文件描述符。 s.setblocking(flag) # 若是flag为0,则将套接字设为非阻塞模式,不然将套接字设为阻塞模式(默认值)。非阻塞模式下,若是调用recv()没有发现任何数据,或send()调用没法当即发送数据,那么将引发socket.error异常。 s.makefile() # 建立一个与该套接字相关连的文件
更多功能
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 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
UDP Demo
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() UDP Demo
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,失败则抛出异常。
内部经过递归调用send,将全部内容发送出去。
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于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,(host,port) = sk.recvfrom(1024) print(data,host,port) sk.sendto(bytes('ok', encoding='utf-8'), (host,port)) #客户端 import socket ip_port = ('127.0.0.1',9999) sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0) while True: inp = input('数据:').strip() if inp == 'exit': break sk.sendto(bytes(inp, encoding='utf-8'),ip_port) data = sk.recvfrom(1024) print(data) sk.close() UDP
实例:智能机器人
服务端:
#!/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() 客户端
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
注意:网络操做、文件操做、终端操做等均属于IO操做,对于windows只支持Socket操做,其余系统支持其余IO操做,可是没法检测 普通文件操做 自动上次读取是否已经变化。
对于select方法:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间) 参数: 可接受四个参数(前三个必须) 返回值:三个列表 select方法用来监视文件句柄,若是句柄发生变化,则获取该句柄。 一、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 二、当 参数2 序列中含有句柄时,则将该序列中全部的句柄添加到 返回值2 序列中 三、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 四、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 当 超时时间 = 1时,那么若是监听的句柄均无任何变化,则select会阻塞 1 秒,以后返回三个空列表,若是监听的句柄有变化,则直接执行。
利用select监听终端操做实例:
#!/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() 利用select监听终端操做实例
利用select实现伪同时处理多个socket客户端请求:服务端
#!/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() 利用select实现伪同时处理多个Socket客户端请求:服务端
利用select实现伪同时处理多个socket客户端请求:客户端
#!/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() 利用select实现伪同时处理多个Socket客户端请求:客户端
此处的Socket服务端相比与原生的Socket,他支持当某一个请求再也不发送数据时,服务器端不会等待而是能够去处理其余请求的数据。可是,若是每一个请求的耗时比较长时,select版本的服务器端也没法完成同时操做。
基于select实现socket服务端
#!/usr/bin/env python #coding:utf8 ''' 服务器的实现 采用select的方式 ''' import select import socket import sys import Queue #建立套接字并设置该套接字为非阻塞模式 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.setblocking(0) #绑定套接字 server_address = ('localhost',10000) print >>sys.stderr,'starting up on %s port %s'% server_address server.bind(server_address) #将该socket变成服务模式 #backlog等于5,表示内核已经接到了链接请求,但服务器尚未调用accept进行处理的链接个数最大为5 #这个值不能无限大,由于要在内核中维护链接队列 server.listen(5) #初始化读取数据的监听列表,最开始时但愿从server这个套接字上读取数据 inputs = [server] #初始化写入数据的监听列表,最开始并无客户端链接进来,因此列表为空 outputs = [] #要发往客户端的数据 message_queues = {} while inputs: print >>sys.stderr,'waiting for the next event' #调用select监听全部监听列表中的套接字,并将准备好的套接字加入到对应的列表中 readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字 若是是文件呢? #监控文件句柄有某一处发生了变化 可写 可读 异常属于Linux中的网络编程 #属于同步I/O操做,属于I/O复用模型的一种 #rlist--等待到准备好读 #wlist--等待到准备好写 #xlist--等待到一种异常 #处理可读取的套接字 ''' 若是server这个套接字可读,则说明有新连接到来 此时在server套接字上调用accept,生成一个与客户端通信的套接字 并将与客户端通信的套接字加入inputs列表,下一次能够经过select检查链接是否可读 而后在发往客户端的缓冲中加入一项,键名为:与客户端通信的套接字,键值为空队列 select系统调用是用来让咱们的程序监视多个文件句柄(file descrīptor)的状态变化的。程序会停在select这里等待, 直到被监视的文件句柄有某一个或多个发生了状态改变 ''' ''' 若可读的套接字不是server套接字,有两种状况:一种是有数据到来,另外一种是连接断开 若是有数据到来,先接收数据,而后将收到的数据填入往客户端的缓存区中的对应位置,最后 将于客户端通信的套接字加入到写数据的监听列表: 若是套接字可读.但没有接收到数据,则说明客户端已经断开。这时须要关闭与客户端链接的套接字 进行资源清理 ''' for s in readable: if s is server: connection,client_address = s.accept() print >>sys.stderr,'connection from',client_address connection.setblocking(0)#设置非阻塞 inputs.append(connection) message_queues[connection] = Queue.Queue() else: data = s.recv(1024) if data: print >>sys.stderr,'received "%s" from %s'% \ (data,s.getpeername()) message_queues[s].put(data) if s not in outputs: outputs.append(s) else: print >>sys.stderr,'closing',client_address if s in outputs: outputs.remove(s) inputs.remove(s) s.close() del message_queues[s] #处理可写的套接字 ''' 在发送缓冲区中取出响应的数据,发往客户端。 若是没有数据须要写,则将套接字从发送队列中移除,select中再也不监视 ''' for s in writable: try: next_msg = message_queues[s].get_nowait() except Queue.Empty: print >>sys.stderr,' ',s,getpeername(),'queue empty' outputs.remove(s) else: print >>sys.stderr,'sending "%s" to %s'% \ (next_msg,s.getpeername()) s.send(next_msg) #处理异常状况 for s in exceptional: for s in exceptional: print >>sys.stderr,'exception condition on',s.getpeername() inputs.remove(s) if s in outputs: outputs.remove(s) s.close() del message_queues[s] 基于select实现socket服务端
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每一个客户端请求链接到服务器时,Socket服务端都会在服务器是建立一个“线程”或者“进程” 专门负责处理当前客户端的全部请求。
ThreadingTCPServer
ThreadingTCPServer实现的Soket服务器内部会为每一个client建立一个 “线程”,该线程用来和客户端进行交互。
一、ThreadingTCPServer基础
使用ThreadingTCPServer:
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() SocketServer实现服务器
客户端:
#!/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() 客户端
ThreadingTCPServer源码剖析
ThreadingTCPServer的类图关系以下:
内部调用流程为:
ThreadingTCPServer相关源码:
BaseServer
class BaseServer: """Base class for server classes. Methods for the caller: - __init__(server_address, RequestHandlerClass) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you do not use serve_forever() - fileno() -> int # for select() Methods that may be overridden: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - server_close() - process_request(request, client_address) - shutdown_request(request) - close_request(request) - handle_error() Methods for derived classes: - finish_request(request, client_address) Class variables that may be overridden by derived classes or instances: - timeout - address_family - socket_type - allow_reuse_address Instance variables: - RequestHandlerClass - socket """ timeout = None def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False def server_activate(self): """Called by constructor to activate the server. May be overridden. """ pass def serve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: while not self.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval) if self in r: self._handle_request_noblock() finally: self.__shutdown_request = False self.__is_shut_down.set() def shutdown(self): """Stops the serve_forever loop. Blocks until the loop has finished. This must be called while serve_forever() is running in another thread, or it will deadlock. """ self.__shutdown_request = True self.__is_shut_down.wait() # The distinction between handling, getting, processing and # finishing a request is fairly arbitrary. Remember: # # - handle_request() is the top-level call. It calls # select, get_request(), verify_request() and process_request() # - get_request() is different for stream or datagram sockets # - process_request() is the place that may fork a new process # or create a new thread to finish the request # - finish_request() instantiates the request handler class; # this constructor will handle the request all by itself def handle_request(self): """Handle one request, possibly blocking. Respects self.timeout. """ # Support people who used socket.settimeout() to escape # handle_request before self.timeout was available. timeout = self.socket.gettimeout() if timeout is None: timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) fd_sets = _eintr_retry(select.select, [self], [], [], timeout) if not fd_sets[0]: self.handle_timeout() return self._handle_request_noblock() def _handle_request_noblock(self): """Handle one request, without blocking. I assume that select.select has returned that the socket is readable before this function was called, so there should be no risk of blocking in get_request(). """ try: request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.shutdown_request(request) def handle_timeout(self): """Called if no new request arrives within self.timeout. Overridden by ForkingMixIn. """ pass def verify_request(self, request, client_address): """Verify the request. May be overridden. Return True if we should proceed with this request. """ return True def process_request(self, request, client_address): """Call finish_request. Overridden by ForkingMixIn and ThreadingMixIn. """ self.finish_request(request, client_address) self.shutdown_request(request) def server_close(self): """Called to clean-up the server. May be overridden. """ pass def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" self.RequestHandlerClass(request, client_address, self) def shutdown_request(self, request): """Called to shutdown and close an individual request.""" self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" pass def handle_error(self, request, client_address): """Handle an error gracefully. May be overridden. The default is to print a traceback and continue. """ print '-'*40 print 'Exception happened during processing of request from', print client_address import traceback traceback.print_exc() # XXX But this goes to stderr! print '-'*40 BaseServer
Tcpserver
class TCPServer(BaseServer): """Base class for various socket-based server classes. Defaults to synchronous IP stream (i.e., TCP). Methods for the caller: - __init__(server_address, RequestHandlerClass, bind_and_activate=True) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you don't use serve_forever() - fileno() -> int # for select() Methods that may be overridden: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - process_request(request, client_address) - shutdown_request(request) - close_request(request) - handle_error() Methods for derived classes: - finish_request(request, client_address) Class variables that may be overridden by derived classes or instances: - timeout - address_family - socket_type - request_queue_size (only for stream sockets) - allow_reuse_address Instance variables: - server_address - RequestHandlerClass - socket """ address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override.""" BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. May be overridden. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. May be overridden. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. May be overridden. """ self.socket.close() def fileno(self): """Return socket file number. Interface required by select(). """ return self.socket.fileno() def get_request(self): """Get the request and client address from the socket. May be overridden. """ return self.socket.accept() def shutdown_request(self, request): """Called to shutdown and close an individual request.""" try: #explicitly shutdown. socket.close() merely releases #the socket and waits for GC to perform the actual close. request.shutdown(socket.SHUT_WR) except socket.error: pass #some platforms may raise ENOTCONN here self.close_request(request) def close_request(self, request): """Called to clean up an individual request.""" request.close() TCPServer
ThreadingMixIn
class ThreadingMixIn: """Mix-in class to handle each request in a new thread.""" # Decides how threads will act upon termination of the # main process daemon_threads = False def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.shutdown_request(request) except: self.handle_error(request, client_address) self.shutdown_request(request) def process_request(self, request, client_address): """Start a new thread to process the request.""" t = threading.Thread(target = self.process_request_thread, args = (request, client_address)) t.daemon = self.daemon_threads t.start() ThreadingMixIn
ThreadingTCPServer
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
Socket套接字的概念
Socket(翻译为套接字, 我以为很挫)
,是操做系统内核中的一个数据结构,它是网络中的节点进行相互通讯的门户
。它是网络进程的ID。网络通讯,归根到底仍是进程间的通讯(不一样计算机上的进程间通讯, 又称进程间通讯, IP协议进行的主要是端到端通讯)。在网络中,每个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通讯时,首先要肯定各自所在的网络节点的网络地址。可是,网络地址只能肯定进程所在的计算机,而一台计算机上极可能同时运行着多个进程,因此仅凭网络地址还不能肯定究竟是和网络中的哪个进程进行通讯,所以套接口中还须要包括其余的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。
因此,使用端口号和网络地址的组合能够惟一的肯定整个网络中的一个网络进程.
端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些经常使用的应用程序固定使用的“周知的端口”,其值通常为0~1023, 用户自定义端口号通常大于等于1024, 我比较喜欢用8888
每个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个相似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的链接创建、数据传输等操做都是经过socket来实现的。
流程描述:
# 流程描述: # # 1 服务器根据地址类型(ipv4,ipv6)、socket类型、协议建立socket # # 2 服务器为socket绑定ip地址和端口号 # # 3 服务器socket监听端口号请求,随时准备接收客户端发来的链接,这时候服务器的socket并无被打开 # # 4 客户端建立socket # # 5 客户端打开socket,根据服务器ip地址和端口号试图链接服务器socket # # 6 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回链接信息。这时候socket进入阻塞状态, # 所谓阻塞即accept()方法一直等到客户端返回链接信息后才返回,开始接收下一个客户端链接请求 # # 7 客户端链接成功,向服务器发送链接状态信息 # # 8 服务器accept方法返回,链接成功 # # 9 客户端向socket写入信息(或服务端向socket写入信息) # # 10 服务器读取信息(客户端读取信息) # # 11 客户端关闭 # # 12 服务器端关闭
套接字格式:
socket(family,type[,protocal]) 使用给定的地址族、套接字类型、协议编号(默认为0)来建立套接字。
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) |
socket类型在Liunx和Python是同样的, 只是Python中的类型都定义在
socket模块
中, 调用方式socket.SOCK_XXXX
用于TCP通讯
流式套接字提供可靠的、面向链接的通讯流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
用于UDP通讯
数据报套接字定义了一种无链接的服务,数据经过相互独立的报文进行传输,是无序的,而且不保证是可靠、无差错的。它使用数据报协议UDP
用于新的网络协议实现的测试等
原始套接字,普通的套接字没法处理ICMP、IGMP等网络报文,而SOCK_RAW能够, 其次,SOCK_RAW也能够处理特殊的IPv4报文;此外,利用原始套接字,能够经过IP_HDRINCL套接字选项由用户构造IP头。
TCP通讯的基本步骤以下:
服务端:socket---bind---listen---while(True){---accept---recv---send----}---close
客户端:socket----------------------------------connect---send---recv-------close
socket函数
使用给定的地址族、套接字类型、协议编号(默认为0)来建立套接字
#Linux int socket(int domain, int type, int protocol); domain:AF_INET:Ipv4网络协议 AF_INET6:IPv6网络协议 type : tcp:SOCK_STREAM udp:SOCK_DGRAM protocol : 指定socket所使用的传输协议编号。一般为0. 返回值:成功则返回套接口描述符,失败返回-1。 #python socket.socket([family[, type[, proto]]]) family : AF_INET (默认ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系统进程间通讯). type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . protocol : 通常为0或者默认
若是socket建立失败会抛出一个socket.error异常
bind函数
将套接字绑定到地址, python下,以元组(host,port)的形式表示地址, Linux下使用sockaddr_in
结构体指针
#Linux int bind(int sockfd, struct sockaddr * my_addr, int addrlen); sockfd : 前面socket()的返回值 my_addr : 结构体指针变量 ##### struct sockaddr_in //经常使用的结构体 { unsigned short int sin_family; //即为sa_family AF_INET uint16_t sin_port; //为使用的port编号 struct in_addr sin_addr; //为IP地址 unsigned char sin_zero[8]; //未使用 }; struct in_addr { uint32_t s_addr; }; #### addrlen : sockaddr的结构体长度。一般是计算sizeof(struct sockaddr); 返回值:成功则返回0,失败返回-1 #python s.bind(address) s为socket.socket()返回的套接字对象 address为元组(host,port) host: ip地址, 为一个字符串 post: 自定义主机号, 为整型
listen函数
使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的链接请求。若是客户端有链接请求,端口就会接受这个链接
#Linux int listen(int sockfd,int backlog); sockfd : 为前面socket的返回值. backlog : 指定同时能处理的最大链接要求,一般为10或者5。最大值可设至128 返回值:成功则返回0,失败返回-1 #python s.listen(backlog) s为socket.socket()返回的套接字对象 backlog : 操做系统能够挂起的最大链接数量。该值至少为1,大部分应用程序设为5就能够了
accept函数
接受远程计算机的链接请求,创建起与客户机之间的通讯链接。服务器处于监听状态时,若是某时刻得到客户机的链接请求,此时并非当即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的链接请求。
#Linux int accept(int s,struct sockaddr * addr,int * addrlen); sockfd : 为前面socket的返回值. addr : 为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。 addrlen : 表示结构体的长度,为整型指针 返回值:成功则返回新的socket处理代码new_fd,失败返回-1 #python s.accept() s为socket.socket()返回的套接字对象 返回(conn,address),其中conn是新的套接字对象,能够用来接收和发送数据。address是链接客户端的地址
connect函数
用来请求链接远程服务器
#Linux int connect (int sockfd,struct sockaddr * serv_addr,int addrlen); sockfd : 为前面socket的返回值. serv_addr : 为结构体指针变量,存储着远程服务器的IP与端口号信息 addrlen : 表示结构体变量的长度 返回值:成功则返回0,失败返回-1 #python s.connect(address) s为socket.socket()返回的套接字对象 address : 格式为元组(hostname,port),若是链接出错,返回socket.error错误
接收远端主机传来的数据
recv函数
#Linux int recv(int sockfd,void *buf,int len,unsigned int flags); sockfd : 为前面accept的返回值.也就是新的套接字。 buf : 表示缓冲区 len : 表示缓冲区的长度 flags : 一般为0 返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1 #python s.recv(bufsize[,flag]) s为socket.socket()返回的套接字对象 bufsize : 指定要接收的数据大小 flag : 提供有关消息的其余信息,一般能够忽略 返回值为数据以字符串形式
send函数
发送数据给指定的远端主机
#Linux int send(int s,const void * msg,int len,unsigned int flags); sockfd : 为前面socket的返回值. msg : 通常为常量字符串 len : 表示长度 flags : 一般为0 返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1 #python s.send(string[,flag]) s为socket.socket()返回的套接字对象 string : 要发送的字符串数据 flag : 提供有关消息的其余信息,一般能够忽略 返回值是要发送的字节数量,该数量可能小于string的字节大小。 s.sendall(string[,flag]) #完整发送TCP数据。将string中的数据发送到链接的套接字,但在返回以前会尝试发送全部数据。 返回值 : 成功返回None,失败则抛出异常。
close函数
关闭套接字
#Linux int close(int fd); fd : 为前面的sockfd 返回值:若文件顺利关闭则返回0,发生错误时返回-1 #python s.close() s为socket.socket()返回的套接字对象
一个简单的回显服务器和客户端模型, 客户端发出的数据, 服务器会回显到客户端的终端上(只是一个简单的模型, 没考虑错误处理等问题)
#服务器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket #socket模块 import commands #执行系统命令模块 BUF_SIZE = 1024 #设置缓冲区大小 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用 server.bind(server_addr) #绑定地址 server.listen(5) #监听, 最大监听数为5 while True: client, client_addr = server.accept() #接收TCP链接, 并返回新的套接字和地址 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #从客户端接收数据 print data client.sendall(data) #发送数据到客户端 server.close() #客户端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket BUF_SIZE = 1024 #设置缓冲区的大小 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象 client.connect(server_addr) #要链接的服务器地址 while True: data = raw_input("Please input some string > ") client.sendall(data) #发送数据到服务器 data = client.recv(BUF_SIZE) #从服务器端接收数据 print data client.close()
在进行网络编程时, 最好使用大量的错误处理, 可以尽可能的发现错误, 也可以使代码显得更加严谨
#服务器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import sys import socket #socket模块 BUF_SIZE = 1024 #设置缓冲区大小 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 try : server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Created!" server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用 try : server.bind(server_addr) #绑定地址 except socket.error, msg : print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() print "Socket Bind!" server.listen(5) #监听, 最大监听数为5 print "Socket listening" while True: client, client_addr = server.accept() #接收TCP链接, 并返回新的套接字和地址, 阻塞函数 print 'Connected by', client_addr while True : data = client.recv(BUF_SIZE) #从客户端接收数据 print data client.sendall(data) #发送数据到客户端 server.close() #客户端 #!/usr/bin/env python # -*- coding:utf-8 -*- import sys import socket BUF_SIZE = 1024 #设置缓冲区的大小 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 try : client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象 except socket.error, msg : print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1] sys.exit() client.connect(server_addr) #要链接的服务器地址 while True: data = raw_input("Please input some string > ") if not data : print "input can't empty, Please input again.." continue client.sendall(data) #发送数据到服务器 data = client.recv(BUF_SIZE) #从服务器端接收数据 print data client.close()
UDP通讯流程图以下:
服务端:socket---bind---recvfrom---sendto---close
客户端:socket----------sendto---recvfrom---close
sendto()函数
发送UDP数据, 将数据发送到套接字
#Linux int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); sockfd : 为前面socket的返回值. msg : 通常为常量字符串 len : 表示长度 flags : 一般为0 to : 表示目地机的IP地址和端口号信息, 表示地址的结构体 tolen : 经常被赋值为sizeof (struct sockaddr) 返回值 : 返回实际发送的数据字节长度或在出现发送错误时返回-1。 #Python s.sendto(string[,flag],address) s为socket.socket()返回的套接字对象 address : 指定远程地址, 形式为(ipaddr,port)的元组 flag : 提供有关消息的其余信息,一般能够忽略 返回值 : 发送的字节数。
recvfrom()函数
接受UDP套接字的数据, 与recv()相似
#Linux int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); sockfd : 为前面socket的返回值. msg : 通常为常量字符串 len : 表示长度 flags : 一般为0 from :是一个struct sockaddr类型的变量,该变量保存链接机的IP地址及端口号 fromlen : 常置为sizeof (struct sockaddr)。 返回值 : 返回接收到的字节数或当出现错误时返回-1,并置相应的errno。 #Python s.recvfrom(bufsize[.flag]) 返回值 : (data,address)元组, 其中data是包含接收数据的字符串,address是发送数据的套接字地址 bufsize : 指定要接收的数据大小 flag : 提供有关消息的其余信息,一般能够忽略
#服务器端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket BUF_SIZE = 1024 #设置缓冲区大小 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象 server.bind(server_addr) #套接字绑定IP和端口 while True : print "waitting for data" data, client_addr = server.recvfrom(BUF_SIZE) #从客户端接收数据 print 'Connected by', client_addr, ' Receive Data : ', data server.sendto(data, client_addr) #发送数据给客户端 server.close() #客户端 #!/usr/bin/env python # -*- coding:utf-8 -*- import socket import struct BUF_SIZE = 1024 #设置缓冲区 server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象 while True : data = raw_input('Please Input data > ') client.sendto(data, server_addr) #向服务器发送数据 data, addr = client.recvfrom(BUF_SIZE) #从服务器接收数据 print "Data : ", data client.close()
s.getpeername() #返回链接套接字的远程地址。返回值一般是元组(ipaddr,port)。 s.getsockname() #返回套接字本身的地址。一般是一个元组(ipaddr,port) s.setsockopt(level,optname,value) #设置给定套接字选项的值。 s.getsockopt(level,optname[.buflen]) #返回套接字选项的值。 s.settimeout(timeout) #设置套接字操做的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。通常,超时期应该在刚建立套接字时设置,由于它们可能用于链接的操做(如connect()) s.gettimeout() #返回当前超时期的值,单位是秒,若是没有设置超时期,则返回None。 s.fileno() #返回套接字的文件描述符。 s.setblocking(flag) #若是flag为0,则将套接字设为非阻塞模式,不然将套接字设为阻塞模式(默认值)。非阻塞模式下,若是调用recv()没有发现任何数据,或send()调用没法当即发送数据,那么将引发socket.error异常。 s.makefile() #建立一个与该套接字相关连的文件