python初识socket
C/S架构: Client/Server(客户端与服务器端架构)
B/S架构: Browser/Server(浏览器端与服务器端架构)
无论是C/S或B/S架构,只要进行网络通信,基本都要用socket
计算机osi五层模型协议:
应用层:每个软件对于数据有不同的自定制协议
应用软件/服务,等...
数据
传输层:端口协议
每一个软件都有固定的端口
端口 | 数据
网络层: 确定局域网的位置
ip协议: 寻找对方局域网的位置
ipv4(点分十进制):同一个局域网内所有计算机的IP地址绝对不同
0~255.0~255.0~255.0~255
子网掩码: 255.255.255.0
IP + 子网掩码才能确定是否在同一个网段,同一个子网,同一个局域网
如果子网掩码都是C类:255.255.255.0,那么一个网段最多能有256个IP,不能用0,255,254,最多253个IP
端口 + IP + 子网掩码 + mac + 广播 = 端口 + ip地址 = 就能确定世界上任何一个计算机的软件的位置
IP | port | 数据
总结:
通过计算机发送数据,我要先获取对方的ip和子网掩码:
去判断是不是同一个局域网
如果是同一个局域网: 通过IP ARP协议 获取mac发送数据
如果不是同一个局域网:网关(路由协议)一层一层的发送
数据链路层: 对数据分组,封包(源地址,目标地址)
数据头 | 数据
数据头部分(18个字节):
源地址:6个字节
目标地址:6个字节
数据类型:6个字节
数据分组形式:一组电信号(数据包,也叫一帧)
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
网卡(mac地址):计算机唯一标识,12位16进制,前六位厂商编号,后六位流水线好
计算机通信方式:广播
理论上: 有了mac地址,加上广播的方式,全世界所有的计算机都可以通信了
实际不行,因为会产生广播风暴,效率低,工作量太大
所以规定了:mac + 广播的形式只能在局域网中实行
原则上:同一个局域网就是通过 源mac地址 + 目标mac地址 + 数据 + 广播 = 可以将数据传输
源MAC地址,目标MAC地址 | IP | port | 数据
交换机(mac地址自主学习功能)
交换机有一个mac 与 网口的对照表.
网口 mac地址
1 40-8D-5C-93-F1-DE
2 40-8D-5C-93-F2-DE
3 41-8D-5C-93-F1-DE
.....
24 40-8D-5F-93-F1-DE
同一个局域网之内发送一个数据:
源mac地址: 40-8D-5C-93-F1-DE 目标mac地址 40-8D-5F-93-F1-DE 数据
如果交换机第一次连接这些电脑:
我的网口1连接的计算机发送一个数据,发送给目标mac,第一次的时候是不知道目标mac地址是多少的
IP地址 + ARP协议 计算出对方的mac地址
例如:
第一次发送给IP地址为 192.168.10.24
通过广播:在同一个局域网内广播一个数据:
(源mac地址 40-8D-5C-93-F1-DE ,对方mac FF-FF-FF-FF-FF-FF)(源ip :192.168.10.22 对方ip 192.168.10.24)
找到符合的ip:
回传一个数据:
(源mac地址 40-8D-5F-93-F1-DE ,对方mac 40-8D-5C-93-F1-DE)(源ip :192.168.10.24 对方ip 192.168.10.22)
同一个局域网之内:
第一次发送数据: 需要广播的形式,获取对方的mac地址,将mac地址与网口写入交换机的对照表
第二次发送数据(计算机与网口没更换):不广播,而是直接从mac对照表寻找对方的地址
物理层:
物理连接介质(网线...)
将数据包转换成0101...发送
socket在内的五层通讯流程:

Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展(就像我们开发一套公司管理系统一样,报账、会议预定、请假等功能不需要单独写系统,而是一个系统上多个功能接口,不需要知道每个功能如何去实现的).于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信.这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理.socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了.
其实站在你的角度上看,socket就是一个模块.我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信.也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序. 所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信
套接字socket的发展史及分类:
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我们只使用AF_INET)
基于TCP和UDP两个协议下socket的通讯流程
TCP和UDP对比
TCP (Transmission ControlProtocol) 可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流.使用TCP的应用: Web浏览器: 文件传输程序
UDP (User DatagramProtocol) 不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制.使用UDP的应用: 域名系统(DNS): 视频流; IP语音(VoIP)
直接看图对比其中差异

