TCP与UDP编程

一、套接字

套接字(socket)是用于网络通信的数据结构,在任何类型的通信开始之前,都必须创建Socket。

Socket主要分为面向连接的Socket和无连接的Socket。面向连接的Socket使用的主要协议是传输控制协议,就是TCP,TCP的Socket的名称为SOCK_STREAM。无连接的Socket主要协议是用户数据报协议,就是常说的UDP,UDP Socket的名字是SOCK_DGRAM。

1.1、建立TCP服务端

Socket主要分为客户端和服务端。客户端用于建立与服务端的连接,服务端用于等待客户端的连接。因此在使用客户端连接之前必须要建立服务端。

服务端Socket除了要指定网络类型(IPv4和IPv6)和通信协议(TCP和UDP)外,还要指定一个端口号。所有建立在TCP/UDP之上的通信协议都具有默认的端口号。例如,HTTP协议的默认端口号是80,HTTPS协议的默认端口号是443.这些都是应用层协议,建立在TCP协议之上。

Python中创建服务端程序,需要使用Socket模块中的Socket类。步骤如下:

  1. 创建Socket对象
  2. 绑定端口号
  3. 监听端口号
  4. 等待客户端的连接
  5. 读取从客户端发送过来的数据
  6. 给客户端发送数据
  7. 关闭客户端的连接
  8. 关闭服务端的连接
from socket import *
# 定义一个空的主机名,在建立服务端Socket一般不需要使用host
host = ""
# 用于接收客户端数据时的缓冲区尺寸
bufferSize = 1024
# 服务端Socket的端口号
port = 9876
addr = (host,port)
# 创建socket对象,AF_INET表示IPv4,AF_INET6表示IPv6,SOCK_STREAM表示TCP
tcpServerSocket = socket(AF_INET,SOCK_STREAM)
# 绑定端口号
tcpServerSocket.bind(addr)
# 监听端口号
tcpServerSocket.listen()
print("Server port:9876")
print("正在等待客户端链接")
# 等待客户端的连接,这里程序会阻塞,直到接收到客户端的连接请求才会往下执行
# 接收到客户端的请求后,同时返回了客户端socket和客户端的端口号
tcpClientSocket,addr = tcpServerSocket.accept()
# 读取客户端发送过来的数据每次不超过buffersize字节的数据
data = tcpClientSocket.recv(bufferSize)
# recv方法返回了字节形式的数据,如果要使用字符串,需要将其进行解码,这里使用utf-8格式解码
print(data.decode('utf-8'))
# 向客户端以utf-8格式发送数据
tcpClientSocket.send("你好,I Love you.\n".encode("utf-8"))
# 关闭客户端
tcpClientSocket.close()
# 关闭服务端
tcpServerSocket.close()

测试服务端Socket的方法很多,可以通过客户端Socket测试。可以用telnet测试。

  • telnet localhost 9876

可以测试上述程序。

1.2、服务端接收数据的缓冲区

如果客户端传给服务端的数据过多,则需要多次读取,每次最多读取缓冲区尺寸的数据,也就是上例中bufferSize变量的值。如果要分多次读取,则根据当前读取的字节数是否小于缓冲区的尺寸来判断是否后面还有其他未读的数据,如果没有,则终止循环。

# 服务端接收数据的缓冲区
from socket import *
host = ''
port = 9876
addr = (host,port)
# 将缓冲区设为2
bufferSize = 2
tcpServerSocket = socket(AF_INET,SOCK_STREAM)
tcpServerSocket.bind(addr)
tcpServerSocket.listen()
print("Server:9876")
print("正在等待客户端连接")
tcpClientSocket,addr = tcpServerSocket.accept()
print("客户端已连接","addr=",addr)
# 初始化一个Bytes类型的变量,用于保存完整的客户端数据
fullDataBytes = b''
while True:
    # 每次最多读取2字节的数据
    data = tcpClientSocket.recv(bufferSize)
    # 将读取的字节书添加到fullDataBytes变量的后面
    fullDataBytes += data
    # 如果读取的字节数小于BufferSize,则终止循环
    if len(data) < bufferSize:
        break
