TCP IP网络编程(六) 基于UDP的服务器端、客户端

news2024/11/24 1:18:09

文章目录

    • 一、理解UDP
      • 1.UDP套接字的特点
      • 2.UDP内部工作原理
      • 3.UDP的高效使用
    • 二、实现基于UDP的服务器端、客户端
      • 1.UDP中的服务端和客户端没有连接
      • 2.UDP服务器端和客户端均只需要一个套接字
      • 3.基于UDP的数据I/O函数
      • 4.基于UDP的回声服务器端、客户端
      • 5.UDP客户端套接字的地址分配
    • 三、UDP的数据传输特性和调用connect函数
      • 1.已连接UDP套接字与未连接UDP套接字
      • 2.创建已连接的UDP套接字

一、理解UDP

1.UDP套接字的特点

如果只考虑可靠性,TCP确实比UDP好,但是UDP在结构上比TCP更简洁,UDP不会发送类似ACK的应答消息,也不会像SEQ那样给数据包分配序号,因此UDP的性能有时比TCP高出许多,在编程中实现UDP也比TCP更加简单。

虽然UDP的可靠性比不上TCP,但是不会频繁的发生数据损毁,在更重视性能而非可靠性的情况下,UDP是一种不错的选择。

2.UDP内部工作原理

与TCP不同,UDP不会进行流量控制

在这里插入图片描述

IP的作用就是让离开主机B的数据包准确传递到主机A,但是把UDP包最终交给主机A的某一UDP套接字的过程就是由UDP完成的,UDP最重要的作用就是根据端口号传到主机的数据包交付给最终的UDP套接字。

3.UDP的高效使用

虽然大部分网络编程都基于TCP实现,但也有一些是基于UDP实现的,接下来考虑何时使用UDP更高效,网络传输特性导致信息丢失频发,可若要传递压缩文件,则必须使用TCP,因为压缩文件只要丢失一点就无法打开。但是通过网络实时传输视频的情况有所不同,对于媒体数据而言,丢失一部分不会产生太大问题,最多会出现短暂的视频模糊,但是需要提供实施服务,速度成为非常重要的因素。

TCP比UDP慢的原因通常有下面两点:

  • 收发数据前后进行的连接设置及清除过程
  • 收发数据过程中为保证可靠性而添加的流控制

如果收发的数据量小但需要频繁连接时,UDP比TCP更高效

二、实现基于UDP的服务器端、客户端

1.UDP中的服务端和客户端没有连接

UDP的服务器端、客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程,不必调用TCP连接过程中调用的listen函数和accept函数,UDP中只有创建套接字的过程和数据交换过程。

2.UDP服务器端和客户端均只需要一个套接字

TCP中套接字之间应该是一对一的关系,如果向10个客户端提供服务,则除了守门的服务器套接字外,还需要10个套接字,但在UDP中,不管是服务器端还是客户端都只需要1个套接字。

在这里插入图片描述

图中显示一个UDP套接字能和多台主机通信。

3.基于UDP的数据I/O函数

创建好TCP套接字后,传输数据无需再添加地址信息,因为TCP套接字将保持与对方套接字的连接。TCP套接字知道目标地址信息,但UDP套接字不会保持连接状态,因此每次传输数据都要添加目标地址信息,这相当于寄信件时填写收件地址。

填写地址并传输数据时调用的UDP相关函数

#include<sys/socket.h>

ssize_t sendto(int sock, void *buff, size_t nbytes, int flags,struct sockaddr *to,socklen_t addrlen);
	成功返回传输的字节数,失败时返回-1
    sock		用于传输数据的UDP套接字文件描述符
    buff		保存待传输数据的缓冲地址值
    nbytes		待传输的数据长度,以字节为单位
    flags		可选项参数,若没有则传递0
    to			存有目标地址信息的sockaddr结构体变量的地址值
    addrlen		传递给参数to的地址值结构体变量长度

接收UDP数据的函数

#include<sys/socket.h>

ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
	成功返回接收的字节数,失败返回-1
    sock		用于传输数据的UDP套接字文件描述符
    buff		保存待传输数据的缓冲地址值
    nbytes		待传输的数据长度,以字节为单位
    flags		可选项参数,若没有则传递0
    to			存有发送端地址信息的sockaddr结构体变量的地址值
    addrlen		传递给参数from的地址值结构体变量长度

4.基于UDP的回声服务器端、客户端

UDP不同于TCP,不存在连接请求和受理过程,因此在某种意义上无法明确区分客户端和服务器端,只是因为提供服务所以叫服务器端

