Linux系统编程_UDP

本文深入解析UDP协议特性,对比TCP协议,探讨其在实时通信场景的优势与不足,包括广播、组播功能的应用,并提供C/S模型下UDP编程实例。
  • UDP协议
  • C\S模型
  • 广播
  • 组播
  • domain本地套接字

UDP协议

  • TCP vs.UDP:
  1. TCP:面向连接的可靠数据传递(完全弥补)
    1)优点:
    稳定:
    数据稳定——丢包回传机制(丢包率千分之97);
    传输速率稳定——路由固定;
    流量稳定——滑动窗口
    2)缺点:
    效率低——三次握手、回传机制;
    传输速度慢
    3)适用场景:大文件、重要文件传输
  2. UDP:无连接的不可靠报文传递(完全不弥补)
    1)优点:效率高、速度快
    2)缺点:
    不稳定:
    数据——无丢包重传机制;
    速率——无固定路由;
    流量——无滑动窗口机制
    3)适用场景:对实时性要求较高,如视频会议、视频电话、广播
    例如,腾讯:TCP+UDP——UDP+应用层自定义协议弥补UDP的丢包
  • 与TCP类似,UDP也有可能出现缓冲区被填满后再接收数据时丢包的现象。由于它没有滑动窗口机制,通常采用如下两种方式解决:
  1. 服务器应用层设计流量控制,控制发送数据的速度。
  2. 借助setsockopt函数改变接收缓冲区大小:
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int n = 220x1024 //建议值
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

C\S模型-UDP

  • 在UDP协议中,没有客户端和服务器端三次握手建立连接的过程,因此也就没有listen、accept、connect等函数的调用,取而代之的是recvfrom、sendto等函数。并且在socket创建时不再指定为流式协议(SOCK_STREAM),而是报式协议(SOCK_DGRAM)。
  • C\S模型:
    在这里插入图片描述
  • 示例程序如下:
//server.c
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <strings.h>
#include <sys/socket.h>

#define SERV_IP "127.0.0.1"
#define SERV_PORT 7777

int main() {
        int fd;
        int i, n;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        char buf[BUFSIZ], str[INET_ADDRSTRLEN];

        fd = socket(AF_INET, SOCK_DGRAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr);
        serv_addr.sin_port = htons(SERV_PORT);
        bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        printf("Accepting connections...\n");
        while (1) {
                clie_addr_len = sizeof(clie_addr);
                n = recvfrom(fd, buf, BUFSIZ, 0, (struct sockaddr *)&clie_addr, &clie_addr_len); //&clie_addr can't be NULL
                if (n == -1)
                        perror("recvfrom error");

                printf("received from %s at port %d\n",
                                inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                                ntohs(clie_addr.sin_port));

                for (i = 0; i < n; i++)
                        buf[i] = toupper(buf[i]);

                n = sendto(fd, buf, n, 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr)); //&clie_addr can't be NULL
                if (n == -1)
                        perror("sendto error");
        }
        close(fd);

        return 0;
}

//client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERV_PORT 7777
#define SERV_IP "127.0.0.1"

int main() {
        int fd; 
        int n;
        struct sockaddr_in serv_addr;
        char buf[BUFSIZ];

        fd = socket(AF_INET, SOCK_DGRAM, 0); 
        
        //implicit binding of client socket fd with its IP address and port

		//serv_addr: address of server to be connected
        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr);

        while (fgets(buf, BUFSIZ, stdin) != NULL) {
                n = sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
                if (n == -1)
                        perror("sendto error");

                n = recvfrom(fd, buf, BUFSIZ, 0, NULL, 0); //NULL: serv_addr has been specified in sendto
                if (n == -1)
                        perror("recvfrom error");

                write(STDOUT_FILENO, buf, n);
        }
        close(fd);

        return 0;
}

广播

  • 广播IP:主机标识段host ID 为全1 的IP 地址为广播地址,如192.168.42.255 (另,网关IP:192.168.42.1)。
  • 广播过程中,接受者的端口号很重要,它要跟发送者的端口号一一对应。而服务器端的端口号可随机指定,因为广播是单向通信,服务器不会再收到来自客户端的数据。
  • 又会用到setsockopt函数来修改socket的属性,获取广播权限:
int flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));
  • 区分IP地址127.0.0.1INADDR_ANY
    前者是本地回环地址,即由本机的网卡发送端发送的数据传递到本机的网卡接收端,通常用于本机自身的进程间通信;后者也即0.0.0.0,是系统自动指定的任意一个主机有效IP地址。当主机要跟另一台电脑(的进程)通信时,就不能指定前者作为IP地址了。
  • 利用UDP实现广播的示例程序如下:
//server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERV_PORT 8000

#define CLIE_PORT 9000
#define BROADCAST_IP "192.168.17.255"

#define MAXLINE 1500

