客户端/服务器架构也称主从式架构,简称C/S架构,它是一种网络结构,把客户端(Client)(一般是一个采用图形界面的程序)与服务器(server)区分开来,在C/S架构中,服务器是一系列的硬件或软件,客户端是提交服务请求的用户,客户端提供用户请求接口,服务端响应请求进行对应的处理,并返回给客户端。客户端/服务器架构既能够应用于计算机硬件,也能够应用于软件。linux
典型的硬件客户端/服务器架构就是打印机,在企业中,员工经过局域网将我的电脑链接到打印机上,做为客户端向打印机发送打印请求,打印机做为服务端完成响应处理相应的请求。程序员
软件服务器也是运行在硬件之上的,典型的软件服务器是Web服务器。在一台或多台电脑上搭建Web服务器,以提供用户访问所需的Web页面和应用程序,Web服务器一旦启动,都将可能永远运行,除非受到一些外力驱使才会中止,如人为关闭,服务器硬件故障等。它的工做就是接收客户端的请求,并响应请求给客户端返回相应的Web页面,而后等待下一个客户端的请求。编程
套接字是网络编程中的一个基本组件,若是想要服务器可以响应客户端发来的请求,首先要创建一个通讯端点,使服务器可以监听服务,当通讯端点创建后,就会进入无限循环的等待请求状态,当接收到客户端的请求,就会响应该请求。缓存
套接字就是两个程序之间的信息通道,能够理解为上面提到的“通讯端点”的概念。在通讯开始以前,网络应用程序必须建立套接字。套接字是网络通讯过程当中端点的抽象表示,包含进行网络通讯必须的五种信息:链接使用的协议,本地主机的IP地址,本地进程的协议端口号,远程主机的IP地址,远程进程的协议端口号。服务器
套接字起源于 20 世纪 70 年代,它是加利福尼亚大学伯克利分校版本的 Unix的一部分,即人们所说的 BSD Unix。 所以,套接字也被人们称为“伯克利套接字”或“BSD 套接字”。套接字最初被设计用于同一台主机上多个应用程序之间的通信,这也就是所谓的进程间通信(IPC)。网络
TCP用主机的IP地址加上主机的端口号做为TCP链接的端点,这种端点就叫作套接字(socket)或插口。套接字用(IP地址:端口号)表示。架构
套接字有两种类型,分别是基于文件的和基于网络的。socket
基于文件的套接字家族名字叫作“AF_UNIX”,表明地址家族(address family):UNIX。在Unix和linux操做系统中,熟为人知的一句话就是:一切皆文件,一个或多个进程运行在同一台机器上,因此套接字是基于文件的,它就能够经过访问底层的基础结构来实现进程之间的通讯tcp
基于网络的套接字家族名字叫作“AF_INET”,表明地址家族(address family):INET(因特网)。它使用IPv4进行通讯,由于IPv4使用32位地址,相比于IPv6的128位来讲,计算更快,更适合于局域网的通讯。目前它也是使用最普遍的。函数
在本文中,重点讲网络编程,因此在后面的涉及最多的仍是AF_INET。
不论使用哪一种地址家族,都只有两种套接字的链接方式,一种是面向链接的,一种是无链接的。
面向链接的套接字链接方式,意味着在进程通讯以前必须先创建好一个链接,这种套接字就称为流式套接字。
流式套接字用于提供面向链接、可靠的数据传输服务。该服务将保证数据可以实现无差错、无重复发送,并按顺序接收。流式套接字之因此可以实现可靠的数据服务,缘由在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。在Python中,建立TCP套接字,就必须声明SOCK_STREAM做为套接字类型。
数据报套接字提供了一种无链接的服务。这也意味着,使用这种链接方式不须要在进程通讯前创建链接。在数据的传输过程当中,SOCK_DGRAM并不能保证数据传输的可靠性,数据有可能在传输过程当中丢失或出现数据重复,且没法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。因为数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失状况,须要在程序中作相应的处理。
虽然存在数据丢失、重复、数据无序接受等不少缺点,但它也有优点所在,在流式套接字中,由于是面向链接并提供了可靠的数据传输服务,这对于虚拟电路链接的维护须要很大的开销,但数据报套接字就不须要这些额外的开销,因此维护、资源占用成本更低。
Python是一个很强大的网络编程工具,Python内有不少针对网络协议的库,这些库对网络协议的各个层次进行抽象封装,这对于程序员来讲就意味着:没必要关心网络协议的原理,只须要经过对程序的逻辑处理,就能够实现网络数据的传输。
在Python中,建立套接字须要使用socket模块,经过socket()函数建立套接字对象。
1 class socket(_socket.socket): 2 -- skip -- 3 def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None): 4 -- skip --
从socket函数的的构造方法中能够看出,能够指定地址家族和套接字的链接方式,proto默认是0,一般都省略。即建立套接字对象的时候:
import socket
#建立TCP/IP套接字,地址家族AF_INET
tcp_socket = socket.socket(socket.AF_INET,socket.SOCKET_STREAM)
#建立UDP/IP套接字,地址家族AF_INET
udp_socket = socket.socket(socket.AF_INET,socket.SOCKET_DGRAM)
常见的套接字内置函数
方法 |
功能 |
st.recv() |
接受TCP的消息 |
st.recv_into() |
接受TCP的消息到指定的缓存区 |
st.send()
|
发送TCP的消息(当待发送的消息量大于缓存区剩余内存时,数据会丢失) |
st.sendall()
|
完整的发送TCP消息(当待发送的消息量大于缓存区剩余内存时,数据不会丢失,循环调用send 直到发完为止) |
st.recvfrom() |
接收UDP的消息 |
st.recvfrom_into() |
接收UDP的消息到指定的缓存区 |
st.sendto() |
发送UDP的消息 |
st.getpeername() |
链接到套接字的远程地址(TCP) |
st.getsockname() |
获取当前套接字的地址 |
st.getsockopt() |
获取指定套接字的参数 |
st.setsockopt() |
设置指定套接字的参数 |
st.close() |
关闭套接字 |
st.shutdown() |
关闭链接 |
服务端套接字方法
方法 |
功能 |
st.bind() |
将IP地址+端口号绑定到套接字上 |
st.listen() |
开启TCP监听功能 |
st.accept() |
被动的接受TCP客户端的链接,(阻塞式)一直等待链接直到链接到达 |
客户端套接字方法
方法 |
功能 |
st.connect() |
主动发起TCP服务器链接 |
st.connect_ex() |
connect()的扩展版本,以错误代码的形式返回问题,而不是抛出异常 |
面向阻塞的套接字方法
方法 |
功能 |
st.setblocking() |
设置套接字为阻塞模式或非阻塞模式 |
st.settimeout() |
设置阻塞套接字的操做超时时间 |
st.gettimoout() |
获取阻塞套接字的操做超时时间 |
面向文件的套接字方法
方法 |
功能 |
st.fileno() |
套接字的文件描述符 |
st.makefile() |
建立与套接字相关联的文件对象 |
数据属性
属性 |
功能 |
st.family |
套接字家族 |
st.type |
套接字类型 |
st.proto |
套接字协议 |
上面提到过,套接字对象都是经过socket.socket()函数来建立的,下面模拟一个TCP服务器和客户端,来实现进程间的通讯。
Tcp服务端:
1 import socket 2 tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) //建立服务器套接字 3 tcp_server.bind(("127.0.0.1",8000)) //将套接字与地址绑定 4 tcp_server.listen(5) //创建监听链接 5 print("The server has started") 6 while True: 7 conn,addr= tcp_server.accept() //接受客户端的链接 8 while True: 9 try: 10 data = conn.recv(1024) //会话的接收(或发送) 11 print("msg is",data.decode("utf-8")) //要将收到的会话数据进行解码 12 conn.send(data.title()) //会话的发送(或接受) 13 except Exception: 14 break 15 conn.close() //关闭链接 16 tcp_server.close() //关闭服务器套接字
在Tcp服务端,先建立服务器套接字并指定类型为流式套接字(SOCK_STREAM)。由于服务器须要占用一个端口并等待客户端的请求,因此它们必须绑定到一个本地地址。Tcp是一种面向链接的通讯方式,因此必须创建监听链接,listen(5)的意义是容许传入链接的最大数为5个。当调用accept()函数后,服务端就会进入一个等待状态,默认状况下,accept()处于阻塞状态,也就意味着,执行到此处,程序会暂停,直到有新的链接到达,才会进行下一步的收发操做。
Tcp客户端:
1 import socket 2 tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) //建立客户端套接字 3 tcp_client.connect(("127.0.0.1",8000)) //链接服务器 4 while True: 5 msg = input("Please input your message:").strip() 6 if not msg:continue 7 tcp_client.send(msg.encode("utf-8")) //会话接收(或发送) 8 data = tcp_client.recv(1024) 9 print("reply is",data.decode("utf-8")) 10 tcp_client.close() //关闭客户端套接字
建立客户端比服务端要简单不少,客户端一旦拥有了套接字,就能够利用套接字的connect()方法直接建立一个服务器的链接,创建好链接,就能够参与到服务端的会话中,当客户端的需求所有完成,就会关闭套接字,终止这次链接。
Udp服务器不须要Tcp服务器那么多的配置,由于它不是面向链接的,除了等待传入的链接,它基本不须要其余的操做。
Udp服务端:
1 import socket 2 ip_port = ("127.0.0.1",8000) 3 udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) //建立服务端套接字 4 udp_server.bind(ip_port) //绑定本地地址 5 print("the server has started") 6 while True: 7 data,addr = udp_server.recvfrom(1024) //关闭接收(或发送) 8 print(data) 9 udp_server.sendto(data.title(),addr) //关闭发送(或接受) 10 从上面代码中能够看出,除了建立套接字并绑定本地地址后,基本
没有其它的操做,它是无链接的,这也就意味着,它无需为了成功通讯而使一个客户端链接到一个“特定”的套接字进行转换操做,服务器端仅仅是接收数据并进行回复。
Udp客户端:
1 import socket 2 ip_port = ("127.0.0.1",8000) 3 udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) //建立服务端套接字 4 while True: 5 msg = input(">>>").strip() 6 udp_client.sendto(msg.encode("utf-8"),ip_port) //发送 7 data,addr = udp_client.recvfrom(1024) //接收 8 print(data) 9 udp_client.close() //关闭套接字
Udp客户端,一旦建立了套接字,就能够进行会话循环中,当会话结束,关闭套接字。
在使用Udp进行通讯的时候,服务端能够同时接收多个客户端的会话请求并返回请求结果。