# 按原始字节格式输出客户端发送过来的数据
print(fullDataBytes)
# 将完整的字节格式数据用ISO-8859-1格式解码,然后输出
print(fullDataBytes.decode('ISO-8859-1'))
tcpClientSocket.close()
tcpServerSocket.close()

1.3、服务端的请求队列

通常服务端不会只为一个客户端服务,当accept方法接收到一个客户端请求后,除非再次调用accept方法,否则将不会再等待下一个客户端的请求。一般将accept方法的代码放在一个循环中,但就算accept方法很快被调用,也是有时间间隔的(如两次调用accept方法的时间间隔是100ms).

在服务端Socket中有一个请求队列。如果服务端暂时无法处理客户端请求,会将客户端请求放到这个队列中,而每次调用accept方法,会从这个队列中取一个客户端请求处理。不过这个请求队列也不能无限制的存储客户端请求,请求队列的上限和操作系统有关。请求队列的存储上限也可以进行设置,只要通过listen方法监听端口号时指定请求队列上限即可。

# 服务端的请求队列
from socket import *
host = ''
bufferSize = 1024
port = 9876
addr = (host,port)
tcpServerSocket = socket(AF_INET,SOCK_STREAM)
tcpServerSocket.bind(addr)
# 设置服务端Socket请求队列的backlog(请求队列存贮上限)值为2
tcpServerSocket.listen(2)
print('Server:9876')
print("正在等待客户端连接")
while True:
    tcpCLientSocket,addr = tcpServerSocket.accept()
    print("客户端已经连接","addr","=",addr)
    data = tcpCLientSocket.recv(bufferSize)
    print(data.decode("utf-8"))
    tcpCLientSocket.send("你好,I love you.\n".encode("utf-8"))
    tcpCLientSocket.close()
tcpServerSocket.close()





1.4、TCP时间戳服务端

# TCP时间戳服务端
from socket import *
from time import ctime
host = ''
port = 9876
addr = (host,port)
bufferSize = 1024
tcpServerSocket = socket(AF_INET,SOCK_STREAM)
tcpServerSocket.bind(addr)
tcpServerSocket.listen(5)
while True:
    print("正在等待客户端连接")
    tcpClientSocket,addr = tcpServerSocket.accept()
    print("客户端已经连接",'addr',addr)
    while True:
        # 接收客户端发送过来的数据
        data = tcpClientSocket.recv(bufferSize)
        if not data:
            break
        print("接收的数据为:",data.decode('utf-8'))
        tcpClientSocket.send(ctime().encode('utf-8')+b' '+data)
    tcpClientSocket.close()
    print("客户端addr:",addr,"已经断开连接")
tcpServerSocket.close()

1.5、客户端Socket

客户端Socket连接服务端时,必须指定服务器的IP或命名。当然端口号也是必须的。http的默认端口号是80,https的默认端口号是443。

客户端Socket成功连接服务端后,可以使用send方法向服务端发送数据,也可以使用recv方法接收从服务端返回的数据。

# 客户端Socket
from socket import *
# 服务器的名称(可以使IP或域名)
host = 'localhost'
# 服务器的端口号
port = 9876
# 客户端Socket接收数据的缓冲区
bufferSize = 1024

addr = (host,port)
tcpClientSocket = socket(AF_INET,SOCK_STREAM)
# 开始连接时间戳服务端
tcpClientSocket.connect(addr)
while True:
    # 从终端用户采集信息
    data = input('>>')
    if not data:
        break
    # 将用户输入的字符串按utf-8格式编码成字符序列
    data = data.encode('utf-8')
    # 向服务器发送字节形式的数据
    tcpClientSocket.send(data)
    # 从服务端接收数据
    data = tcpClientSocket.recv(bufferSize)
    # 输出从服务端接收到的数据
    print(data.decode('utf-8'))