TCP和UDP下socket差异对比图:

TCP协议下的socket
基于TCP的socket通讯流程图

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接.在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了.客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
事例1
1 服务端
2 # 导入socket模块
3 import socket
4
5 # 创建服务端程序socket对象
6 server = socket.socket()
7
8 # 给服务端程序设置一个本机IP地址和本机端口号
9 ip_port = ('192.168.12.42',8002)
10
11 # 配置本机的IP地址与本机的端口号绑定
12 server.bind(ip_port)
13
14 # 监听本机的地址和端口(等待请求)
15 server.listen()
16
17 # 等待建立连接,conn是连接通道,addr是客户端的地址
18 conn,addr = server.accept()
19
20 # 服务端通过conn连接通道来收发消息,通过recv方法,recv里面的参数是字节(B)
21 from_client_msg = conn.recv(1024)
22
23 # 打印传递过来的的消息(因为默认接收到的是字节,所以打印的也是字节)
24 print(from_client_msg)
25
26 # 关闭通道
27 conn.close()
28 # 关闭socket对象
29 server.close()
30 服务端打印结果
31 b'hello'
32
33
34 客户端
35 # 导入socket模块
36 import socket
37
38 # 创建客户端程序socket对象
39 client = socket.socket()
40
41 # 配置要发数据的服务端IP和端口
42 server_ip_port = ('192.168.12.42',8002)
43
44 # 连接服务端的应用程序,通过connect方法,参数是服务端的ip地址和端口
45 client.connect(server_ip_port)
46
47 # 发消息,用的send方法,但是调用者是client的socket对象(默认发送的是字节)
48 client.send(b'hello')
49
50 # 关闭socket对象
51 client.close()
如果有以上报错,说明使用的端口冲突,请更换一个socket端口重新启动服务端

