文章目录
01、网络通信概述
02、TCP/IP协议
03、socket编程
04、socket中的TCP与UDP
05、并发服务器
简单意义上来说,网络就是⼀种辅助双⽅或者多⽅能够连接在⼀起的⼯具。
官方对于网络的定义为:
网络是由若干节点和连接这些节点的链路构成,表示诸多对象及其相互联系。
在1999年之前,人们一般认为网络的结构都是随机的。但随着Barabasi和Watts在1999年分别发现了网络的无标度和小世界特性并分别在世界著名的《科学》和《自然》杂志上发表了他们的发现之后,人们才认识到网络的复杂性。 [1]
网络会借助文字阅读、图片查看、影音播放、下载传输、游戏、聊天等软件工具从文字、图片、声音、视频等方面给人们带来极其丰富的生活和美好的享受。
联通多⽅然后进⾏通信⽤的,即把数据从⼀⽅传递给另外⼀⽅。
⽤⽹络能够把多⽅链接在⼀起,然后可以进⾏数据传递
⽹络编程就是,让在不同的电脑上的软件能够进⾏数据传递,即进程之间的通信,如下图:
TCP/IP参考模型是首先由ARPANET所使用的网络体系结构,共分为四层(个别参考把其分为5层):网络接口层(又称链路层)、网络层(又称互联层)、传输层和应用层,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
每一层对应的协议有:
1)IP地址
1、定义:IP地址是一种在Internet上的给主机编址的方式,也称为网际协议地址。
2、作用:⽤来在⽹络中标记⼀台电脑的⼀串数字,⽐如192.168.1.1;在本地局域⽹上是惟⼀的。
3、分类:每⼀个IP地址包括两部分:⽹络地址和主机地址 ,有如下几种类型:
4、私有IP:⽹络IP中,国际规定有⼀部分IP地址是⽤于局域⽹使⽤,也 就是属于私⽹IP,不在公⽹中使⽤的,它们的范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
5、回环地址IP:
6、子网掩码:⼦⽹掩码不能单独存在,它必须结合IP地址⼀起使⽤。
⼦⽹掩码的作⽤: 将某个IP地址划分成⽹络地址和主机地址两部分. ⼦⽹掩码的设定
必须遵循⼀定的规则, 用来判断两个IP是否在同一个网络。
A: 172.25.254.18/24
B: 172.25.0.10/24
7、端口:端⼝就好⼀个房⼦的⻔,是出⼊这间房⼦的必经之路。
端⼝号只有整数,范围是从0到65535;
下面列举一些常用的服务及端口号:
HTTP |
80 |
FTP | 21 |
SSH | 22 |
Telnet | 23 |
SMTP | 25 |
HTTP |
443 |
POP2 | 109 |
POP3 | 110 |
DHCP Client | 546 |
DHCP Server | 547 |
MSN | 569 |
SSL | 990 |
IMAP | 993 |
Worm.Sasser.e | 1023 |
sqlserver | 1433 |
Oracle | 1521 |
MySql | 3306 |
8、linux中如何查看端口呢?
在命令行输入:cat /etc/services | less 即可查看所需要的查看的服务的端口。
(当然,前提是你的主机安装了你所要查询的服务才能够查询到)
比如:查看http服务的端口:命令行输入:cat /etc/services | less (可以查看规定好的服务对应的端口号)
然后输入/http就可以查询到端口;
可见查询到的http服务的端口为80.
通过之前的学习, 我们知道本地间通信(IPC)有队列、同步(互斥锁、条件变量等)、以及管道等方法。
但是不同主机之间如果需要通信,则就需要用到socakt.
问题: 本地通过进程PID来唯⼀标识⼀个进程,在⽹络中如何唯⼀标识⼀个进程?
⽹络层的“IP地址”可以唯⼀标识⽹络中的主机,⽽传输层的“协议+端⼝”可以唯⼀标识主机中的应⽤程序(进程)。因此利用IP地址,协议,端⼝就可以标识⽹络的进程。
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
简单来说:socket(简称套接字) 是进程间通信的⼀种⽅式, 能实现不同主机间的进程间通信,我们⽹络上各种各样的服务⼤多都是基于 Socket 来完成通信的。比如以下服务等都是基于socket 来完成通信的。
在 Python 中 使⽤socket 模块的函数 socket 就可以完成:
socket.socket(AddressFamily, Type)
1). Address Family:
AF_INET: IPV4⽤于 Internet 进程间通信 AF_INET6: IPV6⽤于 Internet 进程间通信
2). Type:套接字类型
SOCK_STREAM: 流式套接字,主要⽤于 TCP 协议 SOCK_DGRAM: 数据报套接字,主要⽤于 UDP 协 议
import socket
# 1. 创建socket对象
# family: AF_INET(IPv4) AF_INET6(IPv6) ========= 网络层协议
# type: # SOCK_STREAM(TCP) SOCK_DGRAM(UDP) ========== 传输层协议
# Linux: 可以认为是一个文件;
socketObj = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
print(socketObj.getsockname())
#2. 关闭socket对象
socketObj.close()
# 2. socket基本使用
import os
os.system('hostname')
hostname = socket.gethostname()
print("主机名:", hostname)
print(socket.gethostbyname('localhost'))
小练习如下:创建⼀个tcp socket(tcp套接字)
把代码中的Type:套接字类型写成 SOCK_STREAM
创建⼀个udp socket(udp套接字)
把代码中的Type:套接字类型写成 SOCK_DGRAM 即可
1)什么是UDP
2)UDP的特点
3)UDP应用场景
UDP是⾯向消息的协议,通信时不需要建⽴连接,数据的传输⾃然是不可靠 的,UDP⼀般⽤于多点通信和实时的数据业务,⽐如:
4)UDP⽹络程序
通过UDP通信服务的过程: 无论是服务端还是客户端,都需要首先建立一个socket()对象将来进行通信,服务端需要绑定一个IP或一个端口。客户端想要给客户端发消息,由于UDP是无连接的,所以客户端不需要通知服务端,直接发送就好了,消息发送之后,服务端优势会做出一个响应回复给客户端,然年后客户端会进行接收,这样一次通信就结束了。当客户端决定 不发消息了,此时就可以结束掉socket()对象了。(当然,如果服务端也不想让别人给发送消息的时候,也可以吧自己的socket()对象结束掉。)图解如下:
实验:
服务端(UDP Server):提供服务
创建socket对象 ---- 绑定IP和端口 ---- 接收信息 ---- 发送消息(一个响应) ---- 关闭
客户端(UDP Client):访问提供的服务
创建socket对象 ---- 发送消息 ---- 接收消息 ---- 关闭
同一个目录下创建两个.py文件
服务端.py ; 客户端.py
《1》在服务端:
import socket
# 1.实例化socket对象
udpServer = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
# 2.绑定IP和端口,注意是一个元组
udpServer.bind(('0.0.0.0', 9001)) #0.0.0.0代表开放所有的IP地址
print('等待客户端UDP的连接。。。')
# 3.接收客户端的消息
recvdata, address = udpServer.recvfrom(1024) # 会有两个返回值:发送的消息;哪个地址发来的(IP地址,端口地址)。是一个元组。最多1024个字节
print('接收到客户端的数据为:',recvdata.decode('utf-8')) # socket传输的数据是一个bytes类型 :b'hello' ,将它转为字符串
# 4.给客户端回复消息,发送的消息必须是bytes类型
udpServer.sendto(b'hello client',address)
# 5.关闭socket对象
udpServer.close()
《2》在客户端:
import socket
# 1.创建socket对象
udpClient = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
# 2.客户端发送消息,发送的消息必须是bytes类型
udpClient.sendto(b'hello server',('192.168.1.104',9001)) #发给哪台主机的哪个端口
# 3.接收服务端的消息
recvdata,address = udpClient.recvfrom(1024)
print('接收到服务端的数据为:',recvdata.decode('utf-8'))
# 4.关闭socket对象
udpClient.close()
分别运行结果为:
《1》服务端为:
《2》客户端为:
5)UDP协议案例:(模拟QQ聊天)
注:python2中可以之间传字符串
python3中只能传输bytes类型的数据
bytes类型与字符串类型的相互转换:
bytes —> str:bytesobj.decode(‘utf-8’)
str —> bytes:str.encode(‘utf-8’)
"""服务端"""
import socket
udpserver = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
udpserver.bind(('0.0.0.0', 9999))
print('QQ用户A上线.........')
while True:
# 返回的是元组, 每一个元素是客户端发送的信息, 第二个元素是客户端和服务端交互的地址(IP, port)
recv_data, address = udpserver.recvfrom(1024)
# print(address)
print("B:>> ", recv_data.decode('utf-8'))
if recv_data == b'quit':
print("聊天结束.......")
break
# 发送的消息必须是bytes类型
# bytes --> str bytesObj.decode('utf-8')
# str --> bytes strObj.encode('utf-8')
send_data = input('A: >> ').encode('utf-8')
if not send_data:
continue
udpserver.sendto(send_data, address)
udpserver.close()
"""客户端"""
import socket
udpclient = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
print("QQ用户B上线.........")
while True:
send_data = input('B:>> ').encode('utf-8')
if not send_data:
continue
udpclient.sendto(send_data, ('172.25.254.197', 9999))
if send_data == b'quit':
print("聊天结束.....")
break
recv_data, address = udpclient.recvfrom(1024)
print("A:>> ", recv_data.decode('utf-8'))
udpclient.close()
运行结果为:
《1》服务端(A)为:
《2》客户端(B)为:
TCP协议通信的大致过程:首先,服务端和客户端都需要先建立socket()对象——服务端绑定IP和端口——服务端进行监听(为了保证有客户端和服务端进行连接)—— 监听结束如果有客户端和服务端连接,则可允许客户端建立连接(accept())——客户端发出连接信号——服务端读取信号并作出响应,然后回复客户端——以此循环,完成通信,——当双方各或某一方不想再继续通信,则关闭通信。
那么我们为什么说TCP协议比UDP协议更加可靠呢?原因在于TCP具有有名的三次握手和四次分手。下面对于TCP的三次握手和四次分手进行介绍。
第一次握手
当客户端向服务器发起连接请求时,客户端会发送同步序列标号SYN到服务器,在这里我们设SYN为x,等待服务器确认,这时客户端的状态为SYN_SENT。
第二次握手
当服务器收到客户端发送的SYN后,服务器要做的是确认客户端发送过来的SYN,在这里服务器发送确认包ACK,这里的ACK为x+1,意思是说“我收到了你发送的SYN了”,同时,服务器也会向客户端发送一个SYN包,这里我们设SYN为y。这时服务器的状态为SYN_RECV。
一句话,服务器端发送SYN和ACK两个包。
第三次握手
客户端收到服务器发送的SYN和ACK包后,需向服务器发送确认包ACK,“我也收到你发送的SYN了,我这就给你发个确认过去,然后我们即能合体了”,这里的ACK为y+1,发送完毕后,客户端和服务器的状态为ESTABLISH,即TCP连接成功。就可以进行数据信息的传输了。
在三次握手中,客户端和服务器端都发送两个包SYN和ACK,只不过服务器端的两个包是一次性发过来的,客户端的两个包是分两次发送的。
其中的关键内容:
两个包(服务端和客户端双方都会发送): 同步序列标号 SYN 确认包 ACK
四种状态:
SYN_SENT(发送SYN报文) 、LISTEN(监听) 、 SYN_RECV(接收SYN报文) 、 ESTABLISHED(建立连接)
X是任意数值。
当A端和B端要断开连接时,需要四次握手,这里称为四次挥手。
断开连接请求可以由客户端发出,也可以由服务器端发出,在这里我们称A端向B端请求断开连接。
第一次挥手
A端向B端请求断开连接时会向B端发送一个带有FIN标记的报文段,这里的FIN是Finish的意思。
第二次挥手
B端收到A发送的FIN后,B段现在可能现在还有数据没有传完,所以B端并不会马上向A端发送FIN,而是先发送一个确认序号ACK,意思是说“你发的断开连接请求我收到了,但是我现在还有数据没有发完,请稍等一下呗”。
第三次挥手
当B端的事情忙完了,那么此时B端就可以断开连接了,此时B端向A端发送FIN序号,意思是这次可以断开连接了。
第四次挥手
A端收到B端发送的FIN后,会向B端发送确认ACK,然后经过两个MSL时长后断开连接。
MSL是Maximum Segment Lifetime,最大报文段生存时间,2个MSL是报文段发送和接收的最长时间
直白点的理解:
A:我要跟你分手
B:行你让我考虑几天我再考诉你
B:我考虑好了,分就分了吧
A:好的,再见
其中关键点:
两个包: FIN:Finish, ACK确认序号
服务端:
创建socket对象 ---- 绑定IP和端口 ---- 监听(有没有客户端来连接) ---- 建立连接(监听到有客户端连接) ---- 接收消息 ---- 发送消息 ---- 关闭
客户端:
创建socket对象 ---- 发起连接 ---- 发送消息 ---- 接收消息 ---- 关闭
"""服务端"""
import socket
# 1.创建socket对象
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.绑定地址和端口
server.bind(('0.0.0.0',9998))
# 3.监听是否有客户端连接 ,python2可以不传值,python3要传值
server.listen(5) # 最多监听5个客户端
# 4.接收客户端的连接 accept返回两个值:跟服务端交流的socket对象;跟服务端交流的地址
client_socket_obj,client_addres = server.accept()
# 5.接收客户端发送的消息
recv_data = client_socket_obj.recv(1024)
print('接收到客户端发送的消息为:',recv_data.decode('utf-8')) # 发来的是bytes类型,解码成字符串
# 6.给客户端发送消息
send_data = b'hello client'
client_socket_obj.send(send_data)
# 7.关闭socket对象
client_socket_obj.close()
server.close()
"""客户端"""
import socket
# 1.创建socket对象
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.连接服务端
client.connect(('172.25.254.197',9998))
# 3. 给服务端发送消息
client.send(b'hello server')
# 4.接收服务端发送的消息
recv_data = client.recv(1024)
print('接收到服务端发送的消息为:',recv_data.decode('utf-8'))
# 5.关闭socket对象
client.close()
"""服务端"""
import socket
# 1.创建socket对象
serverA = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 2.绑定地址和端口
serverA.bind(('0.0.0.0',9998))
print('QQ用户A上线.....')
serverA.listen(5)
# 4.接收客户端的连接 accept返回两个值:跟服务端交流的socket对象;跟服务端交流的地址
clientB_socket_obj,client_addres = serverA.accept()
while True:
# 5.接收客户端发送的消息
recv_data = clientB_socket_obj.recv(1024)
print('B:',recv_data.decode('utf-8')) # 发来的是bytes类型,解码成字符串
if recv_data == b'quit':
print('聊天结束....')
break
# 6.给客户端发送消息
send_data = input('A:').encode('utf-8')
if not send_data:
continue
clientB_socket_obj.send(send_data)
# 7.关闭socket对象
clientB_socket_obj.close()
serverA.close()
"""客户端"""
import socket
# 1.创建socket对象
clientB = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
print('QQ用户B上线....')
# 2.连接服务端
clientB.connect(('172.25.254.197',9998))
while True:
# 3. 给服务端发送消息
send_data = input('B:').encode('utf-8')
if not send_data:
continue
clientB.send(send_data)
if send_data == b'quit':
print('聊天结束....')
break
# 4.接收服务端发送的消息
recv_data = clientB.recv(1024)
print('A:',recv_data.decode('utf-8'))
# 5.关闭socket对象
clientB.close()
并发服务器是socket应用编程中最常见的应用模型。根据连接方式分为长连接和短连接.
通信方式 |
具体通信过程 |
长连接 |
建立SOCKET连接后不管是否使用都保持连接 |
短连接 |
双方有数据交互时,建立TCP连接,数据发送完成后断开连接 |
并发服务器模型根据处理方式可分为同步方式和异步方式。
还可分为单进程服务器和多进程服务器
单进程服务器:
同⼀时刻只能为⼀个客户进⾏服务,不能同时为多个客户服务
类似于找⼀个“明星”签字⼀样,客户需要耐⼼等待才可以获取到服务
多进程服务器:
通过为每个客户端创建⼀个进程的⽅式,能够同时为多个客户端进⾏服务
缺点: 当客户端不是特别多的时候,这种⽅式还⾏,如果有⼏百上千个,就不 可取了,因为每次创建进程等过程需要好较⼤的资源
多进程服务端实现:
#实现多进程:实例化对象,创建子类继承
# 任务: 处理客户端的请求并为其服务
def dealwithClient(clientB_socket_obj):
while True:
# 接收客户端发送的消息
recv_data = clientB_socket_obj.recv(1024)
print('B:', recv_data.decode('utf-8')) # 发来的是bytes类型,解码成字符串
if recv_data == b'quit':
print('聊天结束....')
break
# 给客户端发送消息
send_data = input('A:').encode('utf-8')
if not send_data:
continue
clientB_socket_obj.send(send_data)
clientB_socket_obj.close()
import socket
from multiprocessing import Process
# 创建socket对象
serverA = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 绑定地址和端口
serverA.bind(('0.0.0.0',9992))
# 监听是否有客户端连接
serverA.listen(5)
# 接收多次请求
while True:
# 4.接收客户端的连接 accept返回两个值:跟服务端交流的socket对象;跟服务端交流的地址
clientB_socket_obj,client_addres = serverA.accept()
p = Process(target=dealwithClient,args=(clientB_socket_obj,))
p.start()
多线程服务器:
协程-TCP服务器
基于TCP多进程服务器
服务端:
# 实现多进程的方式:
# 1. 实例化对象
# 2. 继承子类
# 注意: 使用时一定要确定多进程要处理的任务
# 任务: 处理客户端请求并为其服务
def dealWithClient(clientSocketObj, clientAddress):
while True:
# 5. 接收客户端发送的消息
recv_data = clientSocketObj.recv(1024).decode('utf-8')
print(clientAddress[0] + str(clientAddress[1]) + ':> ' + recv_data)
if recv_data == 'quit':
break
clientSocketObj.close()
import socket
from multiprocessing import Process
# 1. 创建服务端socket对象
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定地址和端口(IP:port)
server.bind(('0.0.0.0', 9997))
# 3. 监听是否有客户端连接?listen
server.listen(5)
print('server start .........')
while True:
# 4.接收客户端的连接accept
clientSocketObj, clientAddress = server.accept()
# dealWithClient(clientSocketObj)
p = Process(target=dealWithClient, args=(clientSocketObj, clientAddress))
p.start()
# server.close()
客户端:
import socket
# 1. 创建服务端socket对象
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 2. 连接服务端
client.connect(('172.25.254.197', 9997))
while True:
# 3.给服务端发送消息
send_data = input('client: >> ').encode('utf-8')
if not send_data:
continue
client.send(send_data)
if send_data == 'quit':
break
# 5. 关闭socket对象
client.close()