前言

UDP为无连接不可靠的数据报协议,不同于TCP提供的面向连接的可靠字节流。

客户端不需要与服务器事先建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定服务器的地址作为参数。

服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。recvfrom将与所接收的数据报一道返回客户的协议地址,因此服务器可以把相应发送给正确的客户。

系统调用

发送

#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

参数

  • sockfd:描述符
  • buf:指向待发送的缓冲区的指针
  • len:待发送缓冲区数据的长度
  • flags:发送数据的标志位,目前设置为0
  • dest_addr:服务器的套接字地址结构(IP地址和端口号)
  • addrlen:上一个参数dest_addr的大小

返回值

发送出去的数据的大小。

接收

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                      struct sockaddr *src_addr, socklen_t *addrlen);

参数

  • sockfd:描述符
  • buf:指向待接收数据的缓冲区指针
  • len:最大接收的数据的长度
  • flags:标志位,目前设置为0
  • src_addr:传出参数,指定了客户端的套接字地址结构;如果该参数为空指针,代表调用者对客户端的地址不感兴趣,那么对应的addrlen参数也应该为空指针
  • addrlen:传出参数,上一个参数src_addr的大小

返回值

接收的数据的大小。

代码示例

服务端

#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <limits.h>

#define MAXLINE 4096

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in servaddr, clientaddr;
    int listen_port;

    if(argc != 2)
    {
        printf("Usage:./a.out <ListenPort>\n");
        exit(EXIT_FAILURE);
    }
    listen_port = atoi(argv[1]);


    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(listen_port);

    if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("bind error");
        exit(EXIT_FAILURE);

    }

    // 回射服务器处理流程
    int n, len;
    char msg[MAXLINE];      // 接收客户端的消息
    for(;;)
    {
        len = sizeof(clientaddr);
        n = recvfrom(sockfd, msg, MAXLINE, 0, (struct sockaddr*)&clientaddr, &len);
        sendto(sockfd, msg, n, 0, (struct sockaddr*)&clientaddr, len);
    }
}

客户端

#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <limits.h>

#define MAXLINE 4096

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in servaddr, clientaddr;
    int server_port;

    if(argc != 3)
    {
        printf("Usage:./a.out <ServerAddr> <ServerPort>\n");
        exit(EXIT_FAILURE);
    }

    server_port = atoi(argv[2]);


    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }


    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    servaddr.sin_port = htons(server_port);

    // connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
  
    // 回射服务器处理流程
    int n, len;
    char sendline[MAXLINE], recvline[MAXLINE];
    char buf[MAXLINE];
    while(fgets(sendline, sizeof(sendline), stdin))
    {
        sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));

        n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&clientaddr, &len);
        recvline[n] = 0;
        fputs(recvline, stdout);
    }
}

注意点

  • 使用UDP写一个长度为0的数据报是可行的,这会形成一个只包含IP首部(对于IPv4通常为20字节,对于IPv6通常为40字节)和一个8字节的UDP首部而没有数据的IP数据包。这也意味着对于UDP,recvfrom返回0值是可接收的:它不像TCP套接字上read返回0值代表对端已关闭,既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类的事情。
  • 一般来说,大多数TCP服务器是并发的,大多数UDP服务器是迭代的。
  • 由已连接UDP套接字(调用connect函数)引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接收任何异步错误。
  • 已连接UDP套接字(调用connect函数),和TCP不同,没有三路握手过程,内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号(取自传递给connect的套接字地址结构),然后立即返回到调用进程。
  • 对于已连接的UDP套接字,不能使用sendto,而改用write或send。
  • 对于已连接的UDP套接字,不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。
  • 当应用进程知道要给同一目的地址发送多个数据报时,显式连接套接字效率更高。临时未连接的UDP套接字大约会耗费每个UDP传输三分之一的开销。
  • UDP缺乏流量控制。

标签: none

添加新评论