Python实现TCP通信

Python实现TCP通信

文章目录

  • Python实现TCP通信
    • 1. 多客户端实现
      • 1.1 客户端
      • 1.2 服务端
    • 2. 单客户端实现
      • 2.1 服务端
      • 2.2 客户端
      • 2.3 注意点


1. 多客户端实现

1.1 客户端

import socket
# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1', 8080))
while True:
    inp = input('>>>:').strip()
    # 如果未输入字节就发送会导致服务端进入阻塞,故需要在输入前进行处理
    if len(inp) == 0:
        continue
    # send() 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))
    # recv() 监听服务端传来的数据
    res = client.recv(1024)
    print(res.decode('utf-8'))
# 套接字关闭
client.close()

1.2 服务端

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', 8080), MyRequestHandler, bind_and_activate=True)
server.serve_forever()


2. 单客户端实现

2.1 服务端

import socket
import struct # 解决粘包问题,对应 **注意点5**


# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据(TCP)
server = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
# 重用端口避免重启导致端口占用,对应 **注意点4**
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 

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

# listen(n)传入的值, n表示的是服务器拒绝(超过限制数量的)连接之前,操作系统可以挂起的最大连接数量。n也可以看作是"排队的数量"
server.listen(5)

# 对应 **注意点3**
while True:
    # 阻塞式等待建立连接请求,会返回两个值,一个是连接会话,一个是连接的客户端IP与端口
    conn,ip_addr = server.accept()

    while True:   
        try:
            # 接收客户端传递的数据
            res = conn.recv(1024)
            # 避免发送空字符,对应 **注意点2**
    	    if len(res) == 0:
    	    # 因为外层还有个循环,所以用break 区别与 客户端的continue
	            break
	        header = struct.pack('i', len(res))
	        conn.send(header)
            # 将客户端的数据接收到以后,转换成大写编码后,再发送给客户端
            conn.send(res.decode('utf-8').upper().encode('utf-8'))
        except Exception:
            break
    # 关闭当前的,接收一个新的连接        
	conn.close()
	
# 关闭套接字
server.close()

2.2 客户端

import socket

# 创建套接字对象,AF_INET基于TCP/UDP通信,SOCK_STREAM以数据流的形式传输数据,这里就可以确定是TCP了
client = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)

# 连接服务端
client.connect(('127.0.0.1',8080))

# 对应 **注意点1**
while True:
    inp = input('>>>:').strip()
    
    # 输入内容不能为空!对应 **注意点2**
    if len(inp) == 0:
        continue
        
    # 向服务端发送数据,需要转换成Bytes类型发送
    client.send(inp.encode('utf-8'))
    
    # 接收到这个固定长度的数据包
    headers = client.recv(4)
    
    # 解析这个数据包,获取里面的内容,也就是数据长度
    data_len = struct.unpack('i',headers)[0]
    count = 0
    # 不断循环获取缓冲区里面的数据,直到获取完毕为止
    while count < data_len:
        data = client.recv(1024) # 每次都获取1024个字节
        count += len(data)
        print(data.decode('utf-8'))

# 套接字关闭
client.close()

2.3 注意点

  1. 持续监听客户端请求——循环
  2. 客户端发送为空会导致服务端卡住阻塞——客户端加判断
  3. 客户端断开连接会导致服务端异常——再增加循环,捕捉异常后跳转到阻塞等待
  4. 重启服务端可能会出现端口占用——端口来不及释放,添加绑定参数实现端口复用
  5. 数据沾包问题——接受端拿到包的长度后通过循环不断取出数据,引入struck模块

沾包问题:
定义:数据全部粘在一起,如果一次性未取完,下一次接着上一次未取完的数据部分接着取。

TCP:由于tcp是数据流的形式接收,所有发送数据都会粘着一起,如果接收数据超过我们指定的,则会出现粘包现象,那么此时这个数据还会保存在缓冲区,这里也就是recv内,待我们下次再次recv时,取到的就会是上次没有取完的数据。
UDP:如果接收数据超过我们指定的,则放弃那些数据。下次再次接收又是全新的数据,所以不会出现粘包现象。

TCP传输特点造成的沾包:

  1. 会将数据量较小,且发送时间间隔较短的的数据一起打包发送,那么这里所讲的时间较短是相比较网络延迟来说的,
    比如我们两次发送间隔为0.00001秒,那么网络延迟为0.001,这个时候两次的数据就会打包发送,这是一种优化机制。
  2. TCP协议发送数据时,是源源不断的发送,像水流一样,因此TCP又叫流式协议。

两种可能造成粘包的情况:

  • 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
  • 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

解决方法:
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
粘包问题参考链接

参考网址链接
Python socket网络编程

你可能感兴趣的:(计算机网络,tcp/ip,python,网络)