网络编程
网络编程
一、 套接字介绍
'''
套接字是从20世纪70年代的加利福尼亚大学的伯克利分校版本的UNIX起源的,
即人们所说的 BSD Unix,因此,有时人们也把套接字称为 ‘伯克利套接字’或者‘BSD 套接字’,
但是那时候的套接字是用在同一台计算机的不同程序之间的通信,也被称为进程型通信
'''
# 套接字有两种
1.基于文件的套接字
套接字家族的名字:AF_UNIX
2.基于网络的套接字
套接字家族的名字:AF_INET
# 套接字的工作流程
套接字是将传输层以下全部打包,但是跟我们有关系的只有TCP UDP 只需要基于这两个来写套接字的软件就好了
二、位于TCP协议的套接字通信流程介绍
1). 服务器先用 socket 函数来建立一个套接字,用这个套接字完成通信的监听。
2). 用 bind 函数来绑定一个端口号和 IP 地址。因为本地计算机可能有多个网址和 IP,每一个 IP 和端口有多个端口。需要指定一个 IP 和端口进行监听。
3). 服务器调用 listen 函数,使服务器的这个端口和 IP 处于监听状态,等待客户机的连接。
4). 客户机用 socket 函数建立一个套接字,设定远程 IP 和端口。
5). 客户机调用 connect 函数连接远程计算机指定的端口。
6). 服务器用 accept 函数来接受远程计算机的连接,建立起与客户机之间的通信。
7). 建立连接以后,客户机用 write 函数向 socket 中写入数据。也可以用 read 函数读取服务器发送来的数据。
8). 服务器用 read 函数读取客户机发送来的数据,也可以用 write 函数来发送数据。
9). 完成通信以后,用 close 函数关闭 socket 连接。
三、基于TCP协议的简单套接字通信
# 可以用手机来形容整个过程
# 服务端
import socket
# 买手机
iphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
# 装手机卡
iphone.bind(('192.168.11.197', 8080))
# 开机
iphone.listen(5) # 5指的是半连接池的大小
# 等人打电话:拿到电话连接conn
conn, client_addr = iphone.accept()
print(conn)
print('客户端的IP和端口', client_addr)
# 通信中
data = conn.recv(1024) # 最大接受量为1024个字节
print('客户端收来的消息数据', data.decode('utf-8'))
conn.send(data.upper())
# 关闭通信
conn.close()
# 关闭手机(可选操作)
iphone.close()
# 客户端
import socket
# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 直接拨打电话
phone.connect(('192.168.11.197', 8080))
# 通信
msg = input('-------->').strip()
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭连接(必须)
phone.close()
四、通信循环
'''
看上述的客户端服务端的代码,你会发现一个极其尴尬的事情,
当两个互相打电话的时候,客户端说了一句你好,服务端也说了一句你好。
然后就挂掉了电话,这样不是两个傻子么,所以我们要改进
'''
# 服务端
import socket
# 买手机
iphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
# 装手机卡
iphone.bind(('192.168.11.197', 8080))
# 开机
iphone.listen(5) # 5指的是半连接池的大小
# 等人打电话:拿到电话连接conn
conn, client_addr = iphone.accept()
print(conn)
print('客户端的IP和端口', client_addr)
# 通信中
while True:
data = conn.recv(1024) # 最大接受量为1024个字节
print('客户端收来的消息数据', data.decode('utf-8'))
conn.send(data.upper())
# 关闭通信
conn.close()
# 关闭手机(可选操作)
iphone.close()
# 客户端
import socket
# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 直接拨打电话
phone.connect(('192.168.11.197', 8080))
# 通信
while True:
msg = input('-------->').strip()
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭连接(必须)
phone.close()
'''加两个while循环其实就好了'''
五、bug1
'''
我们加了连接循环是不是就以为这两段代码完美了呢,
其实不是,这两段代码还是有两个重要bug的,所以我们既然知道就得想办法去解决这两个bug,
第一个BUG就是我们直接客户端去发送空数据,也就是直接按回车,这时候会发生客户端停滞了,所以这个bug很致命
'''
# 服务端
import socket
# 买手机
iphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
# 装手机卡
iphone.bind(('192.168.11.197', 8080))
# 开机
iphone.listen(5) # 5指的是半连接池的大小
# 等人打电话:拿到电话连接conn
conn, client_addr = iphone.accept()
print(conn)
print('客户端的IP和端口', client_addr)
# 通信中
while True:
data = conn.recv(1024) # 最大接受量为1024个字节
print('客户端收来的消息数据', data.decode('utf-8'))
conn.send(data.upper())
# 关闭通信
conn.close()
# 关闭手机(可选操作)
iphone.close()
# 客户端
import socket
# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 直接拨打电话
phone.connect(('192.168.11.197', 8080))
# 通信
while True:
msg = input('-------->').strip()
if len(msg) == 0:
continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭连接(必须)
phone.close()
'''
我们可以加入一个if判断来判断用户输入是不是空的,如果是空的直接跳过这一次循环就好了,
接下来说明一下为什么这个会BUG还有send, recv的原理
首先来讲BUG,为什么发送空数据会出现BUG,我们先来分析客户端,
msg用户输入东西后,会是在send这里发生了bug吗,我们可以做一个测试,在send下面加一个print,
看看这个会不会输出,如果输出了就不会是send的问题,我们加入了后,发现确实不是send问题,
那么问题肯定就在recv呀,接受这方面出了问题,
那我们去看服务端,服务端一开始就是recv,接受,那8说了,肯定是接收的问题,
我们这边客户端发出了空数据,服务端那边接受不到,
所以就一直等一直等,所以就会发生停滞,是这样的
然后我们来讲讲send 与 recv的区别,大家是不是以为send 与 recv是对应的关系,
其实不是的,我们用socket模块把数据都封装打包后,传到网卡,但是我们是通过什么传到网卡呢,
当然是操作系统了,我们把客户端的send重复写很多遍加入,但是服务端依然只有一次recv,
这样行吗,当然可以,他们之间的流程是这样的,客户端一直send输入到数据里,
然后通过操作系统调用网卡,在到服务端的网卡,在通过操作系统,来传到服务端,
所以服务端还是只用接收一次就好了,所以流程是这样的
'''
六、Bug2
'''
别激动,还有一个大BUG呢,我们在运行服务端 客户端的时候,假如客户端突然停电了,中止了,
这时候服务端就会出现分情况BUG,假如你是windows系统,你的服务端会直接报错 出现一个被迫中止了连接,
如果你是linux或者Mac本,就会出现巨烧CPU的死循环BUG,所以我们要想办法去解决她
'''
# 客户端
import socket
# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 直接拨打电话
phone.connect(('192.168.11.197', 8080))
# 通信
while True:
while True:
msg = input('-------->').strip()
if len(msg) == 0:
continue
phone.send(msg.encode('utf-8'))
data = phone.recv(1024)
print(data.decode('utf-8'))
# 关闭连接(必须)
phone.close()
# 服务端
import socket
# 买手机
iphone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
# 装手机卡
iphone.bind(('192.168.11.197', 8080))
# 开机
iphone.listen(5) # 5指的是半连接池的大小
# 等人打电话:拿到电话连接conn
while True:
conn, client_addr = iphone.accept()
print(conn)
print('客户端的IP和端口', client_addr)
# 通信中
try:
while True:
data = conn.recv(1024) # 最大接受量为1024个字节
print('客户端收来的消息数据', data.decode('utf-8'))
conn.send(data.upper())
except Exception:
continue
# 关闭通信
conn.close()
# 关闭手机(可选操作)
iphone.close()
'''
我们就运用异常处理,来解决这个bug,我们假如只用了异常处理,
就会发现客户端假如突然终止,服务器就直接关闭了,我们可不要这样,
因为服务器关闭的场合真的太少太少,基本服务器不可能关闭的,所以我们又加了一个循环来保证服务器不是关闭状态
'''
七、链接循环
软链接又叫符号链接,这个文件包含了另一个文件的路径名。
可以是任意文件或目录,可以链接不同文件系统的文件。
链接文件甚至可以链接不存在的文件,这就产生一般称之为”断链”的现象,
链接文件甚至可以循环链接自己。类似于编程语言中的递归。 软链接文件只是其源文件的一个标记,
当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能查看软链接文件的内容了。
八、半链接池
'''
上面的代码可以看出半连接池只有5个程序的容量,
我们怎么去仔细研究这个半连接池呢,
我们可以多建立几个客户端,建立6个,然后我们启动5个客户端,你便发现,只有第一个在成功运行,
后面4个都是停滞等待状态,如果这时候我们运行第6个,便发现客户端一点反应都没有,等过一段时间就会报错,
然后我们停止客户端1,我们就发现客户端2就可以正常与服务端进行数据传输,
然后这时候半连接池只有4个客户端在排队了,我们在运行客户端6,这个时候客户端6就会进去排队
'''
九、基于UDP协议的简单网络通信
'''UDP的协议就比较简单了,因为他是不可靠协议,不需要建立连接双向通道'''
# 客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议----->UDP协议
while True:
msg = input('-------->').strip()
phone.sendto(msg.encode('utf-8'), ('192.168.11.197', 8080))
data, server_addr = phone.recvfrom(1024)
print(data.decode('UTF_8'), server_addr)
phone.close()
# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('192.168.11.197', 8080))
while True:
data, client_addr = server.recvfrom(1024) # 最大接受量为1024个字节
print(data.decode('utf-8'), client_addr)
server.sendto(data.upper(), client_addr)
iphone.close() '''
判断是不是会有TCP一样的bug:其实一个都不会有,因为他们并没有建立双向连接通道,
客户端发出数据请求,服务端也不会回应,相反也是如此,所以TCP的BUG,UDP都不会出现,
但也正因如此TCP的数据可靠性很强,而UDP数据可靠性就不强了 '''
十、远程执行命令程序引出TCP协议的粘包问题
粘包就是上一次传入的数据没有一次收干净,跟下一次的数据混合在了一起
服务端
import socket
import subprocess
'''
服务端的两个要求
1.要一直运行,对外提供服务
2.要并发服务多个客户端
'''
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 6668))
server.listen(5) # 设置半连接池的大小
# 服务端应该做的两件事 1.服务端应该循环从半连接池拿出连接请求与其建立连接关系,拿到连接对象
while True:
conn, client_addr = server.accept()
# 2.服务端拿到连接对象应该对其接受数据还有发出数据,进通信循环
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:
break
obj = subprocess.Popen(cmd.decode('utf8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
client.connect(("127.0.0.1", 6668))
while True:
msg = input('-------------->').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf8'))
'''
解决粘包的具体思路
1.拿到数据的总大小 data_size
2.建立一个变量 recv_size 初始值为0,然后循环每收一次数据就 += 接受的长度
3.直到变量recv_size跟数据总大小 data_size 是一样的
'''
cmd_res = client.recv(1024) # 最大接受值 1024
print(cmd_res.decode('gbk'))
'''
粘包问题出现的原因
1.tcp协议是流式协议,数据像流水一样,没有边界
2.一次没有收干净,然后剩下的数据也没有消失,然后下一次就会粘到一起
解决的核心办法就是
直接收干净,不留任何残留
'''
十一、TCP协议的Nagle算法介绍与UDP介绍
'''
TCP协议采用了nagle算法,这个算法是将发送间隔短的量也小的数据都合在一起发送
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
'''
UDP介绍
UDP是没有粘包的,UDP也是没有nagle算法的,面对间隔短量小的数据分批发送,也得多次接收才可以,然后TCP数据量大,没有接收到的部分会一直放在缓存,而UDP会直接扔掉,所以这也是不可靠协议的一个特征
# 自定义协议解决得用到新模块 struct
# 客户端
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 流式协议
client.connect(("127.0.0.1", 6668))
while True:
msg = input('-------------->').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf8'))
'''
解决粘包的具体思路
1.拿到数据的总大小 data_size
2.建立一个变量 recv_size 初始值为0,然后循环每收一次数据就 += 1024
3.直到变量recv_size跟数据总大小 data_size 是一样的
'''
# 先收固定的头信息
header = client.recv(4)
data_size = struct.unpack('i', header)[0]
# 再收真实数据
recv_size = 0
while recv_size < data_size:
cmd_res = client.recv(1024) # 最大接受值 1024
recv_size += len(cmd_res)
print(cmd_res.decode('gbk'))
'''
粘包问题出现的原因
1.tcp协议是流式协议,数据像流水一样,没有边界
2.一次没有收干净,然后剩下的数据也没有消失,然后下一次就会粘到一起
解决的核心办法就是
直接收干净,不留任何残留
'''
# 服务端
import socket
import struct
import subprocess
'''
服务端的两个要求
1.要一直运行,对外提供服务
2.要并发服务多个客户端
'''
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 6668))
server.listen(5) # 设置半连接池的大小
# 服务端应该做的两件事 1.服务端应该循环从半连接池拿出连接请求与其建立连接关系,拿到连接对象
while True:
conn, client_addr = server.accept()
# 2.服务端拿到连接对象应该对其接受数据还有发出数据,进通信循环
while True:
try:
cmd = conn.recv(1024)
if len(cmd) == 0:
break
obj = subprocess.Popen(cmd.decode('utf8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
# 数据的总大小
data_size = len(stdout_res) + len(stderr_res)
# 面对这个总大小,假如我们直接send发送就会沾到一块,所以得想办法解决
# 先包装成头(固定字节):对数据描述信息 int--->字节类型
header = struct.pack('i', data_size)
conn.send(header)
# 再发真实的数据
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
## 十三、解决粘包问题终极大招
#server粘包
import socket
import subprocess
import struct
soc=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('127.0.0.1',8001))
soc.listen(3)
while True:
print('等待客户端连接')
conn,addr=soc.accept()
print('有个客户端连接上了',addr)
while True:
try:
data=conn.recv(1024)
if len(data)==0:
break
print(data)
obj = subprocess.Popen(str(data,encoding='utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#执行正确的结果 b 格式,gbk编码(windows平台)
msg=obj.stdout.read()
#发送的时候需要先把长度计算出来
#头必须是固定长度
#先发4位,头的长度
import json
dic={'size':len(msg)}
dic_bytes=(json.dumps(dic)).encode('utf-8')
#head_count是4个字节的长度
head_count=struct.pack('i',len(dic_bytes))
print(dic)
conn.send(head_count)
#发送头部内容
conn.send(dic_bytes)
#发了内容
conn.send(msg)
except Exception:
break
# 关闭通道
conn.close()
# 关闭套接字
soc.clos
## 十四、Socketserver模块
```python
十五、阿里云服务器的使用

浙公网安备 33010602011771号