THIS IS B3c0me

记录生活中的点点滴滴

0%

Socket编程-Python

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

#创建套接字对象,AF_INET基于TCP/UDP通信;SOCK_STREAM以数据流的形式传输数据,因此确定是TCP
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

#server = socket.socket()等同于上面的写法

#绑定ip地址和端口,127.0.0.1回环地址只能当前计算机访问
server.bind(('127.0.0.1', 8877))

#建立半连接池,允许存放5个请求
server.listen(5)

#等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端的IP和端口号
conn, ipaddr = server.accept()

#接受客户端传递的数据,只接收1024字节数据
res = conn.recv(1024)

#查看连接客户端的IP和端口
print(ipaddr)

#接收到的是Byte类型,需要解码
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))

#向服务器发送数据,需要转换成Bytes类型发送
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

#创建套接字对象,AF_INET基于TCP/UDP通信;SOCK_STREAM以数据流的形式传输数据,因此确定是TCP
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

#server = socket.socket()等同于上面的写法

#绑定ip地址和端口,127.0.0.1回环地址只能当前计算机访问
server.bind(('127.0.0.1', 8877))

#建立半连接池,允许存放5个请求
server.listen(5)

#等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端的IP和端口号
conn, ipaddr = server.accept()

while True:
#接受客户端传递的数据,只接收1024字节数据
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()
#向服务器发送数据,需要转换成Bytes类型发送
client.send(inp.encode('utf-8'))

#接收服务端返回的数据,不超过1024字节
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
#向服务器发送数据,需要转换成Bytes类型发送
client.send(inp.encode('utf-8'))

#接收服务端返回的数据,不超过1024字节
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:
#等待建立连接请求,会返回两个值,一个是连接状态,一个是连接的客户端的IP和端口号
conn, ipaddr = server.accept()

while True:
#接受客户端传递的数据,只接收1024字节数据
res = conn.recv(1024)

#当res的长度为0,说明客户端断开了连接, 我们中断当前的while
if len(res) == 0:
break

#将客户端的数据接收到以后,转成大写再编码后发送给客户端

conn.send(res.decode('utf-8').upper().encode('utf-8'))
#接收一个新的连接
conn.close()
问题:连接已经关闭,但是端口还没来得及释放
解决方法:
  1. 更换端口

  2. 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)

    #绑定IP地址和端口
    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

#拉取socket对象
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

#绑定IP和端口
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()

#不需要判断是否为空,因为UDP每次发送的都不只是单纯的数据,还有Ip和端口信息

#直接向写好的Ip和端口发送数据
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))

#监听状态,允许半连接池存在5个连接
server.listen(5)

#等待建立连接,有连接请求过来后开始tcp三次握手协议,建立成功
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)

#绑定ip地址和端口号
server.bind(('127.0.0.1', 8811))

#建立半连接池,允许存放5个请求
server.listen(5)

while True:

#等待建立连接请求,会返回两个值,一个是连接状态,一个是客户端Ip与端口
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'))

#将接收字节量调整为10个
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
#向服务器发送数据,需要转换成Bytes类型发送
client.send(inp.encode('utf-8'))

#接收服务端返回的数据,不超过1024字节
res = client.recv(1024)
print(res.decode('utf-8'))

#关闭套接字
client.close()

可以创建多个客户端同时与服务端进行通信

欢迎关注我的其它发布渠道