# 关闭客户端
tcpClientSocket.close()

2.1、UDP时间戳服务端

# UDP时间戳服务端
from socket import *
from time import ctime
host = ''
port = 9876
bufferSize = 1024
addr = (host,port)
# SOCK_DGRAM表示UDP
udpServerSocket = socket(AF_INET,SOCK_DGRAM)
udpServerSocket.bind(addr)
while True:
    print("正在等待消息")
    # 接收从客户端发过来的数据
    data,addr = udpServerSocket.recvfrom(bufferSize)
    # 向客户端发送服务端时间和客户端发送过来的字符串
    udpServerSocket.sendto(ctime().encode('utf-8') + b' ' + data,addr)
    print('客户端地址:',addr)
udpServerSocket.close()

2.2、UDP时间戳客户端

# UDP时间戳客户端
from socket import *
host = 'localhost'
port = 9876
bufferSize = 1024
addr = (host,port)
udpCLientSocket = socket(AF_INET,SOCK_DGRAM)
while True:
    # 从终端采集向发服务端发送的数据
    data = input(">>")
    if not data:
        break
    # 向服务端发送数据
    udpCLientSocket.sendto(data.encode('utf-8'),addr)
    # 接收服务端返回的数据
    data,addr = udpCLientSocket.recvfrom(bufferSize)
    if not data:
        break
    print(data.decode('utf-8'))

udpCLientSocket.close()

二、socketserver模块

socketserver模块是标准块中的一个高级模块,该模块的目的是让Socket编程更简单。

2.1、实现Socketserver TCP时间戳服务端

socketserver模块中提供了一个TCPserver类,用于实现TCP服务端。TCPServer类的构造方法有两个参数,第一个参数需要传入host和port(元组形式);第二个参数需要传入一个回调类,该类必须是StreamRequestHandler类的子类。在StreamRequestHandle类中需要实现一个handle方法,如果接收到客户端的响应,那么系统就会调用handle方法进行处理。

from socketserver import (TCPServer as TCP,StreamRequestHandler as SRH)
from time import ctime
host = ''
port = 9876
bufferSize = 1024
addr = (host,port)
# 定义回调类,该类必须从StreamRequestHandler继承
class MyRequestHandler(SRH):
    def handle(self):
        # 获取并输出客户端IP和端口号
        print("客户端已经连接,地址:",self.client_address)
        # 向客户端发送服务端的时间,以及按原样返回客户端发过来的字符串
        self.wfile.write(ctime().encode('utf-8') + b" " + self.rfile.readline())
# 创建TCPserver类实例
tcpServer = TCP(addr,MyRequestHandler)
print('正在等待客户端的连接')
# 调用Server——forever方法让服务端等待客户端的连接
tcpServer.serve_forever()

2.2、实现TCP server时间戳客户端

socketserverTCP客户端和前面的TCP Socket客户端没什么区别,只是在向SocketServerTCP时间戳服务端发送文本数据时要加一个行结束符。

# socketserverTCP时间戳客户端
from socket import *
# 服务器的名称(可以使IP或域名)
host = 'localhost'
# 服务器的端口号
port = 9876
# 客户端Socket接收数据的缓冲区
bufferSize = 1024

addr = (host,port)

while True:
    tcpClientSocket = socket(AF_INET, SOCK_STREAM)
    # 开始连接时间戳服务端
    tcpClientSocket.connect(addr)
    # 从终端用户采集信息
    data = input('>>')
    if not data:
        break
    # 向服务器发送字节形式的数据
    tcpClientSocket.send(('%s\r\n'%data).encode('utf-8'))
    # 从服务端接收数据
    data = tcpClientSocket.recv(bufferSize)
    # 输出从服务端接收到的数据
    print(data.decode('utf-8'))
# 关闭客户端
tcpClientSocket.close()

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值