int main() {
        int sockfd;
        struct sockaddr_in serv_addr, clie_addr;
        char buf[MAXLINE];

        sockfd = socket(AF_INET, SOCK_DGRAM, 0); 
        
        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY = 0.0.0.0 (can't be 127.0.0.1 here)
        serv_addr.sin_port = htons(SERV_PORT);
        bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        int flag = 1;
        setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));

        //specify the ip and port of clients to receive the broadcast message
        bzero(&clie_addr, sizeof(clie_addr));
        clie_addr.sin_family = AF_INET;
        inet_pton(AF_INET, BROADCAST_IP, &clie_addr.sin_addr.s_addr);
        clie_addr.sin_port = htons(CLIE_PORT);

        int i = 0;
        while (1) {
                sprintf(buf, "Drink %d glassed of water\n", i++);
                //fgets(buf, sizeof(buf), stdin);
                sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
                sleep(1);
        }
        close(sockfd);

        return 0;
}

//client.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define CLIE_PORT 9000 //same as specified in server.c

#define MAXLINE 4096

int main() {
        struct sockaddr_in local_addr;
        int cfd;
        ssize_t len;
        char buf[MAXLINE];

        cfd = socket(AF_INET, SOCK_DGRAM, 0); 

        bzero(&local_addr, sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        local_addr.sin_port = htons(CLIE_PORT);
        int ret = bind(cfd, (struct sockaddr *)&local_addr, sizeof(local_addr));
        if (ret == 0)
                printf("bind ok!\n");

        while (1) {
                len = recvfrom(cfd, buf, sizeof(buf), 0, NULL, 0);
                write(STDOUT_FILENO, buf, len);
        }
        close(cfd);

        return 0;
}

组播

  • 广播是一个服务器端对与之相连的交换机的所有其他客户端传递同样的信息,但实际上可能与这个交换机相连的某些客户端并不是目的接收者,因此出现了组播,即对某一小组内的成员广播,组外的成员则不会接收到。注意,只有客户端加入了组播组,才会收到服务器组播的信息。
  • 组播IP:
224.0.0.0 ~ 224.0.0.255 //预留组播地址
224.0.1.0 ~ 224.0.1.255 //公用组播地址,可用于Internet;使用需申请
224.0.2.0 ~ 238.255.255.255 //用户可用的组播地址(临时组地址,程序退出即失效),全网范围内有效
239.0.0.0 ~ 239.255.255.255 //本地管理组播地址,仅在特定本地范围内有效(局域网)
  • 查看当前网卡序号:
    1)用命令的方式:ip ad //ip address的缩写
    2)用函数:if_nametoindex
  • 又会用到setsockopt函数来修改socket的属性,获取组播权限。
  • 总结:setsockopt函数的常用功能有:
    1)端口复用
    2)设置缓冲区大小
    3)开放广播权限
    4)开放组播权限
    5)加入组播组
  • 组播示例程序如下:
//server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERV_PORT 8000

#define MAXLINE 1500

#define CLIE_PORT 9000
#define GROUP "239.0.0.2"

/* system-defined struct about multicast information in /usr/include
struct ip_mreqn {
        struct in_addr  imr_multiaddr;          // IP multicast address of group
        struct in_addr  imr_address;            // local IP address of interface 
        int             imr_ifindex;            // Interface index 
};
*/

int main() {
        int sockfd;
        struct sockaddr_in serv_addr, clie_addr;
        char buf[MAXLINE] = "hey you!\n";
        struct ip_mreqn group;

        sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(SERV_PORT);
        bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        //specify the info of group receiving broadcast
        inet_pton(AF_INET, GROUP, &group.imr_multiaddr);   //group IP
        inet_pton(AF_INET, "0.0.0.0", &group.imr_address); //group.imr_address = htonl(INADDR_ANY);
        group.imr_ifindex = if_nametoindex("eth0");        //eth0 -> index cmd: ip ad   

        setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group)); //get multicast permission

        //specify the info of clients to receive multicast message
        bzero(&clie_addr, sizeof(clie_addr));
        clie_addr.sin_family = AF_INET;
        inet_pton(AF_INET, GROUP, &clie_addr.sin_addr.s_addr);
        clie_addr.sin_port = htons(CLIE_PORT);

        int i = 0;
        while (1) {
                sprintf(buf, "hey you! %d\n", i++);
                //fgets(buf, sizeof(buf), stdin);
                sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clie_addr, sizeof(clie_addr));
                sleep(1);
        }
        close(sockfd);

        return 0;
}

//client.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERV_PORT 8000
#define CLIE_PORT 9000

#define GROUP "239.0.0.2"

int main() {
        struct sockaddr_in local_addr;
        int cfd;
        ssize_t len;
        char buf[BUFSIZ];

        struct ip_mreqn group; //multicast group

        cfd = socket(AF_INET, SOCK_DGRAM, 0);

        bzero(&local_addr, sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        local_addr.sin_port = htons(CLIE_PORT);
        bind(cfd, (struct sockaddr *)&local_addr, sizeof(local_addr));

        //specify the multicast group this client is to join (same as in server.c)
        inet_pton(AF_INET, GROUP, &group.imr_multiaddr);
        inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
        group.imr_ifindex = if_nametoindex("eth0");

        setsockopt(cfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

        while (1) {
                len = recvfrom(cfd, buf, sizeof(buf), 0, NULL, 0);
                write(STDOUT_FILENO, buf, len);
        }

        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值