我们了解的涉及到两个程序之间通讯的应用大致可以分为两种:
第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用。
第二种是web类:比如百度、知乎、谷歌等使用浏览器访问就可以直接使用的应用。
这些应用的本质其实都是两个程序之间的通讯,而这两个分类又对应了两个软件开发的架构。
C/S架构
Client/Server,客户端和服务器结构。这种架构也是从用户层面(也可以是物理层面)来划分的。这里的客户端一般泛指客户端应用程序exe文件等,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。
B/S架构
Browser/Server,浏览器端和服务器架构。这种架构是从用户层面来划分的,Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。
socket
中文:套接字。套接字是计算机网络数据结构,它体现了上节中所描述的“通信端点”的概念。
套接字的起源可以追溯到 20 世纪 70 年代,它是加利福尼亚大学的伯克利版本 UNIX(称 为 BSD UNIX)的一部分。因此,有时你可能会听过将套接字称为伯克利套接字或 BSD 套接 字。套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个 进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication, IPC)。有两种类型的套接字:基于文件的和面向网络的。
Python只支持 AF_UNIX、AF_NETLINK、AF_TIPC 和 AF_INET 家族。
AF_UNIX:基于文件 系统支持它们的底层基础结构,是套接字的第一个家族。
AF_NETLINK:支持无连接服务,允许使用标准的 BSD 套接字接口进行用户级别和内核级别代码之间的 IPC。
AF_TIPC:支持透明的进程间通信(TIPC)协议,允许计算机集群之中的机器相互通信,而无须使用基于 IP 的寻址方式。
AF_INET:基于网络,是使用得最广泛的。
套接字地址:主机 + 端口号
面向网络两种方式连接操作步骤如图:
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
基于TCP的socket
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端。
server端:
import socket
HOST = '127.0.0.1' # loop back host
PORT = 8080 # the port number must be greater than or equal to 1024
ADDRESS = (HOST, PORT) # must be a tuple
ss = socket.socket() # create socket
ss.bind(ADDRESS) # bind ADDRESS
ss.listen() # listen
con, add = ss.accept() # accept a tuple (socket_object,address)
print(con, add)
res = con.recv(1024)
print(res)
con.send(b'Hello') # send binary
con.close() # close con
ss.close() # close socket_object
client端:
import socket
HOST = '127.0.0.1' # loop back host
PORT = 8080 # the port number must be greater than or equal to 1024
ADDRESS = (HOST, PORT) # must be a tuple
ss = socket.socket() # create socket
ss.connect(ADDRESS) # connect ADDRESS
ss.send(b'This is Python') # only send binary
res = ss.recv(1024) # the size every time can receive
print(res)
ss.close()
执行方法:先打开服务器,然后打开客户端。
结果:
server端:
('127.0.0.1', 1122)
b'This is Python'Process finished with exit code 0
client端:
b'Hello'
Process finished with exit code 0
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接。
server端:
import socket
HOST = '127.0.0.1' # loop back host
PORT = 8080 # the port number must be greater than or equal to 1024
ADDRESS = (HOST, PORT) # must be a tuple
ss = socket.socket(type=socket.SOCK_DGRAM) # for UDP, type=socket.SOCK_DGRAM
ss.bind(ADDRESS) # bind ADDRESS
msg, add = ss.recvfrom(1024) # the size every time can receive
print(msg)
ss.sendto(b'Hello', add) # binary parameter, ADDRESS
ss.close()
client端:
import socket
HOST = '127.0.0.1' # loop back host
PORT = 8080 # the port number must be greater than or equal to 1024
ADDRESS = (HOST, PORT) # must be a tuple
ss = socket.socket(type=socket.SOCK_DGRAM) # for UDP, type=socket.SOCK_DGRAM
ss.sendto(b'This is Python', ADDRESS) # binary parameter, ADDRESS
msg, add = ss.recvfrom(1024) # the size every time can receive
print(msg.decode('utf-8'), add) # must be decode
ss.close()
执行方法:先打开服务器,然后打开客户端。
结果:
server端:
b'This is Python'
Process finished with exit code 0
client端:
Hello ('127.0.0.1', 8080)
Process finished with exit code 0
简单QQ聊天程序
服务端:
import socket
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ss.bind(ADDRESS)
while 1:
c_msg, add = ss.recvfrom(BUFF_SIZE)
print("FROM[%s : %s]:\033[1;44m%s\033[0m" % (add[0], add[1], c_msg.decode('utf-8')))
s_msg = input("REPLY:").strip()
ss.sendto(s_msg.encode('utf-8'), add)
客户端:
import socket
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
sc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
name_dict = {
'Mike': ADDRESS,
'Nick': ADDRESS,
'Jane': ADDRESS,
'Mary': ADDRESS
}
while 1:
choose_name = input("Please input a person you want to talk:")
if choose_name not in name_dict.keys():
print("Please input correct name,initial capital.")
continue
while 1:
c_msg = input("Please input message,press 'Enter' to send, 'Q' for exit:").strip()
if c_msg == 'Q':
break
if not c_msg:
print("Please input something:")
continue
sc.sendto(c_msg.encode('utf-8'), name_dict[choose_name])
s_msg, add = sc.recvfrom(BUFF_SIZE)
print("FROM[%s : %s]:\033[1;44m%s\033[0m" % (add[0], add[1], c_msg.encode('utf-8')))
sc.close()
执行方法:先打开服务器,然后打开客户端。
结果:
(略)
时间服务器(为发送至服务器的内容打上时间)
服务端:
import socket
import time
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
ss.bind(ADDRESS)
while 1:
msg, add = ss.recvfrom(BUFF_SIZE)
time_format = '%Y-%m-%d %X'
t = time.strftime(time_format)
back_msg = (t + ' ').encode('utf-8') + msg
if msg:
ss.sendto(back_msg, add)
else:
ss.sendto((t + " Empty string invalid").encode('utf-8'), add)
客户端:
import socket
HOST = '127.0.0.1'
PORT = 8080
ADDRESS = (HOST, PORT)
BUFF_SIZE = 1024
sc = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while 1:
msg = input("Please input anything you want:")
sc.sendto(msg.encode('utf-8'), ADDRESS)
re_msg, add = sc.recvfrom(BUFF_SIZE)
print(re_msg.decode('utf-8'))
sc.close()
结果:
服务端:
(无结果,等待状态)
客户端:
Please input anything you want:Hello
2018-08-15 13:55:19 Hello
Please input anything you want:This is Python
2018-08-15 13:55:26 This is Python
Please input anything you want:
2018-08-15 13:55:28 Empty string invalid
Please input anything you want:...
socket参数详解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
family | AF_INET(默认值) AF_INET6(IPV6使用) AF_UNIX(Unix早期文件通信)AF_CAN(原始套接字,操作底层)和AF_RDS。 |
---|---|
type | SOCK_STREAM(默认值,TCP) SOCK_DGRAM(UDP) SOCK_RAW(IP、ICMP等)或其它。 |
proto | 协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一。 |
fileno | 如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的,这有助于使用socket.close()关闭一个独立的插座。 |