事例2
1 服务端
2 import socket
3 server = socket.socket()
4 ip_port = ('192.168.12.42',8002)
5 server.bind(ip_port)
6 server.listen()
7 conn,addr = server.accept()
8 from_client_msg = conn.recv(1024)
9 # 打印客户端发过来的消息
10 print('小明说:',from_client_msg.decode('utf-8'))
11
12 #回复消息:通过send方法,参数必须是字节类型的(发给客户端的消息)
13 conn.send('你猜我猜不猜'.encode('utf-8'))
14 conn.close()
15 server.close()
16
17
18 客户端
19 import socket
20 client = socket.socket()
21 server_ip_port = ('192.168.12.42',8002)
22 client.connect(server_ip_port)
23
24 # 可以通俗的理解这就是发送给服务器端的消息
25 client.send('猜猜我是谁'.encode('utf-8'))
26
27 # 可以通俗的理解这就是接收服务器端返回给客服端的消息
28 from_server_msg = client.recv(1024)
29 print('小刚说:',from_server_msg.decode('utf-8'))
30 client.close()
事例3
1 服务端
2 import socket
3 server = socket.socket()
4 ip_port = ('192.168.12.42', 8002)
5 server.bind(ip_port)
6 server.listen()
7 conn,addr = server.accept()
8 while 1:
9 from_client_msg = conn.recv(1024)
10
11 # 打印客户端发送过来的消息
12 print('小明说:',from_client_msg.decode('utf-8'))
13
14 # 判断如果客户端输入的是bye就退出
15 if from_client_msg.decode('utf-8') == 'bye':
16 break
17
18 # 给客户端发送的的消息
19 to_clinet_msg = input('小刚说:')
20 conn.send(to_clinet_msg.encode('utf-8'))
21
22 # 判断如果服务器端输入的是bye就退出
23 if to_clinet_msg.upper() == 'bye':
24 break
25 conn.close()
26 server.close()
27
28
29 客户端
30 import socket
31 client = socket.socket()
32 server_ip_port = ('192.168.12.42', 8002)
33 client.connect(server_ip_port)
34 while 1:
35 # 给服务器端发送的消息
36 to_server_msg = input('小明说:')
37 client.send(to_server_msg.encode('utf-8'))
38
39 # 判断如果客户端输入的是bye就退出
40 if to_server_msg.upper() == 'bye':
41 break
42
43 # 接收服务端返回的消息并打印
44 from_server_msg = client.recv(1024)
45 print('小刚说:',from_server_msg.decode('utf-8'))
46
47 # 如果服务器端返回bye,就退出
48 if from_server_msg.decode('utf-8') == 'bye':
49 break
50 client.close()
注意: 先运行server,然后再运行client,然后你会发现client这个文件再输出台的地方让你输入内容,你输入一个内容然后回车,你会发现server那边的控制台就输出了以client发送的内容
事例4
1 每隔5s把时间戳时间转换为格式化时间
2 服务端
3 import socket,time
4 server = socket.socket()
5 ip_port = ('192.168.12.42', 8002)
6 server.bind(ip_port)
7 server.listen()
8 conn,addr = server.accept()
9 while 1:
10 # 接收客户端发送来的字节,然后打印,解码decode
11 from_client_msg = conn.recv(1024)
12 print(from_client_msg.decode('utf-8'))
13
14 # 发送给客户端的消息,因为发送的必须是字符,所以必须转码encode
15 conn.send(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(float(from_client_msg.decode('utf-8')))).encode('utf-8'))
16
17
18 客户端
19 import socket,time
20 client = socket.socket()
21 server_ip_port = ('192.168.12.42', 8002)
22 client.connect(server_ip_port)
23 while 1:
24 # 发送给服务器端的时间(因为传递的必须是字节,所以必须转码encode)
25 to_server_msg = time.time()
26 client.send(str(to_server_msg).encode('utf-8'))
27
28 # 接收服务器端返回的格式化时间(因为接收过来的是字节,所以必须解码decode)
29 from_server_msg = client.recv(1024)
30 print(from_server_msg.decode('utf-8'))
31 time.sleep(5)
如果在发送消息的过程中,服务器端突然中断了或强制中断,则会报错

如果同时打开两个socket服务端,则会报错:

而同时打开两个socket客户端,则一个连接的客户端可以和服务端收发消息,但是第二个连接的客户端发消息服务端是收不到的
原因解释:
tcp属于长连接,长连接就是一直占用着这个链接,这个连接的端口被占用了,第二个客户端过来连接的时候,他是可以连接的,但是处于一个占线的状态,就只能等着去跟服务端建立连接,除非一个客户端断开了(优雅的断开可以,如果是强制断开就会报错,因为服务端的程序还在第一个循环里面),然后就可以进行和服务端的通信了
优雅的断开一个client端之后另一个client端就可以通信
多服务端和多客户端互相通信,虽然启动多服务端和多客户端,但同一时间只有一个服务端给一个客户端通信,但是起码起多个服务端不报错,而多客户端的消息服务端能只能一个一个的处理
1 服务器端
2 import socket
3 server = socket.socket()
4
5 # 解决了起多个服务端地址及端口冲突报错
6 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
7 ip_port = ('127.0.0.1', 8888)
8 server.bind(ip_port)
9
10 # 同时接受三个客户端的连接(可以不写)
11 server.listen(3)
12
13 while 1:
14 # 建立通道
15 conn,addr = server.accept()
16 while 1:
17 #防止客户端异常关闭报错
18 try:
19 # 客户端给服务端发送的消息
20 from_client_msg = conn.recv(1024)
21 print('来自客户端的消息:', from_client_msg.decode('utf-8'))
22
23 if from_client_msg.decode('utf-8') == 'byebye':
24 break
25
26 # 服务端给客户端回应的消息
27 to_client_msg = input('服务端:')
28 conn.send(to_client_msg.encode('utf-8'))
29
30 except Exception: #如果客户端异常关闭就break退出
31 break
32 conn.close()
33 server.close()
34
35
36 客户端
37 import socket
38 client = socket.socket()
39 client.connect(('127.0.0.1',8888))
40
41 while 1:
42
43 #客户端给服务端发送的消息
44 to_server_msg = input('客户端:')
45 client.send(to_server_msg.encode('utf-8'))
46
47 if to_server_msg == 'byebye':
48 break
49
50 # 客户端接收服务端发送的消息
51 from_server_msg = client.recv(1024)
52 print('来自服务器端消息:',from_server_msg.decode('utf-8'))
53
54 client.close()
UDP协议下的socket

