从原理到代码实践:Python Socket 网络编程

预备知识建议:初步了解TCP.IP基础知识,Python基础知识,掌握bytes数据类型的用法。

1、TCP与UDP协议介绍

TCP/UDP 原理

TCP/UDP 位于OSI 七层模型的第4层,在IP层之前。
在这里插入图片描述
尽管TCP和UDP都基于IP层,UDP是无连接服务,也就是说,只是IP层通了,UDP就可以发送消息,对消息无编号。而 TCP 提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用 TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接逻辑通道。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。连接建立以后,可以持续发送消息,每个消息都会编号,如果丢失,有重发机制保障完整性。
通常,重要业务应使用TCP,不重要的实时性业务使用UDP.

在网络上的发送的TCP/IP消息,如同多层包裹

  • 发送侧,是打包过程,最里面的是用户数据,先包MAC层,其次IP层,再次TCP或UDP层,最上面可能是http 层。
    在这里插入图片描述

  • 接收侧,则是解包过程,先解开 http层,再解TCP层,其次为 IP ,MAC层,最后得到了包裹里的物品,即数据。

TCP消息结构

在这里插入图片描述
消息到TCP这一层,会添加两部分内容, TCP首部,TCP数据。
在这里插入图片描述

每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端 IP地址唯一确定一个TCP连接。

一个IP地址和一个端口号也称为一个插口 Socket。这个术语出现在最早的 TCP规范(RFC793 )中,后来它也作为表示伯克利版的编程接口。插口对Socket Pair, 包含客户IP地址、客户端口号、服务器 IP地址和服务器端口号(也称Socket四元组), 可唯一确定网络中1条TCP连接的双方。

序号用来标识从TCP发端向TCP收端发送的数据字节流,如果将字节流看作在两个应用程序间的单向流动,则 TCP用序号对每个字节进行计数。序号是32 bit的无符号数,序号到达最大值后又从0开始。

TCP的三次握手建立连接

我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
• 客户端向服务器发送一个SYN J
• 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
• 客户端再想服务器发一个确认ACK K+1

这个三次握手发生在socket的那几个函数中呢?请看下图:
在这里插入图片描述

之后,就建立了TCP连接,或者说 soket 连接建立完成, 就可以通过这个socket相互发送消息了。

在网络上传输的TCP、UDP消息,可以通过 WireShark等抓包工具捕获并解包查看(如下图),初学者可以使用这个工具对网络通信建立更直观的认识。
在这里插入图片描述

2. TCP Socket 编程实现步骤

对应TCP三层握手,Python Server端与客户端实现流程如下:
在这里插入图片描述
实现代码如下
在这里插入图片描述

3. UDP Socket编程实现步骤

在这里插入图片描述
实现代码:
在这里插入图片描述
UDP编程的特点:

  1. socket建立时,type=socket.SOCK_DGRAM,(TCP为SOCK_STREAM)
  2. UDP socket server 端代码在进行bind后,无须listen方法。accept 方法,

4. Socket类主要方法

Socket 对象构建方法

使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字

socket.socket([family[, type[, proto]]])
•	family : AF_INET (默认ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系统进程间通信).
•	type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) .
•	protocol : 一般为0或者默认  

type 类型参数说明
在这里插入图片描述

Socket主要方法

Server方法:
• bind(address) 绑定主机地址与端口号
• listen(backlog) 监听
• accept 接受客户端连接请求

Client 方法

  • connect() 连接服务器

服务端与客户端的公共方法
s.recv() #接收TCP数据
s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() #接收UDP数据,
s.sendto() #发送UDP数据
s.getpeername() #连接到当前套接字的远端的地址
s.getsockname() #当前套接字的地址
s.getsockopt() #返回指定套接字的参数
s.setsockopt() #设置指定套接字的参数
s.close() #关闭套接字

面向锁的套接字
s.setblocking() #设置套接字的阻塞与非阻塞模式
s.settimeout() #设置阻塞套接字操作的超时时间

Accept 函数
返回值为元组
(clientsocket, address) = serversocket.accept()

Send函数
s.send(string[,flag])
s为socket.socket()返回的套接字对象
string : 要发送的字符串数据
flag : 提供有关消息的其他信息,通常可以忽略
Return值是成功发送的字节数

**s.sendall(string[,flag])**
#完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。

recv函数
s.recv(bufsize[,flag])
s为socket.socket()返回的套接字对象
bufsize : 指定要接收的数据大小 ,2的整数倍,要小于系统规定的buffsize, 建议1024
flag : 提供有关消息的其他信息,通常可以忽略
返回收到的数据,是1个bytes类型的对象

