最终目标
最终目标是实现一个能够双方互相通信的socket
socket原理
一个接收端和一个发送端,接收端和发送端分别有各自的应用,通信就是发送端想发送给指定的应用,想要找到对应的主机则需要IP,找到IP就能知道发送的地址,需要找到指定的应用则则需要端口,一个端口对应一个应用。
0.分析伪代码
接收端
1.首先导入socket ## import socket。
2.然后指定协议类型,指定为TCP/IP。
3.等待时间,等发送端发送数据。
4.接收到了数据。
5.再把需要发送的数据发送回去。
6.继续等待接收数据。
发送端
1.同样导入socket ## import socket。
2.指定网络协议类型,为TCP/IP。
3.进行连接接收端,输入ip和端口号,##connect(a.ip,a.porn)。
4.将数据发送过去。##connect.send(hello)
5.等待接收返回的数据。
6.关闭连接。
开始写代码
1.最简单的实现互相通信
客户端(发送端)
#客户端
import socket
client = socket.socket() # 创建socket实例并且声明socket类型
client.connect(("localhost",9999)) #connect只接收一个参数,但是我们需要传入ip和端口,所以用元组将两个参数包裹起来
client.send('你好世界'.encode('utf-8')) #传数据
data = client.recv(1024) #接收服务端的返回
print(data.decode()) #打印服务端的返回
client.close() #关闭连接
服务端(接收端)
import socket
server = socket.socket()
server.bind(('localhost',9999)) #绑定要监听端口
server.listen() #监听
conn,addr = server.accept() #等客户端连进来
print(conn,addr) #打印下conn和addr,可以看出conn为一个内存地址,addr为ip地址和一个随机端口
data = conn.recv(1024)
print('recv:',data)
conn.send(data.upper())
server.close()
笔记:
conn是客户端连接后再服务器端为其生成的一个连接实例。
addr是ip地址和一个随机端口,这个端口是服务端发到的客户端接收的端口。
9999是服务端的端口号,而addr中第二个参数是服务端返回数据时客户端的端口号。
2.实现双方多次通信
客户端(发送端)
客户端的变化:
添加了一个判断是否直接回车,如果回车那么就重新输入。
#客户端
import socket
client = socket.socket()
client.connect(("localhost",9999)) #绑定端口IP和接口
while True: #实现多次通信
msg = input('>>>:').strip() #输入
if len(msg) == 0: #判断输入是否为空,如果直接输入回车那就重新输入
continue
client.send(msg.encode('utf-8'))#发送输入的数据
data = client.recv(1024) #等待服务端返回的数据。
print("recv:",data.decode()) #输出服务端返回的数据
服务端(接收端)
# 服务端
import socket
server = socket.socket() #声明socket类型
server.bind(('localhost', 9999)) #绑定主机ip和端口
server.listen(5) #设定最大连接的客户端数量
print('dengdianhua')
while True: #这个作用是解决当客户端断开时会导致服务端异常断开的错误
conn, addr = server.accept()
print(conn, addr)
while True: #这个死循环作用是实现多次通信
data = conn.recv(1024) #接收数据
print('recv:', data) #打印传过来的数据
if not data: #判断data是否为空,如果为空,则代表客户端断开了
print('client ha host') #连接中断
break
conn.send(data.upper()) #返回个客户端数据并将字母大写
以上就实现了客户端和服务端的多次通信。
问题:
这个程序我发现一个我无法解决的bug,就是我判断了当客户端断开时服务器端会断开并等待下一次的请求,但是我运行程序时断开客户端,服务器端还是会出现错误。希望能有大神能帮我解决一哈。
3.能够使用简单的命令
客户端发送命令,服务端执行并将结果返回给客户端。
客户端(发送端)
客户端的变化:
#客户端
import socket
client = socket.socket()
client.connect(('localhost', 9999))
while True:
cmd = input('>>:').strip()
if len(cmd) == 0: #判断是否为直接敲了回车
continue
client.send(cmd.encode('utf-8')) #发送数据
cmd_res_size = client.recv(1024) #接收服务器返回的数据
print('命令结果大小:', cmd_res_size) ******防粘包
client.send("准备接收数据".encode('utf-8'))
received_size = 0
received_data = b""
while received_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_size += len(data)
received_data += data
else:
print('cmd res recvive done...', received_size)
print(received_data.decode())
client.close()
服务端(接收端)
# 服务端
# Author:fang
import socket, os
server = socket.socket()
server.bind(("localhost", 9999))
server.listen(5)
while True:
conn, addr = server.accept()
print('new conn', addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print('客户端已断开')
break
print('执行指令:', data)
cmd_res = os.popen(data.decode()).read()
print('before send', len(cmd_res))
if len(cmd_res) == 0:
cmd_res = 'cmd has no output'
conn.send(str(len(cmd_res.encode())).encode())
client_ack = conn.recv(1024)
conn.send(cmd_res.encode('utf-8'))
print('send done')
server.close()
结果
遇到的问题:
粘包:
连续发送数据的时候,服务端的缓存机制将缓存中的数据发送给服务端,数据可能会粘到一起发送到客户端。
方法一
sleep一小段时间。
import time
time.sleep(0.5)
缺点:会影响服务器运行速度。
方法二
在连续的两次send间隙向服务端或客户端(客户端向服务端发,服务端向客户端发)接收客户端发过来的一次请求或者消息,会将缓存的数据直接发出去,这样就会不会将两份前后发的数据粘到一起。
代码
服务端
conn.send(str(len(cmd_res.encode())).encode())
client_ack = conn.recv(1024) #接收客户端器发送数据
conn.send(cmd_res.encode('utf-8'))
客户端
cmd_res_size = client.recv(1024) #第一次等待服务端发送数据
print('命令结果大小:', cmd_res_size) #打印要传输过来的数据大小
client.send("准备接收数据".encode('utf-8')) #告诉服务端自己已经准备好接收数据了
总结
- 熟悉了socket的基本用法,对于TCP/IP协议也有了一定的了解。
- 实现了服务端和客户端双方通信,并能多次通信。
- 实现了发送命令给服务端,服务端执行命令并将得到的结果返回给客户端
- 解决了编程中遇到的各种问题,比如编码问题,粘包问题,经过努力最终解决了问题。