socket UDP

UDP套接字

概述

在使用 TCP 编程和使用 UDP 编程之间存在一些本质差异,其原因在于这两个传输层之间的差别:UDP是无连接不可靠的数据报协议不同于 TCP 提供的面向连接的可靠字节流。从资源的角度来看,相对来说UDP套接字开销较小,因为不需要维持网络连接,而且因为无需花费时间来连接,所以UDP套接字的速度也较快。

因为UDP提供的是不可靠服务,所以数据可能会丢失。如果数据对于我们来说非常重要,就需要小心编写UDP客户程序,以检查错误并在必要时重传。实际上,UDP套接字在局域网中是非常可靠的。


图:UDP 客户 / 服务器程序使用的套接字函数

上图展示了客户与服务器使用UDP套接字进行通信的过程。在UDP套接字程序中,客户不需要与服务器建立连接,而只管直接使用sendto函数给服务器发送数据报。同样的,服务器不需要接受来自客户的连接,而只管调用recvfrom函数,等待来自某个客户的数据到达。

套接字成为了应用程序进行通信的一种抽象机制。每一个进程都有一个或者多个套接字。当生成一个套接字的时候,就会为它分配一个端口号。我们是在C/S架构上应用UDP套接字编程。那么,服务器总是在等待客户端的请求。客户端在请求的时候,它会告知目的地址(服务器的IP地址和目的进程的端口号)。

创建套接字

可以使用系统调用socket来创建一个套接字并返回该套接字的文件描述符。

from socket import *    包含socket

= socket(AF_INET,SOCK_DGRAM)

创建的套接字是一条通信线路的一个端点。

family

family参数指定哪种协议族,常见的协议族包括AF_UNIXAF_INETAF_UNIX用于通过文件系统实现的本地套接字,AF_INET用于网络套接字。

type

type参数指定这个套接字的通信类型,取值包括SOCK_STREAMSOCK_DGRAM
SOCK_STREAM流套接字,基于TCP,提供可靠,有序的服务。
SOCK_DGRAM数据报套接字,基于UDP,提供不可靠,无序的服务。

protocol

protocol允许为套接字指定一种协议。对于AF_UNIXAF_INET,我们使用默认值即可。

以下代码创建一个UDP socketfamily使用AF_INETtype 使用SOCK_DGRAMprotocol 协议使用默认的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函数来发送。创建套接字的时候注意UDPSOCK_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类来重构,那样子显得跟规范一些

 


版权声明:本文为weixin_43955170原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。