TCP
初级版:
实现一次服务器和客户端的数据通信
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import socket
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8877))
server.listen(5)
conn, ipaddr = server.accept()
res = conn.recv(1024)
print(ipaddr)
print(res.decode('utf-8'))
conn.close()
server.close()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8877))
client.send('hello'.encode('utf-8'))
client.close()
|
先运行服务端,再运行客户端,可以在服务端的界面看到客户端发来的消息”hello”,以及发送时所用的端口号
第二代:
实现多次通信
服务器端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import socket
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8877))
server.listen(5)
conn, ipaddr = server.accept()
while True: res = conn.recv(1024) conn.send(res.decode('utf-8').upper().encode('utf-8'))
conn.close()
server.close()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8877))
while True: inp = input('>>>: ').strip() client.send(inp.encode('utf-8')) res = client.recv(1024) print(res.decode('utf-8'))
client.close()
|
可以实现多次数据的传输,服务器端将从用户端接收到的数据转成大写后返回到用户端
存在的问题:
当客户端发送空内容到服务器时,服务端会阻塞在原地等待
解决方法:
在客户端输入后判断是否为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8877))
while True: inp = input('>>>: ').strip() if len(inp) == 0: continue client.send(inp.encode('utf-8')) res = client.recv(1024) print(res.decode('utf-8'))
client.close()
|
依然存在的问题:
当客户端主动断开连接时,服务端也会因为没有连接而随之关闭
优化方案:
当检测到客户端断开连接时,让服务端重新进入等待连接请求的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| while True: conn, ipaddr = server.accept()
while True: res = conn.recv(1024) if len(res) == 0: break
conn.send(res.decode('utf-8').upper().encode('utf-8')) conn.close()
|
问题:连接已经关闭,但是端口还没来得及释放
解决方法:
更换端口
bind绑定IP之前添加一个参数,达到端口复用的目的
1 2 3 4 5 6 7
| import os
server = socket.socket(family = socket.AF_INET,type=socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8877))
|
UDP
UDP无连接,所以发送数据前不需要先建立连接
可以使用多个客户端与服务端进行数据发送,服务端可以逐个回复
UDP发送数据采用的是数据报模式,数据会一次性全部发过去,如果未接收到也不会保存,安全性没有保障
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8899))
while True: data, addr = server.recvfrom(1024) print(f'来自{addr}发送的一条消息:', data.decode('utf-8')) send_data = input('回复信息:')
server.sendto(send_data.encode("utf-8"), addr)
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_ip = ('127.0.0.1', 8899)
while True: send_data = input("输入发送的数据: ").strip()
client.sendto(send_data.encode("utf-8"),server_ip)
receive_data,addr = client.recvfrom(1024)
print(f'来自{addr}的回信:{receive_data.decode("utf-8")}')
|
配合subprocess模块实现远程执行命令
与套接字的搭配使用,通过客户端执行服务端的系统命令
使用tcp进行连接
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import socket import subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8866))
server.listen(5)
while True: conn, ipadd = server.accept()
while True: cmds = conn.recv(1024) if len(cmds) == 0: break cmds = subprocess.Popen(cmds.decode('gbk'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = cmds.stdout.read() stderr = cmds.stderr.read()
conn.send(stdout+stderr)
conn.close()
server.close()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8866))
while True: cmds = input(">>>:").strip() if len(cmds) <= 0: print("输入内容不能为空!") continue client.send(cmds.encode('gbk'))
data = client.recv(1024) print(data.decode('gbk'))
client.close()
|
先执行服务端,再运行客户端,即可以在客户端使用windows shell.
存在的问题:
当客户端只接受1024个字节,但是服务端发送过来的数据超过了1024字节,那该怎么办?
粘包问题
粘包指的是:数据全部粘在一起,如果一次性未取完,下一次接着上次未取完的数据部分接着取
tcp:recv(1024)
udp:recvfrom(1024)
粘包是有tcp产生的,由于tcp是数据流的形式接收,所有发送数据都会粘在一起,如果接收数据超过我们指定的,则会出现粘包现象,那么此时这个数据还会保存在缓冲区,也就是recv里,待我们下次再recv时,取到的就是上次没有取完的数据
udp中,如果接收数据超过我们指定的,则放弃那些数据。下次再次接收又是全新的数据,所以不会出现粘包现象
tcp粘包实例:
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import socket
server = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', 8811))
server.listen(5)
while True:
conn, ip_addr = server.accept()
while True: res = conn.recv(1024)
if len(res) == 0: break
conn.send(res.decode('gbk').upper().encode('gbk'))
conn.close() server.close()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import socket
client = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8811))
while True: inp = input('>>>:').strip() if len(inp) == 0: continue client.send(inp.encode('gbk'))
res = client.recv(10)
print(res.decode('gbk'))
client.close()
|
先运行客户端,再运行服务端,发现当服务端返回的字节过长时,客户端的下一次请求会收到以前的数据
粘包问题解决:
定义一个固定的头部长度作为标识,只要这个头部标识对上了,就可以把这个包解开,获取我们想要的数据
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| while True: conn, ipadd = server.accept()
while True: cmds = conn.recv(1024) if len(cmds) == 0: break cmds = subprocess.Popen(cmds.decode('gbk'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = cmds.stdout.read() stderr = cmds.stderr.read()
header = struct.pack('i', len(stdout) + len(stderr))
conn.send(header) conn.send(stdout+stderr)
conn.close()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| while True: cmds = input(">>>:").strip() if len(cmds) <= 0: print("输入内容不能为空!") continue client.send(cmds.encode('gbk'))
headers = client.recv(4)
data_len = struct.unpack('i', headers)[0] count = 0
while(count < data_len):
data = client.recv(1024) count += len(data) print(data.decode('gbk'))
client.close()
|
socketserver模块
实现实现服务端同时与多个客户端进行连接
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import socketserver
class MyRequestHandler(socketserver.BaseRequestHandler): def handle(self): while True: try: data = self.request.recv(1024) if len(data) == 0: break self.request.send(data.upper()) except Exception: break self.request.close()
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8822),MyRequestHandler, bind_and_activate=True) server.serve_forever()
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import socket
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8822))
while True: inp = input('>>>: ').strip() if len(inp) == 0: continue client.send(inp.encode('utf-8'))
res = client.recv(1024) print(res.decode('utf-8'))
client.close()
|
可以创建多个客户端同时与服务端进行通信