uecho_server.c

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t clnt_adr_sz;
	
	struct sockaddr_in serv_adr, clnt_adr;
	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(serv_sock==-1)
		error_handling("UDP socket creation error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");

	while(1) 
	{
		clnt_adr_sz=sizeof(clnt_adr);
		str_len=recvfrom(serv_sock, message, BUF_SIZE, 0, 
								(struct sockaddr*)&clnt_adr, &clnt_adr_sz);
		sendto(serv_sock, message, str_len, 0, 
								(struct sockaddr*)&clnt_adr, clnt_adr_sz);
	}	
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

接下来介绍与上述服务器端协同工作的客户端,与TCP客户端不同,不存在connect函数调用

uecho_client.c

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	socklen_t adr_sz;
	
	struct sockaddr_in serv_adr, from_adr;
	if(argc!=3){
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_DGRAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	while(1)
	{
		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);     
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))	
			break;
		
		sendto(sock, message, strlen(message), 0, 
					(struct sockaddr*)&serv_adr, sizeof(serv_adr));
		adr_sz=sizeof(from_adr);
		str_len=recvfrom(sock, message, BUF_SIZE, 0, 
					(struct sockaddr*)&from_adr, &adr_sz);

		message[str_len]=0;
		printf("Message from server: %s", message);
	}	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

5.UDP客户端套接字的地址分配

UDP客户端缺少把IP和端口分配给套接字的过程。TCP客户端调用connect函数自动完成此过程,而UDP中连能承担相同功能的函数调用语句都没有。

UDP程序中,调用sento函数传输数据前应完成对套接字的地址分配工作,因此调用bind函数。bind函数不区分TCP或者UDP,也就是说,在UDP程序中同样可以调用。另外如果调用sendto函数时发现尚未分配地址信息,则在首次调用sendto函数时给相应套接字自动分配IP和端口。而且此时分配的地址一直保留到程序结束为止,因此也可用来与其他UDP套接字进行数据交换。IP用主机IP,端口号选尚未使用的任意端口号。

调用sendto函数时自动分配IP和端口号,UDP客户端中无需额外的地址分配过程。

三、UDP的数据传输特性和调用connect函数

TCP传输的数据不存在数据边界,表示“数据传输过程中调用I/O函数的次数不具有任何意义”。

UDP数据传输中存在数据边界,传输中调用I/O函数的次数非常重要。因此,输入函数的调用次数应和输出函数的调用次数完全一致,这样才能保证接收全部已发送数据。

bound_host1.c

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	struct sockaddr_in my_adr, your_adr;
	socklen_t adr_sz;
	int str_len, i;

	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_DGRAM, 0);
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&my_adr, 0, sizeof(my_adr));
	my_adr.sin_family=AF_INET;
	my_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	my_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(sock, (struct sockaddr*)&my_adr, sizeof(my_adr))==-1)
		error_handling("bind() error");
	
	for(i=0; i<3; i++)
	{
		sleep(5);	// delay 5 sec.
		adr_sz=sizeof(your_adr);
		str_len=recvfrom(sock, message, BUF_SIZE, 0, 
								(struct sockaddr*)&your_adr, &adr_sz);     
	
		printf("Message %d: %s \n", i+1, message);
	}
	close(sock);	
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

bound_host2.c

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

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char msg1[]="Hi!";
	char msg2[]="I'm another UDP host!";
	char msg3[]="Nice to meet you";

	struct sockaddr_in your_adr;
	socklen_t your_adr_sz;
	if(argc!=3){
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_DGRAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&your_adr, 0, sizeof(your_adr));
	your_adr.sin_family=AF_INET;
	your_adr.sin_addr.s_addr=inet_addr(argv[1]);
	your_adr.sin_port=htons(atoi(argv[2]));
	
	sendto(sock, msg1, sizeof(msg1), 0, 
					(struct sockaddr*)&your_adr, sizeof(your_adr));
	sendto(sock, msg2, sizeof(msg2), 0, 
					(struct sockaddr*)&your_adr, sizeof(your_adr));
	sendto(sock, msg3, sizeof(msg3), 0, 
					(struct sockaddr*)&your_adr, sizeof(your_adr));
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

UDP数据报: UDP套接字传输的数据包又称为数据报,实际上数据报也属于数据包的一种。只是与TCP包不同,其本身可以成为1个完整数据。这与UDP的数据传输特性有关,UDP中存在数据边界,1个数据包即可成为有关完整数据,因此称为数据报。

