UDP 套接字
概述
在使用 TCP 编程和使用 UDP 编程之间存在一些本质差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议,不同于 TCP 提供的面向连接的可靠字节流。从资源的角度来看,相对来说UDP套接字开销较小,因为不需要维持网络连接,而且因为无需花费时间来连接,所以UDP套接字的速度也较快。
因为UDP提供的是不可靠服务,所以数据可能会丢失。如果数据对于我们来说非常重要,就需要小心编写UDP客户程序,以检查错误并在必要时重传。实际上,UDP套接字在局域网中是非常可靠的。
图:UDP 客户 / 服务器程序使用的套接字函数
上图展示了客户与服务器使用UDP套接字进行通信的过程。在UDP套接字程序中,客户不需要与服务器建立连接,而只管直接使用sendto函数给服务器发送数据报。同样的,服务器不需要接受来自客户的连接,而只管调用recvfrom函数,等待来自某个客户的数据到达。
套接字成为了应用程序进行通信的一种抽象机制。每一个进程都有一个或者多个套接字。当生成一个套接字的时候,就会为它分配一个端口号。我们是在C/S架构上应用UDP套接字编程。那么,服务器总是在等待客户端的请求。客户端在请求的时候,它会告知目的地址(服务器的IP地址和目的进程的端口号)。
创建套接字
可以使用系统调用socket来创建一个套接字并返回该套接字的文件描述符。
from socket import * # 包含socket库
s = socket(AF_INET,SOCK_DGRAM)
创建的套接字是一条通信线路的一个端点。
family
family参数指定哪种协议族,常见的协议族包括 AF_UNIX 和 AF_INET。AF_UNIX 用于通过文件系统实现的本地套接字,AF_INET 用于网络套接字。
type
type参数指定这个套接字的通信类型,取值包括 SOCK_STREAM 和 SOCK_DGRAM。
SOCK_STREAM 即流套接字,基于 TCP,提供可靠,有序的服务。
SOCK_DGRAM 即数据报套接字,基于 UDP,提供不可靠,无序的服务。
protocol
protocol允许为套接字指定一种协议。对于 AF_UNIX 和 AF_INET,我们使用默认值即可。
以下代码创建一个 UDP socket,family使用 AF_INET,type 使用 SOCK_DGRAM,protocol 协议使用默认的 0 值。
命名套接字
要想让创建的套接字可以被其他进程使用,那必须给该套接字命名。对套接字命名的意思是指将该套接字关联一个IP地址和端口号,可以使用系统调用bind来实现。
server_port = 8000 # 服务器端口
# 创建套接字,设置Ipv4地址以及指定UDP连接
server_socket = socket(AF_INET,SOCK_DGRAM)
# 绑定IP地址和端口号。监听该端口
server_socket.bind(('',server_port))
bind系统调用把参数address中的地址分配给与文件描述符socket关联的套接字。
对于客户套接字,我们一般不需要指定套接字的端口号,而对于服务器套接字,我们需要指定套接字的端口号以便让客户正确向服务器发送数据。如果不需要指定端口号,可以将server_port的值赋为0。
如果我们没特别为套接字绑定IP地址,可以让操作系统选择一个,即address使用地址0.0.0.0,使用INADDR_ANY来表示这个地址常量。
下面是服务器代码:
# python3 实现循环无连接服务器
from socket import * # 包含socket库
server_port = 8000 # 服务器端口
# 创建套接字,设置Ipv4地址以及指定UDP连接
server_socket = socket(AF_INET,SOCK_DGRAM)
# 绑定IP地址和端口号。监听该端口
server_socket.bind(('',server_port))
while True:
print("receive data:")
# 从客户端发来的包中获取数据存放在data中,将源地址放在client_address中。
# 设置缓存大小为4096。完成这些功能需要使用函数recvfrom.
data,client_address = server_socket.recvfrom(4096)
print(data.decode()) # 打印客户端的数据
server_socket.sendto(b"success!",client_address)
server_socket.close()
客户端代码:
# python3实现的客户端代码
from socket import * # 包含网络模块
#服务器地址以及端口号
#127.0.0.1是本地回环地址,经常用来进行测试,也可以使用域名localhost来代替该ip地址
server_ip = '127.0.0.1'
server_port = 8000
server_address = (server_ip, server_port)
#创建套接字 ipv4地址以及UDP协议
client_socket = socket(AF_INET,SOCK_DGRAM)
while True:
data = input("please input:")
client_socket.sendto(data.encode(),server_address)
recv,server_addr = client_socket.recvfrom(4096)
print(recv.decode())
client_socket.close()
总的来说,在使用UDP作为传输层协议的时候,客户端需要知道服务器的IP地址和目的端口号。由于UDP是面向无连接的协议,因此,客户端使用sendto函数来发送。创建套接字的时候注意UDP是SOCK_DGRAM。服务器也使用sendto函数来发送响应给客户端。recvfrom函数能够接受包,并知晓客户端的地址。
使用Python socket编程实现简单的聊天室功能
服务器和客户端使用UDP编程,客户端两个线程一个负责接收,一个负责发送。
服务器:接收消息并保存地址,如果触发‘EXIT’关键字则从地址表中移除该地址
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
addr = ('127.0.0.1', 9999)
s.bind(addr)
logging.info('UDP Server on %s:%s...', addr[0], addr[1])
user = {} # {addr:name}
while True:
try:
data, addr = s.recvfrom(1024)
if not addr in user:
for address in user:
s.sendto(data + ' 进入聊天室...'.encode(), address)
user[addr] = data.decode()
continue
if 'EXIT' in data.decode('utf-8'):
name = user[addr]
user.pop(addr)
for address in user:
s.sendto((name + ' 离开了聊天室...').encode(), address)
else:
print('"%s" from %s:%s' %
(data.decode('utf-8'), addr[0], addr[1]))
for address in user:
if address != addr:
s.sendto(data, address)
except ConnectionResetError:
logging.warning('Someone left unexcept.')
客户端:两个线程,并设置接收线程为守护线程
def recv(sock, addr):
'''
一个UDP连接在接收消息前必须要让系统知道所占端口
也就是需要send一次,否则win下会报错
“ data=sock.recv(1024)
OSError: [WinError 10022] 提供了一个无效的参数。 ”
'''
sock.sendto(name.encode('utf-8'), addr)
while True:
data = sock.recv(1024)
print(data.decode('utf-8'))
def send(sock, addr):
while True:
string = input()
message = name + ' : ' + string
data = message.encode('utf-8')
sock.sendto(data, addr)
if string == 'EXIT':
break
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server = ('127.0.0.1', 9999)
tr = threading.Thread(target=recv, args=(s, server), daemon=True)
ts = threading.Thread(target=send, args=(s, server))
tr.start()
ts.start()
ts.join()
s.close()
if __name__ == '__main__':
print("-----欢迎来到聊天室,退出聊天室请输入'EXIT'-----")
name = input('请输入你的名称:')
print('-----------------%s------------------' % name)
main()
可以通过继承socket.socket类来重构,那样子显得跟规范一些
标签:UDP,addr,server,接字,data,socket From: https://blog.51cto.com/u_1439909/6321651