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

文章目录

    • 一、理解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);成功返回传输的字节数,失败时返回-1sock		用于传输数据的UDP套接字文件描述符buff		保存待传输数据的缓冲地址值nbytes		待传输的数据长度,以字节为单位flags		可选项参数,若没有则传递0to			存有目标地址信息的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);成功返回接收的字节数,失败返回-1sock		用于传输数据的UDP套接字文件描述符buff		保存待传输数据的缓冲地址值nbytes		待传输的数据长度,以字节为单位flags		可选项参数,若没有则传递0to			存有发送端地址信息的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.mzph.cn/news/79838.shtml

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

相关文章

IDEA中创建Java Web项目1

一、File-> New -> Project... 1. 项目类型中选择 Java Enterprise 项目 2. Name&#xff1a;填写自己的项目名称 3. Project template&#xff1a;选择项目的模板&#xff0c;Web application。支持JSP和Servlet的项目 4. Application server&#xff1a;选择应用服务…

【C++11保姆级教程】列表初始化(Literal types)和委派构造函数(delegating))

文章目录 前言一、列表初始化 (List Initialization)1.1数组初始化1.2结构体初始化1.3容器初始化1.4列表初始化的优势 二、委派构造函数 (Delegating Constructors)2.1委派构造函数是什么&#xff1f;2.2委派构造函数示例代码2.3调用顺序2.3委派构造函数优势 总结 前言 C11引入…

MySQL基础运维知识点大全

一. MySQL基本知识 1. 目录的功能 通用 Unix/Linux 二进制包的 MySQL 安装下目录的相关功能 目录目录目录binMySQLd服务器&#xff0c;客户端和实用程序docs信息格式的 MySQL 手册manUnix 手册页include包括&#xff08;头&#xff09;文件lib图书馆share用于数据库安装的错…

数据结构-leetcode-数组形式的整数加法

解题图解&#xff1a; 下面是代码&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* addToArrayForm(int* num, int numSize, int k, int* returnSize){int k_tem k;int klen0;while(k_tem){//看看k有几位k_tem /10;klen;}i…

激活函数总结(三十五):激活函数补充(KAF、Siren)

激活函数总结&#xff08;三十五&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 KAF激活函数2.2 Siren激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、Softp…

Ubuntu 22.04.3 LTS安装

最近换电脑了&#xff0c;准备重新装一下ubuntu。多年前装过ubuntu很老的版本&#xff0c;现在发现官网最新的LTS版本是 Ubuntu 22.04.3 LTS 版本。那重新装的话&#xff0c;肯定装最新的版本了。这里我记录下自己的安装过程&#xff0c;作为以后的笔记查看。 我的环境&#x…

【Linux】生产者消费者模型

文章目录 一、生产者消费者模型1. 生产者消费者模型的概念2. 生产者消费者之间的关系3. 生产者和消费者的特点 二、基于BlockingQueue的生产者消费者模型1. 单生产单消费随机数任务计算器任务Task 2. 多生产多消费3. 为什么生产者消费者模型高效 三、基于环形队列的生产消费模型…

c++中关于Thread Affinity(线程亲和性)示例源码

win10下&#xff0c;可以在任务管理器里面设置某个进程的线程亲和性,如下图: 然后选择相关的cpu&#xff0c;如下图&#xff1a; 这么做可以使得相关的线程在某些密集型计算任务中只会运行在某些指定的cpu上&#xff0c;以便提高性能。 以下是windwos上c程序中应用Thread Affi…

腾讯mini项目-【指标监控服务重构】2023-08-20

今日已办 PPT制作 答辩流程 概述&#xff1a;对项目背景、架构进行介绍&#xff08;体现我们分组的区别和需求&#xff09;人员&#xff1a;小组成员进行简短的自我介绍和在项目中的定位&#xff0c;分工进展&#xff1a;对项目进展介绍&#xff0c;其中a、b两组的区别和工作…

STM32——SPI通信

