python通过socket实现tcp/udp通信
# 网络通信开发基础-脚本开发-第一关
人生没有白走的路,你走的每一步都算数。
任务目的
建立 socket 连接通道,可以相互之间传输数据
采用语言
python
基础原理
OSI七层模型
各层的基本作用
socket通信主要实现于传输层
传输层功能
tcp和udp
tcp
tcp(传输控制协议),面向连接,也就是说tcp连接的建立和释放,需要经过三次握手和四次挥手。同时在建立连接后的数据传输过程中,tcp会有四个机制来保证数据可靠传输;
1.确认应答和序列号
2.超时重传
3.流量控制
4.拥塞控制
通过这些机制,确保数据不会丢失。这很明显,是tcp面向连接的有点。因为这样,也导致存在了一些缺点。
慢,效率低,占用系统资源高,易被攻击。
udp
udp(用户数据报协议),是一个非连接的协议,传输数据之前源端和终端不建立连接, 当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、 计算机的能力和传输带宽的限制; 在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等, 因此一台服务机可同时向多个客户机传输相同的消息。
udp的报头短,只有8个字节,相比tcp的最小20字节来说,开销更小
udp吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、 源端和终端主机性能的限制
因为无连接,所有udp不保证可靠交付,只保证把数据发出去就行。没有丢失重传机制。
区别
1、基于连接与无连接;
2、对系统资源的要求(TCP较多,UDP少);
3、UDP程序结构较简单;
4、流模式与数据报模式 ;
5、TCP保证数据正确性,UDP可能丢包;
6、TCP保证数据顺序,UDP不保证。
python实现socket通信
socket介绍
socket又称“套接字”,socket会通过udp/tcp协议来发送数据,用来实现两台机器的简单通信.socket是基于C/S架构的,所以socket网络编程,需要编写客户端程序和服务端程序。
socket通信流程
TCP通信
socket关键函数介绍
函数 | 描述 |
---|---|
socket() | 获取socket类对象 |
bind((hostname, port)) | 在指定主机的端口绑定监听 |
listen() | 在绑定端口上开启监听,参数表示最大等待建立连接的个数 |
accept() | 等待客户端连接,连接后返回客户端地址 |
send(data) | 发送数据,data 是二进制数据 |
recv(buffer) | 表示接收数据, buffersize 是每次接收数据的长度 |
close() | 关闭套接字连接 |
connect((hostname, port)) | 设置要连接的主机名称与端口号 |
代码及介绍
server
import socket
# 创建一个socket对象,默认TCP套接字
s = socket.socket()
# 绑定端口
s.bind(('127.0.0.1',9999))
# 监听端口
s.listen(10)
print("正在连接中……")
# 建立连接之后,持续等待连接
while 1:
# 阻塞等待连接
sock,addr = s.accept()
print(sock,addr)
# 一直保持发送和接收数据的状态
while 1:
text = sock.recv(1024)
# 客户端发送的数据为空的无效数据
if len(text.strip()) == 0:
print("服务端接收到客户端的数据为空")
elif text.decode() == "exit":
print("客户端下线")
break
else:
print("收到客户端发送的数据为:{}".format(text.decode()))
content = input("请输入发送给客户端的信息:")
# 返回服务端发送的信息
sock.send(content.encode())
sock.close()
client
import socket
# 创建一个socket对象
s1 = socket.socket()
s1.connect(('127.0.0.1',9999))
# 不断发送和接收数据
while 1:
send_data = input("客户端要发送的信息:")
# socket传递的都是bytes类型的数据,需要转换一下
if send_data=="exit":
info="exit"
s1.send(info.encode())
break
else:
s1.send(send_data.encode())
# 接收数据,最大字节数1024,对返回的二进制数据进行解码
text = s1.recv(1024).decode()
print("服务端发送的数据:{}".format(text))
print("------------------------------")
注:服务端可以持续监听连接,客户端下线,服务端自动断开连接。客户端再次上线,服务端建立连接
效果图
UDP通信
流程:
- 导入包socket
- 创建一个套接字
- 收发消息(优先发送)
- 关闭套接字
client
import socket
def main():
ip = "127.0.0.1" # 对方ip和端口
port = 8888
other_addr = (ip, port)
byte = 1024
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)# 创建socket
while True:
send_data = input("输入要发送的信息:").encode("utf-8")
udp_socket.sendto(send_data, other_addr)
"""输入数据为空退出,否则进入接收状态"""
if send_data:
recv_data, other_addr = udp_socket.recvfrom(byte)
print("收到来自%s的消息: %s" % (other_addr, recv_data.decode("utf-8")))
else:
break
udp_socket.close()
if __name__ == '__main__':
main()
server
流程:
- 导入包socket
- 创建一个套接字
- 绑定信息
- 收发消息(优先接收)
- 关闭套接字
import socket
def main():
ip = ""
port = 8888
own_addr = (ip, port) # 接收方端口信息
byte = 1024
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(own_addr) # 绑定端口信息
while True:
recv_data, other_addr = udp_socket.recvfrom(byte)
print("收到来自%s的消息: %s" % (other_addr, recv_data.decode("utf-8")))
send_data = input("输入要发送的信息:").encode("utf-8")
udp_socket.sendto(send_data, other_addr)
"""输入数据为空退出"""
if send_data:
pass
else:
break
udp_socket.close() # 关闭socket
if __name__ == '__main__':
main()
效果图
扩展-远程执行命令
server
from socket import *
import subprocess
import struct
# address=('127.0.0.1',10000)
# back_log=5
# buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM) ##定义tcp协议的socket接口
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) ##加入这条socket配置,重用IP和端口,还可以在操作系统上配置ip和端口重用
tcp_server.bind(('127.0.0.1',10000)) ##为套接字绑定IP和端口,(IP和端口可以标示全网唯一)
tcp_server.listen(5) ## 可以接收的接收的连接数,并且监听端口
while True:
conn,addr=tcp_server.accept() ##建立链接,并堵塞端口
print(addr)
while True:
try: ## 异常处理,防止客户端不正常断开
data_cmd = conn.recv(1024) ## 接收消息
if not data_cmd:break ##防止接收的消息为空
print('收到客户端的命令'+str(data_cmd.decode()))
## 通过subprocess.Popen执行操作系统命令,并且把返回值重定向到subprocess.PIPE的管道零时存放
res = subprocess.Popen(data_cmd.decode('utf-8'), shell=True,stderr=subprocess.PIPE,\
stdout=subprocess.PIPE,stdin=subprocess.PIPE)
err = res.stderr.read() ##通过res.stderr.read()读取错误消息
if err:
res_data=err
else:
res_data=res.stdout.read() ##通过res.stdout.read()读取标准输出消息
if not res_data:
res_data = '执行成功'.encode('gbk') ##防止操作系统执行命令后,返回值为空时,为客户端返回此消息
length = len(res_data) ## 计算返回值数据长度
data_length = struct.pack('i', length) ## 通过struct.pack处理返回值数据长度,并且也只能占用4个字节
conn.send(data_length) ## 发送返回值数据长度(4字节)作为返回给客户端消息的包头,因为是定长的,所\
# 以客户端在接收时,第一次接收4个字节,就知道服务端返回的消息长度,就可以到自己的内存按次长度获取数据
conn.send(res_data) ## 发送数据
except Exception as e:
print(e) ##处理方式
break
client
from socket import *
import subprocess
import struct
# ip_port=('127.0.0.1',10000)
buff_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(('127.0.0.1',10000)) ## 链接服务端
while True:
cmd_data=input('请输入命令:') ## 输入命令
if not cmd_data:continue ##判断是否输入空值,为空跳出本次循环,让用户重新输入
if cmd_data == 'quit': break ## 当用户输入quit时,断开链接(相当于不正常断开)
tcp_client.send(cmd_data.encode('utf-8')) ## 发送命令
length_data = tcp_client.recv(4) ## 第一次接收4字节,就是服务端用struct.pack封装为4字节的包头
head_data=struct.unpack('i',length_data)[0] ## 街封装,结果是列表类型
# 处理数据
res_length=0
res_msg=b''
while res_length < head_data:
res_msg += tcp_client.recv(buff_size)
res_length += len(res_msg)
print(res_length)
print('命令的执行结果是 ', res_msg.decode('gbk'))
tcp_client.close()