总结一下UDP下的socket通讯流程
服务器端先初始化Socket,然后与端口绑定(bind),recvform接收消息,这个消息有两项,消息内容和对方客户端的地址,然后回复消息时也要带着你收到的这个客户端的地址,发送回去,最后关闭连接,一次交互结束
1 服务器端:
2 import socket
3
4 # 创建一个服务器的DUP套接字(TCP默认是SOCK_STREAM)
5 udp_server = socket.socket(type=socket.SOCK_DGRAM)
6
7 # 绑定服务器套接字
8 udp_server.bind(('127.0.0.1',8888))
9
10 # 打印接收客户端发送过来的消息
11 from_client_msg,client_addr = udp_server.recvfrom(1024)
12 print(from_client_msg,client_addr)
13
14 # 发送给客户端的UDP消息(client_addr客户端地址和端口)
15 udp_server.sendto(b'hello',client_addr)
16
17 # 关闭DUP链接
18 udp_server.close()
19
20
21 客户端
22 import socket
23
24 # 创建客户端UDP套接字type=socket.SOCK_DGRAM
25 udp_client = socket.socket(type=socket.SOCK_DGRAM)
26
27 # 配置要发送给服务端的消息和地址端口
28 udp_client.sendto(b'world',('127.0.0.1',8888))
29
30 # 接收并打印服务器端发送过来的消息
31 from_server_msg,server_addr = udp_client.recvfrom(1024)
32 print(from_server_msg,server_addr)
UDP多客户端发送消息
1 服务器端
2 import socket
3
4 # 创建一个UDP套接字
5 udp_server = socket.socket(type=socket.SOCK_DGRAM)
6
7 # 绑定IP和端口
8 udp_server.bind(('127.0.0.1',8888))
9
10 while 1:
11
12 # 接收客户端发送给服务器端的消息,转码并打印
13 from_client_msg, client_addr = udp_server.recvfrom(1024)
14 print(from_client_msg.decode('utf-8'), client_addr)
15
16 # 服务端转码发送给客户端的消息
17 to_client_msg = input('服务端说:')
18 udp_server.sendto(to_client_msg.encode('utf-8'), client_addr)
19
20 udp_server.close()
21
22
23 客户端
24 import socket
25
26 # 建立UDP套接字
27 udp_client = socket.socket(type=socket.SOCK_DGRAM)
28
29 # 设置要发送的服务器地址和端口
30 ip_port = ('127.0.0.1',8888)
31
32 while 1:
33
34 # 配置客户端给服务器端发送的消息并转码发送
35 to_server_msg = input('客户端说')
36 udp_client.sendto(to_server_msg.encode('utf-8'), ip_port)
37
38 # 接收并转码打印服务器返回给客户端的消息
39 from_server_msg, server_addr = udp_client.recvfrom(1024)
40 print(from_server_msg.decode('utf-8'), server_addr)
41
42 udp_client.close()

浙公网安备 33010602011771号