1.已连接UDP套接字与未连接UDP套接字

TCP套接字中需要注册待传输数据的目标IP和端口号,而UDP无需注册,因此通过sendto函数传输数据的过程分为三个阶段

  • 第一阶段:向UDP套接字注册目标IP和端口号
  • 第二阶段:传输数据
  • 第三阶段:删除UDP套接字中注册的目标地址信息

每次调用sendto函数时重复上述过程,每次都变更地址,因此可以重复利用同一UDP套接字向不同的目标传输,这种未注册目标地址信息的套接字成为未连接套接字,注册了目标地址信息地址的套接字成为连接connected套接字,UDP默认为未连接套接字

如果IP为192.168.233.20向端口100准备了三个数据,调用了3次sendto函数进行传输

此时需要重复3次上述阶段,因此需要与同一主机进行长时间通信,将UDP套接字变为已连接会提高效率

2.创建已连接的UDP套接字

针对UDP套接字调用connect函数并不意味着要与对方UDP套接字连接,这只是向UDP套接字注册目标IP和端口信息。

之后就可以跟TCP一样,每次调用sendto函数只需传递数据,因为已经指定了收发对象,所以不仅可以使用sendto函数、recvfrom函数,还可以使用write函数、read函数进行通信


这是《TCP/IP网络编程》专栏的第六篇文章,欢迎各位读者订阅!

更多资料点击 GitHub 欢迎各位读者去Star

⭐学术交流群Q 754410389 持续更新中~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1021989.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】java: 无效的标记: -parameters

问题描述 maven項目&#xff0c;原来使用jdk8的版本&#xff0c;现在改成jdk7的版本&#xff0c;结果报错&#xff1a; java: 无效的标记: -parameters 原因分析 解决方法 删掉下图所示的-parameters参数 参考文章 https://www.cnblogs.com/lovezzb/p/10072854.html

英伟达 nvidia 官方code llama在线使用

新一代编程语言模型Code Llama面世&#xff1a;重新定义编程的未来 随着人工智能和机器学习技术的迅速发展&#xff0c;我们现在迎来了一款革命性的大型编程语言模型——Code Llama。该模型是基于Llama 2研发的&#xff0c;为开放模型中的佼佼者&#xff0c;其性能达到了行业领…

零基础学前端(四)1. 重点讲解 CSS:盒子模型、样式选择器

1. 该篇适用于从零基础学习前端的小白 2. 初学者不懂代码得含义也要坚持模仿逐行敲代码&#xff0c;以身体感悟带动头脑去理解新知识 3. 初学者切忌&#xff0c;不要眼花缭乱&#xff0c;不要四处找其它文档&#xff0c;要坚定一个教授者的方式&#xff0c;将其学通透&#xff…

基于GBDT+Tkinter+穷举法按排队时间预测最优路径的智能导航推荐系统——机器学习算法应用(含Python工程源码)+数据集(二)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Pycharm 环境Scikit-learnt 模块实现1. 数据预处理2. 客流预测1&#xff09;创建并保存模型2&#xff09;损失函数3&#xff09;测试集测试4&#xff09;自定义特征并预测 3. 百度地图API调用1&#xff09;申请密…

许少辉博士后挂站联谊《乡村振兴战略下传统村落文化旅游设计》河北站——2023学生开学季辉少许

许少辉博士后挂站联谊《乡村振兴战略下传统村落文化旅游设计》河北站——2023学生开学季辉少许

【大规模 MIMO 检测】基于ADMM的大型MU-MIMO无穷大范数检测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Winform中DataGridView中实现一个单元格增加多个操作按钮

1.表格名称&#xff1a;dgv&#xff1b;操作列名称&#xff1a;ColOperate 2.相关代码如下&#xff1a; private void Load(object sender, EventArgs e){//初始化调用 前提是表格先填充好数据//循环遍历添加每行的控件for (int i 0;i < dgv.Rows.Count; i){MulAutoBtnEdi…

【最新!企知道AES加密分析】使用Python实现完整解密算法

文章目录 1. 写在前面2. 过debugger3. 抓包分析4. 断点分析5. Python实现解密算法 1. 写在前面 最近华为各方面传递出来的消息无不体现出华为科技实力与技术处于遥遥领先的地位。所以出于好奇想要了解一下咱们国内这些互联网科技企业有哪些技术专利&#xff0c;于是就有了这篇文…

怎么防止360安全卫士修改默认浏览器?

默认的浏览器 原先选项是360极速浏览器&#xff08;如果有安装的话&#xff09;&#xff0c;我这里改成了Chrome。 先解锁 才能修改。

