【Linux网络编程】UDP服务器

------------->【Linux系统编程/网络编程】(学习目录汇总) <--------------

1. 相关概念

​ 传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可或缺的重要通信手段。

​ 相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。

​ 那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。

​ 与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:

  1. 服务器应用层设计流量控制,控制发送数据速度。

  2. 借助setsockopt函数改变接收缓冲区大小。如:

    #include <sys/socket.h>
    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    
    //eg:
    int n = 220x1024
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
    

2. C/S模型-UDP

在这里插入图片描述

​ 由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。

3. UDP通信server和client流程

server端

  1. 创建通信的套接字

    // 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    
  2. 使用通信的套接字和本地的 IP 和端口绑定,IP 和端口需要转换为大端

    bind();
    
  3. 设置监听(可选)

    listen();
    
  4. 与客户端进行通信

    // 接收数据
    recvfrom();
    // 发送数据
    sendto();
    
  5. 关闭套接字

    close(fd);
    

client端

  1. 创建通信的套接字

    // 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    
  2. 与客户端进行通信

    // 发送数据
    sendto();
    // 接收数据
    recvfrom();
    
  3. 关闭套接字

    close(fd);
    

4.相关通信函数

4.1 socket()函数

使用套接字通信函数需要包含头文件 <arpa/inet.h>,包含了这个头文件 <sys/socket.h> 就不用在包含了。

基于 UDP 进行套接字通信,创建套接字的函数还是socket()但是第二个参数的值需要指定为 SOCK_DGRAM,通过该参数指定要创建一个基于报式传输协议的套接字,最后一个参数指定为 0 表示使用报式协议中的 UDP 协议。

// 创建一个套接字
int socket(int domain, int type, int protocol);
  • 参数:
    • domain: 使用的地址族协议
      • AF_INET: 使用 IPv4 格式的 ip 地址
      • AF_INET6: 使用 IPv4 格式的 ip 地址
    • type:
      • SOCK_STREAM: 使用流式的传输协议(通常对于TCP协议)
      • SOCK_DGRAM: 使用报式 (报文) 的传输协议(通常对于UDP协议)
    • protocol: 一般写 0 即可,使用默认的协议
      • SOCK_STREAM: 流式传输默认使用的是 tcp
      • SOCK_DGRAM: 报式传输默认使用的 udp
  • 返回值:
    • 成功:可用于套接字通信的文件描述符
    • 失败: -1,设置errno

4.2 recvfrom()函数

// 接收数据, 如果没有数据,该函数阻塞
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • 参数
    • sockfd: 基于 udp的通信的文件描述符
    • buf: 指针指向的地址用来存储接收的数据
    • len:buf指针指向的内存的容量,最多能存储多少字节
    • flags: 设置套接字属性,一般使用默认属性,指定为 0 即可
    • src_addr: 发送数据的一端的地址信息,IP 和端口都存储在这里边,是大端存储的
      • 如果这个参数中的信息对当前业务处理没有用处,可以指定为 NULL, 不保存这些信息
    • addrlen: 类似于 accept () 函数的最后一个参数,是一个传入传出参数
      • 传入的是 src_addr 参数指向的内存的大小,传出的也是这块内存的大小
      • 如果 src_addr 参数指定为 NULL, 这个参数也指定为 NULL 即可
  • 返回值:成功返回接收的字节数
    • 若失败返回 - 1,设置errorno
    • 若返回0,表示对端关闭

4.3 sendto()函数

// 发送数据函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • 参数:
    • sockfd: 基于 udp 的通信的文件描述符
    • buf: 这个指针指向的内存中存储了要发送的数据
    • len: 要发送的数据的实际长度
    • flags: 设置套接字属性,一般使用默认属性,指定为 0 即可
    • dest_addr: 接收数据的一端对应的地址信息,大端的 IP 和端口
    • addrlen: 参数 dest_addr 指向的内存大小
  • 返回值:函数调用成功返回实际发送的字节数,调用失败返回 - 1

5.代码实现

server:

/*************************************************************************
#	> File Name:server.c
#	> Author: Jay
#	> Mail: billysturate@gmail.com
#	> Created Time: Mon 31 Oct 2022 09:14:30 PM CST
 ************************************************************************/

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int sockfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

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

	bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	printf("Accepting connections ...\n");

	while (1) {
		cliaddr_len = sizeof(cliaddr);
		n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len);
		if (n == -1)
			perror("recvfrom error");
		printf("received from %s at PORT %d\n", 
				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
				ntohs(cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);

		n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
		if (n == -1)
			perror("sendto error");
	}
	close(sockfd);
	return 0;
}

client

/*************************************************************************
#	> File Name:client.c
#	> Author: Jay
#	> Mail: billysturate@gmail.com
#	> Created Time: Tue 08 Nov 2022 03:10:51 PM CST
 ************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 9527
int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("create failed");
        exit(1);
    }

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "124.221.165.184", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	int i = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (i < 0)
    {
        perror("connect failed");
        exit(1);
    }
    int num = 0;
    printf("服务器连接成功\n");
    while (1)
    {
        sprintf(buf, "hello, world, %d\n...", num++);
        printf("%s\n", buf);
        write(sockfd, buf, strlen(buf) + 1);
        recv(sockfd, buf, sizeof(buf), 0);
        printf("recv msg:%s\n", buf);
        usleep(10000);
    }
    recv(sockfd, buf, sizeof(buf), 0);
    printf("recv msg:%s\n", buf);	
    printf("over-----------\n");
	close(sockfd);
	return 0;
}

编译运行,结果如下:

服务器端:

在这里插入图片描述

客户端1:

在这里插入图片描述

客户端2

在这里插入图片描述

可以看出,使用UDP实现的的服务器,在没有使用IO多路复用等技术时,依然具备并发能力

6.TCP通信和UDP通信的优缺点比较

TCP: 面向连接的,可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式。 丢包重传。

  • 优点:

    • 稳定

    • 数据流量稳定、速度稳定、顺序稳定

  • 缺点:

    • 传输速度慢。传输效率低。开销大。
  • 使用场景:数据的完整型要求较高,不追求效率。

UDP: 无连接的,不可靠的数据报传递。对于不稳定的网络层,采取完全不弥补的通信方式。 默认还原网络状况

  • 优点:

    • 传输速度块。传输效率高。开销小。
  • 缺点:

    • 不稳定。
    • 数据流量不稳定。速度不稳当。顺序不稳定,可能会出现后发送的包比先发送的包先到达的情况。
  • 使用场景:对时效性要求较高场合。稳定性其次。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BillySturate

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值