文章目录 SPI&#xff08;Serial Peripheral Interface&#xff09;概述&#xff1a;SPI的硬件连接&#xff1a;SPI的特点和优势&#xff1a;SPI的常见应用&#xff1a;SPI的工作方式和时序图分析&#xff1a;工作模式传输模式与时序分析工作流程 SPI设备的寄存器结构和寄存器设…

Linux四种I/O模型

一.四种模型 阻塞式IO&#xff0c;非阻塞式IO&#xff0c;信号驱动IO&#xff0c;IO多路复用 二.阻塞式IO 特点&#xff1a;最简单&#xff0c;最常用&#xff0c;效率低 阻塞I/O 模式是最普遍使用的I/O 模式 系统默认状态&#xff0c;套接字建立后所处于的模式就是阻塞I/O 模式…

国家网络安全周2023时间是什么时候?有什么特点?谁举办的?

国家网络安全周2023时间是什么时候&#xff1f; 2023年国家网络安全宣传周将于9月11日至17日在全国范围内统一开展。其中开幕式等重要活动将在福建省福州市举行。今年网安周期间&#xff0c;除开幕式外&#xff0c;还将举行网络安全博览会、网络安全技术高峰论坛、网络安全微视…

【Git】万字git与gitHub

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理在git和GitHub时的笔记与感言 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1faf0;&…

【FAQ】安防监控/视频汇聚/云存储/智能视频分析平台EasyCVR显示CPU过载,如何解决?

视频云存储/安防监控/视频汇聚平台EasyCVR基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频监控系统EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云…

线性代数的本质(四)——行列式

文章目录 行列式二阶行列式 n n n 阶行列式行列式的性质克拉默法则行列式的几何理解 行列式 二阶行列式 行列式引自对线性方程组的求解。考虑两个方程的二元线性方程组 { a 11 x 1 a 12 x 2 b 1 a 21 x 1 a 22 x 2 b 2 \begin{cases} a_{11}x_1a_{12}x_2b_1 \\ a_{21}x_…

无涯教程-JavaScript - COLUMNS函数

描述 COLUMNS函数返回数组或引用中的列数。 语法 COLUMNS (array)争论 Argument描述Required/OptionalarrayAn array or array formula, or a reference to a range of cells for which you want the number of Columns.Required Notes COLUMNS(1:1)返回Excel中的列数,即…

【python手写算法】numpy实现简易神经网络和反向传播算法【1】

import numpy as npdef dense(A,W):Znp.matmul(A,W)#矩阵乘法return 1/(1np.exp(-Z))if __name__ __main__:leanring_rate100Anp.array([[200.0,17.0]])# Wnp.array([[1,-3,5],# [-2,4,-6]])# bnp.array([[-1,1,2]])W1 np.array([[0., -10, 4],[-1,3,2]])W2np.ar…

STM32单片机——串口通信(轮询+中断)

STM32单片机——串口通信&#xff08;轮询中断&#xff09; 串口通信相关概念HAL库解析及CubeMX工程配置与程序设计常用函数介绍CubeMX工程配置HAL库程序设计&#xff08;轮询中断&#xff09;轮询数据收发中断收发数据 固件库程序设计及实现固件库配置流程结构体配置及初始化程…

React复习日志大纲

文章目录 React基础篇创建项目启动项目项目目录说明调整项目src剩余目录01基本使用02 列表渲染03 条件渲染04 样式处理05 函数和类组件创建和渲染06 事件绑定07 事件对象e08 传递额外参数09 组件状态修改10 受控组件11 非受控组件12 组件通信父传子13 Props说明14 组件通信子传…

Golang代码漏洞扫描工具介绍——govulncheck

Golang Golang作为一款近年来最火热的服务端语言之一&#xff0c;深受广大程序员的喜爱&#xff0c;笔者最近也在用&#xff0c;特别是高并发的场景下&#xff0c;golang易用性的优势十分明显&#xff0c;但笔者这次想要介绍的并不是golang本身&#xff0c;而且golang代码的漏洞…