相伴六年,Smartbi与荣耀共建数据化运营新生态

“从 2018 年到 2023 年&#xff0c;整整六年的时间里面&#xff0c;我们从最开始使用Smartbi功能里最朴素的真自助能力&#xff0c;到现在做了BI、AI融合&#xff0c;过去六年业务的发展离不开Smartbi的大力支持&#xff0c;Smartbi与荣耀自有平台能力打通融合&#xff0c;相辅…

SpringBoot2.0(mybatis-plus常见的增删改查和分页)

目录 一&#xff0c;mybatis-plus常见注解二&#xff0c;创建一个工具类和启动类三&#xff0c;创建实体类四&#xff0c;创建mapper接口五&#xff0c;创建service接口和impl类六&#xff0c;创建配置类七&#xff0c;创建controller八&#xff0c;使用测试工具测试增删改查和…

Linux编译安装dig9.18

Linux编译安装dig9.18 背景查看dig版本 编译安装dig安装依赖包下载dig压缩包解压编译安装创建软链接 结果验证 背景 Centos7的dig版本是9.11&#xff0c;体验9.18新版本的功能特性。 例如&#xff1a; 使用yaml格式输出。 # 使用yaml格式输出 dig yaml google.com查看dig版本…

3、ARM寄存器组织

ARM寄存器组织 1、寄存器 概念 寄存器是处理器内部的存储器&#xff0c;没有地址 作用 一般用于暂时存放参与运算的数据和运算的结果 分类 包括通用寄存器、专用寄存器、控制寄存器 2、ARM寄存器 注&#xff1a; 在某个特定的模式下&#xff0c;只能使用当前模式下的…

什么测试自动化测试?

什么测试自动化测试&#xff1f; 做测试好几年了&#xff0c;真正学习和实践自动化测试一年&#xff0c;自我感觉这一个年中收获许多。一直想动笔写一篇文章分享自动化测试实践中的一些经验。终于决定花点时间来做这件事儿。 首先理清自动化测试的概念&#xff0c;广义上来讲&a…

用Python判断是否为闰年并计算生肖年

1 问题 润平年以及生肖是新的一年到来我们应该了解的信息。那么如何利用python程序计算快速计算该年为什么年&#xff1f; 2 方法 利用if条件判断语句实现。 代码清单 1 year eval(input(请输入咨询的年份:))if (year % 4 0 and year %100 ! 0) or year % 400 0: print(…

递归视角下

def listSum(numbers): if not numbers: return 0 else: (f, rest) numbers return f listSum(rest)myList (1, (2, (3, (4,None))))total listSum(myList)print(total) while循环何时退出&#xff1f; 恐怕是while循环技巧所在&#xff0c;即选择恰…

4G模块驱动移植

一、4G模块概述 1、调试的模块型号是广和通的 NL668-EAU-00-M.2。 2、使用的接口是 M.2 Key-B。实际只用到了M2里的USB接口。 调试过程 以QMI_WWAN号方式进行说明&#xff0c;其他拨号方式也试过。最后以QMI_WWAN方式调通了&#xff0c;拨号成功了。 其他拨号方式因为现有文档…

上四休三,未来的期许

近日“少上一天班&#xff0c;究竟香不香”引发关注&#xff0c;英国媒体2月21日报道&#xff0c;一项全世界目前为止参加人数最多的“四天工作制”试验&#xff0c;不久前在英国取得了成功。很多人表示上过四天班之后&#xff0c;给多少钱也回不去五天班的时代了。 来百度APP畅…

【Vue入门】语法 —— 插值、指令、过滤器、计算属性、监听器

目录 一、模版语法 1.1 插值 1.1.1 文本 1.1.2 html解析 1.1.3 属性 1.1.4 表达式 1.2 指令 1.2.1 核心指令 1.2.3 动态参数 二、过滤器 2.1 局部过滤器 2.2 全局过滤器 三、计算属性 四、监听器 五、排座案例 小结&#xff1a;计算属性和监听属性的区别 一、模…

Linux ____02、Linux开关机、目录介绍、文件目录相关命令(常用命令)

Linux开关机、目录介绍、目录相关命令&#xff08;常用命令&#xff09; 一、Linux开关机二、目录介绍三、目录相关命令&#xff08;常用命令&#xff09;1、ls命令&#xff0c;列出目录内容2、cd命令&#xff0c;切换目录&#xff1b;pwd&#xff0c;查看当前工作路径3、mkdi…