一、网络通信概述
1、网络通信就是一种能够使双方或者多方连接在一起的工具。
2、网络通信的目的
- 能够把多方连接在一起,然后进行数据的传递
- 网络编程就是,使不同电脑上的软件能够进行数据传递,即进程之间的通信
二、TCP/IP协议
1、网络协议为计算机中进行数据交换而建立的规则、标准或约定的集合。
2、互联网有上百种协议标准,最重要的两个协议时TCP/IP协议。
3、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
4、回环地址ip:ip地址127.0.0.1代表本机ip地址,等价与localhost,用http://127.0.0.1即可测试本机中的配置的Web服务器。
5、⼦⽹掩码不能单独存在,它必须结合IP地址⼀起使⽤。
⼦⽹掩码的作⽤: 将某个IP地址划分成⽹络地址和主机地址两部分. ⼦⽹掩码的设定 必须遵循⼀定的规则, 用来判断两个IP是否在同一个网络。
A: 172.25.254.18/24
B: 172.25.0.10/24
6、端口是连接的必要条件,端口号只有整数范围ie是从0到65535。下面是一些主要服务的端口号:
- http端口号:80
- ssh端口号:22
- https端口号:443
- mysql端口号:3306
- redis端口号:6379
三、socket编程
1、本地间的通信有:队列、同步、管道
2、网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议和端口”可以唯一标识主机中的应用程序(进程)。因此利用IP地址,协议,端口就可以标识网络的进程。
3、socket是进程通信间的一种方式,能实现不同主机间的通信,我们网络上的多数服务器都是基于Socket完成通信。
4、在 Python 中 使⽤socket 模块的函数 socket 就可以完成: socket.socket(AddressFamily, Type)
- Address Family:
AF_INET: IPV4⽤于 Internet 进程间通信
AF_INET6: IPV6⽤于 Internet 进程间通信 - Type:套接字类型
SOCK_STREAM: 流式套接字,主要⽤于 TCP 协议
SOCK_DGRAM: 数据报套接字,主要⽤于 UDP 协 议
5、UDP用户数据报协议,是⼀个⽆连接的简单的⾯向数据报的运输层协议。UDP不提供可靠性,它只是把应⽤程序传给IP层的数据报发送出去,但 是并不能保证它们能到达⽬的地。由于UDP在传输数据报前不⽤在客户和服 务器之间建⽴⼀个连接,且没有超时重发等机制,故⽽传输速度很快。
6、UDP是⾯向消息的协议,通信时不需要建⽴连接,数据的传输⾃然是不可靠 的,UDP⼀般⽤于多点通信和实时的数据业务,⽐如:
- 语音广播
- 视频
- TFTP(简单文件传输)
- SNMP(简单网络管理协议)
- DNS(域名解释)
服务端代码:
import socket
udpsocket = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
udpsocket.bind(('0.0.0.0',9996))
print('模拟qq聊天开始....')
while True:
#返回的是一个元组,第一个元素的是客户端发送的消息,第二个元素是客户端与服务端交互的地址(IP,port)
recvdata,address = udpsocket.recvfrom(1024)
r = recvdata.decode('utf-8')
print('B:>>',r)
if r == 'quit':
print('聊天结束...')
break
send_data = input('A:>>').encode('utf-8')
udpsocket.sendto(send_data,address)
udpsocket.close()
客户端代码:
import socket
udpclient = socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
print('用户B上线.....')
while True:
send_data = input('B:>>').encode('utf-8')
udpclient.sendto(send_data,('172.25.254.47',9996))
if send_data == 'quit':
print(';聊天结束....')
break
recv_Data, address = udpclient.recvfrom(1024)
print('A:>>',recv_Data.decode('utf-8'))
udpclient.close()
四、TCP详解
1、TCP三次握手
- 两个包: 同步序列标号 SYN 确认包 ACK
- 四种状态: SYN_SENT, LISTEN, SYN_RECV, ESTABLISHED
在三次握手中,客户端和服务器端都发送两个包SYN和ACK,只不过服务器端的两个包是一次性发过来的,客户端的两个包是分两次发送的。 - 第一次握手:当客户端向服务器发起连接请求时,客户端会发送同步序列标号SYN到服务器,在这里我们设SYN为x,等待服务器确认,这时客户端的状态为SYN_SENT。
- 第二次握手:当服务器收到客户端发送的SYN后,服务器要做的是确认客户端发送过来的SYN,在这里服务器发送确认包ACK,这里的ACK为x+1,意思是说“我收到了你发送的SYN了”,同时,服务器也会向客户端发送一个SYN包,这里我们设SYN为y。这时服务器的状态为SYN_RECV
- 第三次握手:客户端收到服务器发送的SYN和ACK包后,需向服务器发送确认包ACK,“我也收到你发送的SYN了,我这就给你发个确认过去,然后我们即能合体了”,这里的ACK为y+1,发送完毕后,客户端和服务器的状态为ESTABLISH,即TCP连接成功。
在三次握手中,客户端和服务器端都发送两个包SYN和ACK,只不过服务器端的两个包是一次性发过来的,客户端的两个包是分两次发送的。
2、TCP四次挥手
断开连接请求可以由客户端发出,也可以由服务端发出,在这里我们称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是报文段发送和接收的最长时间。
TCP服务端代码:
import socket
#1.创建服务端socket对象
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
#2.绑定地址和端口
server.bind(('0.0.0.0',9998))
#3.监听是否有客户端连接
server.listen(5)
print('服务端开始.....')
#4.接受客户端的连接
clientSockObj,clientAddress = server.accept()
#5.接受客户端发送的信息
recv_data = clientSockObj.recv(1024).decode('utf-8')
print('接收到客户端发送的消息:',recv_data)
#6.给客户端发送消息
send_data = b'hello'
clientSockObj.send(send_data)
#7.关闭socket对象
clientSockObj.close()
server.close()
客户端代码:
import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('172.25.254.47',9998))
while True:
send_data = input('客户端:>>').encode('utf-8')
if not send_data:
continue
client.send(send_data)
if send_data == 'quit':
break
client.close()
五.并发服务器
1.并发服务器是socket应用编程中最常见的应用模型。根据连接方式分为长连接和短连接.
2.
通信方式 | 具体通信过程 |
---|---|
长连接 | 建立SOCKET连接后不管是否使用都保持连接 |
短连接 | 双方有数据交互时,建立TCP连接,数据发送完成后断开连接 |
3.并发服务器模型根据处理方式可分为ie同步方式和异步方式.
4.单进程服务器
- 同一时刻只能为⼀个客户进⾏服务,不能同时为多个客户服务
- 客户需要耐⼼等待才可以获取到服务
5.多进程服务器
- 优点:通过为每个客户端创建⼀个进程的⽅式,能够同时为多个客户端进⾏服务
- 缺点: 当客户端不是特别多的时候,这种⽅式还⾏,如果有⼏百上千个,就不 可取了,因为每次创建进程等过程需要好较⼤的资源
服务端代码:
def dealWithClient(clientSockObj,clientAddress):
while True:
#5.接受客户端发送的信息
recv_data = clientSockObj.recv(1024).decode('utf-8')
print(clientAddress[0] + str(clientAddress[1]) + ':>' + recv_data)
if recv_data == 'quit':
break
# send_data = input('server:>>').encode('utf-8')
# if not send_data:
# continue
# clientSockObj.send(send_data)
clientSockObj.close()
import socket
from multiprocessing import Process
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('0.0.0.0',9996))
server.listen(5)
print('server start..........')
while True:
clientSockObj,clientAddress = server.accept()
p = Process(target=dealWithClient,args=(clientSockObj,clientAddress))
p.start()
客户端1代码:
import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('172.25.254.47',9996))
while True:
send_data = input('客户端:>>').encode('utf-8')
if not send_data:
continue
client.send(send_data)
if send_data == 'quit':
break
client.close()
客户端2代码:
import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('172.25.254.47',9996))
while True:
send_data = input('客户端:>>').encode('utf-8')
if not send_data:
continue
client.send(send_data)
if send_data == 'quit':
break
client.close()
服务端显示结果:
server start..........
172.25.254.4757732:>1
172.25.254.4757734:>2
172.25.254.4757732:>1.1
172.25.254.4757734:>2.1