一、套接字
1.1、套接字
套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)。有两种类型的套接字:基于文件的和面向网络的。
第一种:基于文件的
因为两个进程运行在同一台计算机上,所以这些套接字都是基于文件的,这意味着文件系统支持他们的底层基础结构。这是能够说的通的,因为文件系统是一个运行在同一个主机上的多个进程之间的共享常量。
第二种:基于网络的
基于网络的套接字,有自己的家族名字AF_INET,或者地址家族:因特网
地址家族(address family,缩写AF)
1.2、面向连接和无连接的套接字
面向连接的通信提供序列化的、可靠的和不重复的数据交付,而没有记录边界。这基本上意味着每条消息可以拆分成多个片段,并且每一条消息片段都确保能够到达目的地,然后将他们按顺序组合在一起,最后将完整消息传递给正在等待的应用程序。
实现这种连接类型的主要协议是传输控制协议(TCP)。为了创建TCP套接字,必须使用SOCK_STREAM作为套接字类型。
实现无连接的主要协议是用户数据报协议(UDP)。为了创建UDP套接字,必须使用SOCK_DGRAM作为套接字类型。
二、Python中的网络编程
2.1、socket()模块函数
要创建一个套接字,必须使用socket.socket()函数,它一般的语法如下。
socket(socket_family, socket_type, protocol=0)
所以,为了创建TCP/IP套接字,可以用下面的方式调用socket.socket()。
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
同样,为了创建UDP/IP套接字,需要执行以下语句。
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
参数一:地址簇socket_family
socket.AF_INET #IPv4(默认)
socket.AF_INET6 #IPv6
socket.AF_UNIX #只能够用于单一的unix系统进程间通信
参数二:类型socket_type
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 #可靠的连续数据包服务
参数三:协议
protocol通常省略,默认为0,则系统就会根据地址格式和套接类别,自动选择一个合适的协议。
因为有很多socket模块属性,如果使用“from socket import *”,那么他们就把socket属性引入到了命名空间中。虽然看起来麻烦,但是通过这种方式将能够大大缩短代码。
tcpSock = socket(AF_INET, SOCK_STREAM)
一旦有了一个套接字对象,那么使用套接字对象的方法将可以进行进一步的交互。
2.2、套接字对象(内置)方法
常见的套接字对象方法和属性
2.3、创建通用TCP服务器的一般伪代码
伪代码:服务端
ss = socket() #创建服务器套接字
ss.bind() #套接字与地址绑定
ss.listen() #监听连接
inf_loop: #服务器无限循环
cs = ss.accept() #接收客户端连接,产生阻塞
comm_loop: #通信循环
cs.recv()/cs.send() #对话(接收/发送)
cs.close() #关闭客户端套接字
ss.close() #关闭服务器套接字 #可选
伪代码:客户端
cs = socket() #创建客户端套接字
cs.connect() #尝试连接服务器
comm_loop: #通信循环
cs.send()/cs.recv() #对话(发送/接收)
cs.close()
from socket import * from time import ctime HOST = '' PORT = 9999 BUFSIZ = 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(BUFSIZ) if not data: break tcpCliSock.send(bytes(ctime(), encoding="utf-8") + data) tcpCliSock.close() tcpSerSock.close()
解释: 第1个代码块: 导入了time.ctime()和socket模块的所有属性。 第2个代码块: HOST变量时空白的,这是对bind()方法的标识,表示它可以使用任何可用的地址。我们也选择了一个随机的端口号。将缓冲区大小设置为1KB。可以根据网络性能和程需要改变这个容量。listen()方法的参数是在连接被转接或拒绝之前,传入连接请求的最大数。 第3个代码块: 分配了TCP服务器套接字(tcpSerSock),紧随其后的是将套接字绑定到服务器地址以及开启TCP监听器的调用。 第4个代码块: 一旦进入服务器的无限循环之后,我们就(被动地)等待客户端的连接。当一个连接请求出现时,我们进入对话循环中,在该循环中我们等待客户端发送的消息。如果消息是空白的,这意味着客户端已经退出,所以此时我们将跳出对话循环,关闭当前客户端连接,然后等待另一个客户端连接。如果确实得到了客户端发送的消息,就将其格式化并返回相同的数据,但是会在这些数据中加上当前时间戳的前缀。最后一行永远不会执行,它只是用来提醒读者,如果写了一个处理程序来考虑一个更加优雅的退出方式,那么应该调用close()方法。
from socket import * HOST = '127.0.0.1' #or 'localhost' PORT = 9999 BUFSIZ = 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, 'utf-8')) data = tcpCliSock.recv(BUFSIZ) if not data: break print(str(data, encoding="utf-8")) #和下面语句相同 # print(data.decode('utf-8')) tcpCliSock.close()
解释: 第1个代码块: 从socket模块导入所有属性 第2个代码块: HOST和PORT变量指服务器的主机名与端口名。因为在同一台计算机上运行测试,所以HOST包含本地主机名。端口号PORT应该与你为服务器设置的完全相同。此外,也将缓冲区大小设置为1KB。 第3个代码块: 分配了TCP客户端套接字,接着主动调用并连接到服务器。 第4个代码块: 客户端也有一个无限循环,但这并不意味着它会像服务器的循环一样永远运行下去。客户端循环在以下两种条件下将会跳出:用户没有输入,或者服务器终止且对recv()方法的调用失败。否则,在正常情况下,用户输入一些字符串数据,把这些数据发送到服务器进行处理。然后,客户端接收到加了时间戳的字符串,并显示在屏幕上。
执行上面代码输出结果: >>:i Tue Sep 13 19:11:20 2016i