Socket 其它方法
socket.SocketType()

socket.getaddrinfo()、
此函数返回1个5-tuple 的列表
(family, type, proto, canonname, sockaddr)

socket.getpeername()
获取soket连接对端地址

Socket.Setsockopt()
设置socket选项

5. Socket发送与接收文件与图片示例

Socket 发送时,只用bytes 方式发送,所以传送内容必须转换成bytes,需要做一定的处理
在这里插入图片描述

大文件的发送与接收

发送方:当发送大块数据时,如果超过了1条IP包允许的最大字节限制,这就要求1条数据被拆分成多次发送,

   def mysend(socket, msg):
        totalsent = 0
        while totalsent < MSGLEN:
            sent = socket.send(msg[totalsent:])
            if sent == 0:
                raise RuntimeError("socket connection broken")
            totalsent = totalsent + sent
	s.send(len(message)
	mysnd(s,message)

接收方,要根据内容大小与最大窗口,循环接收。

    def myreceive(self):
        chunks = []
        bytes_recd = 0
        while bytes_recd < MSGLEN:
            chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
            if chunk == b'':
                raise RuntimeError("socket connection broken")
            chunks.append(chunk)
            bytes_recd = bytes_recd + len(chunk)
        return b''.join(chunks)

6. SocketServer处理并发请求

网络编程,1个常见场景,就是要考虑如何处理并发请求。 Python提供了SocketServer模块,简化server端的socket编程,
其中 ThreadingTCPServer子类基于多线程方式,可并行处理多个客户请求。当每个客户端请求连接到服务器时,Socket服务端都会在服务器创建一个“线程””,专门负责处理当前客户端请求,多个用户就创建多个线程。

实现步骤:

  • 创建处理客户请求的handler类,从StreamRequestHandler继承,要实现handle()方法。
  • 创建 socketserver.ThreadingTCPServer服务器实例,
  • 启动服务端event loop.

客户端按正常的socket client实现步骤编程即可。

实现代码示例

import socketserver
import threading

ServerAddress = ("127.0.0.1", 6060)

class MyTCPClientHandler(socketserver.StreamRequestHandler):
    def handle(self):
        # Receive and print the data received from client
        print("Recieved one request from {}".format(self.client_address[0]))
        msg = self.rfile.readline().strip()
        print("Data Recieved from client is:".format(msg))
        print(msg)  
        print("Thread Name:{}".format(threading.current_thread().name))

# Create a Server Instance
TCPServerInstance = socketserver.ThreadingTCPServer(ServerAddress, MyTCPClientHandler)
# Make the server wait forever serving connections
TCPServerInstance.serve_forever()

客户端为测试多个清求,在本地用多线程发起多个连接请求至服务器。


import socket
import threading

def Connect2Server():
    #Create a socket instance
    socketObject = socket.socket()
    #Using the socket connect to a server...in this case localhost
    socketObject.connect(("localhost", 6060))
    print("Connected to localhost")
    # Send a message to the web server to supply a page as given by Host param of GET request
    HTTPMessage = "GET / HTTP/1.1\r\nHost: localhost\r\n Connection: close\r\n\r\n"
    bytes = str.encode(HTTPMessage)
    socketObject.sendall(bytes)
    # Receive the data
    while(True):
        data = socketObject.recv(1024)
        print(data)
        if(data==b''):
            print("Connection closed")
            break

    socketObject.close()

 
print("Client - Main thread started")  
ThreadList  = []
ThreadCount = 20

for index in range(ThreadCount):
    ThreadInstance = threading.Thread(target=Connect2Server())
    ThreadList.append(ThreadInstance)
    ThreadInstance.start()


# Main thread to wait till all connection threads are complete
for index in range(ThreadCount):
    ThreadList[index].join()

7. Socket编程注意事项

Socket 编程相当灵活,开发者需要处理好数据编码,接收窗口大小,超时或失败重传等事项。 在实际应用socket方式通信时,请求-响应的消息对及内容,应该尽可能简洁,消息类别要少,内容变化少,以减少出错机率。

对于复杂的通信场景,不稳定的网络,非局域网环境等场景,建议:
1、使用成熟的第3方soket库,如 PyZMQ, Twisted库,对socket 进行封装装,简化了编程,增强了网络容错处理能力。
2、使用高层协议,如基于http的 Websocket,来代替TCP编程,当然,多了几层协议,带宽开销也增加了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值