壹 简介
1.1 为什么要用Socekt?
有一个问题,网络中进程之间如何通信?
本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类(这里这些可以不用理解):
- 消息传递(管道、FIFO、消息队列)
- 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
- 共享内存(匿名的和具名的)
- 远程过程调用(Solaris门和Sun RPC) 但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的ip地址可以唯一标识网络中的主机,而传输层的协议+端口可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
要搞清楚Sockert是什么首先我们需要明白一些概念:
- TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
- UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
这里有一张图,表明了这些协议的关系。TCP/IP协议族包括运输层、网络层、链路层。
注意:在这里Socket确实是不在这些协议层里面的,而是本人为了理解Socket加上去的!!!
1.2 Socket是什么呢?
理解:用发快递打比方。在A地的A要寄一个物品给在B地的B。选择一个快递公司,就是选择了一种传输物品的规范。 因为不同的快递公司传输物品的具体方案不同。A作为一个 寄件人, 他不需要知道快递公司传输物品的方案的所有细节 。他只要知道如何把物品给快递公司上门的收件人,就行了。B作为一个收件人, 他也不需要知道快递公司传输物品的方案的所有细节。他只要知道,如何从快递公司的收件人 收物品 就行了。
对应到 软件开发上,收发信息的 程序进程 就像 发件人 和 收件人 ;收发的 信息 就像 快递传输的 物品 ;具体信息的传输路径(中间经过哪些路由器)和传输的方法(使用什么协议)就像 快递公司的运输流程。
同样的,我们编写 发出信息的程序和接收信息的程序,并不需要知道 信息传输的所有细节,比如 中间经过哪些路由器,路由器之间又是如何传输的。我们作为程序员,只要知道,我们的程序如何把 所要发送的信息交给 ‘收件人’, 如何从 ‘送件人’ 手中获取信息。那么和我们的应用程序直接打交道的 ‘收件人’ 和 ‘送件人’ 到底是谁?就是操作系统 提供的 socket 编程接口。
Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些Socket函数就是对其进行的操作(读/写IO、打开、关闭),Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。基本上,Socket 是任何一种计算机网络通讯中最基础的内容。任何网络通讯都是通过 Socket 来完成的。
socket一词的起源
在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。”
就是可以理解为:两个进程,跨计算机,他俩需要通讯的话,需要通过网络对接起来。这网络对接通信的过程就是socket。
1.3 那要怎么用Socket呢?
首先要明白Socket编程是用来简化网络间通信的,而网络间的通信是非常复杂的一个过程,Socket就是将这些过程简化了。
首先我们来理解Socket编程原理。
这是Socket编程原理图:
由上图可知,Socket包括两个:服务器Socket和客户机Socket
- 服务器Socket:创建一个服务器套接字后,让它等待连接。这样它就在某个网络地址处监听。
- 客户机Socket:处理客户端套接字比处理服务器套接字简单,因为服务器套接字必须准备随时处理客户端的连接,同时还要处理多个连接,而客户机只是地连接,完成事务,断开连接。
贰 安装
Socket库是标准库,所以不需要安装!此处路过!
叁 Socket的基本操作
既然Socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。
3.1 Socket类型
Socket类型是在初始化Socket时使用的。
socket类型 | 描述 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
socket.AF_INET | 该socket网络层使用IP协议,IPV4 |
socket.AF_INET6 | IPv6 |
socket.SOCK_STREAM | 该socket传输层使用TCP协议 |
socket.SOCK_DGRAM | 该socket传输层使用UDP协议 |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
3.2 Socket函数
注意点:
1)TCP发送数据时,已建立好TCP连接,所以不需要指定地址。UDP是面向无连接的,每次发送要指定是发给谁。
2)服务端与客户端不能直接发送列表,元组,字典。需要字符串化repr(data)。
3.2.1 socket()函数:初始
作用:socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。
格式:
socket(family,type[,protocal])
参数说明:
- family:表示使用给定的地址族,例如IPV4、IPV6等。
- type:为套接字类型,例如:TCP或者UDP等。
- protocal:协议编号,默认为0,例如ICMP、ARP等。
在创建Socket时,分为TCP和UDP两种:
- 创建TCP Socket:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- 创建UDP Socket:
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
3.2.2 bind()函数:服务端
作用:将套接字绑定到地址。
格式:
s.bind(address)
参数说明:
- address:将套接字绑定到地址。address地址的格式取决于地址族,在AF_INET下,以元组(host,port)的形式表示地址。
3.2.3 listen()函数:服务端
作用:开始监听TCP传入连接。作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听socket。
格式:
s.listen(backlog)
参数说明:
- backlog:是指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
socket()函数创建的socket默认是一个主动类型的,listen()函数将socket变为被动类型的,等待客户的连接请求
3.2.4 accept()函数:服务端
作用:接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
格式:
s.accept()
3.2.5 connect()函数:客户端
作用:主动初始化 TCP 服务器连接,连接到address处的套接字,客户端调用connect()发出连接请求,服务器端就会接收到这个请求。
格式:
s.connect(address)
参数说明:
- address:服务端地址,一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误
客户端通过调用connect()函数来建立与TCP服务器的连接
3.2.6 connect_ex()函数:客户端
作用:connect() 函数的扩展版本,出错时返回出错码,而不是抛出异常。
格式:
s.connect_ex(address)
参数说明:
- address:服务端地址,一般address的格式为元组(hostname,port),如果连接出错,返回出错码,而不是抛出异常。
3.2.7 recv()函数:公共
作用:接受TCP套接字的数据,数据以字符串形式返回。bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
格式:
s.recv(bufsize[,flag])
参数说明:
- bufsize:指定要接收的最大数据量
- flag:提供有关消息的其他信息,通常可以忽略。
3.2.8 send()函数:公共
作用:发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
格式:
s.send(string[,flag])
参数说明:
- string:发送到连接的套接字的数据。
- flag:提供有关消息的其他信息,通常可以忽略。
3.2.9 sendall()函数:公共
作用:完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
格式:
s.sendall(string[,flag])
参数说明:
- string:发送到连接的套接字的数据。
- flag:提供有关消息的其他信息,通常可以忽略。
3.2.10 recvfrom()函数:公共
作用:接受UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
格式:
s.recvfrom(bufsize[.flag])
参数说明:
- bufsize:指定要接收的最大数据量
- flag:提供有关消息的其他信息,通常可以忽略。
3.2.11 sendto()函数:公共
作用:发送UDP数据。将数据发送到套接字,返回值是发送的字节数。
格式:
s.sendto(string[,flag],address)
参数说明:
- string:发送到连接的套接字的数据。
- flag:提供有关消息的其他信息,通常可以忽略。
- address:形式为(ipaddr,port)的元组,指定远程地址
3.2.12 close()函数:公共
作用:关闭套接字。
格式:
s.close()
3.2.13 getpeername()函数:公共
作用:返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
格式:
s.getpeername()
3.2.14 getsockname()函数:公共
作用:返回套接字自己的地址。通常是一个元组(ipaddr,port)。
格式:
s.getsockname()
3.2.15 setsockopt()函数:公共
作用:设置给定套接字选项的值,默认的socket选项不够用的时候,就必须要使用setsockopt来调整。
格式:
s.setsockopt(level,optname,value)
参数说明:
- level:选项定义的层次。支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
- optname:需设置的选项。
- value:设置选项的值。
3.2.16 getsockopt()函数:公共
作用:返回套接字选项的值。
格式:
s.getsockopt(level,optname[.buflen])
3.2.17 settimeout()函数:公共
作用:设置套接字操作的超时期,
格式:
s.settimeout(timeout)
参数说明:
- timeout:一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
3.2.18 gettimeout()函数:公共
作用:返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
格式:
s.gettimeout()
3.2.19 fileno()函数:公共
作用:返回套接字的文件描述符。
格式:
s.fileno()
3.2.20 setblocking()函数:公共
作用:如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
格式:
s.setblocking(flag)
参数说明:
- flag:判断套接字是不是为阻塞模式。
3.2.21 makefile()函数:公共
作用:创建一个与该套接字相关连的文件
格式:
s.makefile()
肆 Socket编程思路
这里讲述TCP套接字编程过程:
4.1 TCP服务端
1、创建套接字,绑定套接字到本地IP与端口
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((IP, PORT))
2、开始监听连接
s.listen(num)
3、进入循环,不断接受客户端的连接请求
s.accept()
4、然后接收传来的数据,并发送给对方数据
s.recv(BUFLEN)
s.sendall()
5、传输完毕后,关闭套接字
s.close()
4.2 TCP客户端
1、创建套接字,连接远端地址
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect()
2、连接后发送数据和接收数据
s.sendall()
s.recv()
3、传输完毕后,关闭套接字
s.close()
伍 例子
5.1 简单使用
需要客户端、服务端两个脚本:
# === TCP 服务端程序 server.py ===
# 导入socket 库
from socket import *
# 主机地址为空字符串,表示绑定本机所有网络接口ip地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号
PORT = 50000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512
# 实例化一个socket对象
# 参数 AF_INET 表示该socket网络层使用IP协议
# 参数 SOCK_STREAM 表示该socket传输层使用TCP协议
listenSocket = socket(AF_INET, SOCK_STREAM)
# socket绑定地址和端口
listenSocket.bind((IP, PORT))
# 使socket处于监听状态,等待客户端的连接请求
# 参数 8 表示 最多接受多少个等待连接的客户端
listenSocket.listen(8)
print(f'服务端启动成功,在{PORT}端口等待客户端连接...')
dataSocket, addr = listenSocket.accept()
print('接受一个客户端连接:', addr)
while True:
# 尝试读取对方发送的消息
# BUFLEN 指定从接收缓冲里最多读取多少字节
recved = dataSocket.recv(BUFLEN)
# 如果返回空bytes,表示对方关闭了连接
# 退出循环,结束消息收发
if not recved:
break
# 读取的字节数据是bytes类型,需要解码为字符串
info = recved.decode()
print(f'收到对方信息: {info}')
# 发送的数据类型必须是bytes,所以要编码
dataSocket.send(f'服务端接收到了信息 {info}'.encode())
# 服务端也调用close()关闭socket
dataSocket.close()
listenSocket.close()
# === TCP 客户端程序 client.py ===
from socket import *
IP = '127.0.0.1'
SERVER_PORT = 50000
BUFLEN = 1024
# 实例化一个socket对象,指明协议
dataSocket = socket(AF_INET, SOCK_STREAM)
# 连接服务端socket
dataSocket.connect((IP, SERVER_PORT))
while True:
# 从终端读入用户输入的字符串
toSend = input('>>> ')
if toSend =='exit':
break
# 发送消息,也要编码为 bytes
dataSocket.send(toSend.encode())
# 等待接收服务端的消息
recved = dataSocket.recv(BUFLEN)
# 如果返回空bytes,表示对方关闭了连接
if not recved:
break
# 打印读取的信息
print(recved.decode())
dataSocket.close()
5.2 项目
下面使关于Socket应用的项目:Socket进行文件的传输