【网络编程】之Udp网络通信步骤

【网络编程】之Udp网络通信步骤

  • TCP网络通信
    • TCP网络通信的步骤
      • 对于服务器端
      • 对于客户端
    • TCP实现echo功能
      • 代码实现
        • 服务器端
          • getsockname函数介绍
        • 客户端
        • 效果展示
  • 对比两组函数

TCP网络通信

TCP网络通信的步骤

对于服务器端

  1. 创建监听套接字。(调用socket函数)

    • 使用 socket 函数创建一个 TCP 套接字,为服务器提供网络通信的基础。该套接字将用于监听客户端的连接请求。例如:
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
  2. 显式bind服务器的IP地址和端口号。

    • 使用 bind 函数将服务器的 IP 地址和端口号绑定到创建的套接字上。确保服务器能够通过指定的地址和端口来接受客户端的连接请求。例如:
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用网络接口
    server_addr.sin_port = htons(8080);  // 指定端口号
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    
  3. 设置监听套接字为监听状态,监听客户端请求。

    • 使用 listen 函数将套接字转换为监听状态,服务器开始等待客户端的连接请求。
    listen(server_fd, 5);  // 最大监听队列长度为 5
    
  4. 接受客户端的连接请求。(同时会创建一个专门与这个客户端通信的套接字)

    • 使用 accept 函数接受客户端的连接请求。在此期间,服务器会阻塞,直到有客户端发起连接请求。接受客户端请求后,函数会返回一个新的套接字,该套接字专门用于与客户端通信。
    int client_fd = accept(server_fd, NULL, NULL);
    

    最后两个参数是与正在请求连接的客户端地址相关的参数,如果你不需要发送数据,可以都传NULL

    • 返回的套接字(即与客户端通信的套接字)在大多数情况下会继承 监听套接字本地地址和端口。也就是说它并不需要重复bind
  5. 收发数据

    • 服务器通过 recv 接收客户端发送的数据,并通过 send 向客户端发送响应数据。此时,通信已经通过与客户端建立的专用套接字进行。

      recv(client_fd, buffer, sizeof(buffer), 0);  // 接收数据
      send(client_fd, response, strlen(response), 0);  // 发送数据
      

细节

  1. listen函数的作用是将监听套接字设置为监听状态,并不会阻塞,当监听套接字设置为监听状态后,服务器端才可以监听客户端请求,进而建立连接。
  2. 当客户端向服务器发起连接请求时,这些请求不会直接被服务器立即处理,而是由操作系统暂时存放在监听队列中。监听队列的长度backlog,由用户指定。但是它只影响监听队列的大小,而不限制已经成功建立的连接数量。
  3. 在默认情况下,accept 函数会阻塞,直到有客户端的连接请求到来并完成三次握手。

对于客户端

和服务器端的行为类似,不同的是:

  • 客户端是请求连接方,网络中不会有进程与它主动建立连接,所以它不需要监听套接字,进而也不需要调用listen函数。
  • 它需要主动调用connect函数与服务器端发起连接请求。

步骤

  1. 创建通信的套接字。
  2. bind(不用显式bind,当客户端发起连接时,OS会自动bind)。
  3. 向服务器发起连接请求(connect函数)。
  4. 收发数据。
  5. 关闭通信套接字。

TCP实现echo功能

客户端发送什么,服务器就返回什么。

代码实现

服务器端

服务器端需要不停的建立连接,可以使用多线程、线程池、或者多进程来实现。但是不能使用一个单线程的进程,因为可能需要连接的客户端有很多,建立连接成功后,每个连接都会进入死循环(不停收发数据),直到客户端退出。

如果使用单线程的进程,一个客户端建立连接成功,它就会阻塞到该客户端处理数据的函数中,无法继续处理请求了。

我们使用线程池版本来实现服务器端的代码:

#pragma once
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<cstdio>
#include<cstdlib>
#include"Log.hpp"
#include"InetAddr.hpp"
#include"ThreadPool.hpp"// 错误码
enum
{SOCKETERROR = 1,BINDERROR,USAGEERROR
};// 定义funccommunicate为一个函数类型,用于线程池的任务队列
using funccommunicate = function<void()>;// TcpServer类实现TCP服务器的功能
class TcpServer
{   
private:int _listensock;  // 监听套接字uint16_t _port;   // 服务器端口bool _is_running; // 服务器是否在运行public:// 构造函数,初始化套接字和端口号TcpServer(uint16_t port):_listensock(-1),_port(port),_is_running(false){}// 初始化服务器,创建监听套接字并绑定地址和端口void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字if (_listensock == -1){LOG(FATAL, "socket error");exit(1);}LOG(INFO, "socket success");// 配置服务器地址struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(_port);  // 转换端口号为网络字节序addr.sin_addr.s_addr = INADDR_ANY;  // 绑定所有可用接口// 绑定套接字到指定端口和IP地址if (bind(_listensock, (struct sockaddr*)&addr, sizeof(addr)) == -1){LOG(FATAL, "bind error");exit(1);}LOG(INFO, "bind success");// 开始监听客户端连接,最大连接数为5if (listen(_listensock, 5) == -1){LOG(FATAL, "listen error");exit(1);}}// 处理每个连接的业务逻辑void Service(int sockfd, InetAddr addr){LOG(INFO, "new connect: %s:%d", inet_ntoa(addr.addr().sin_addr), ntohs(addr.addr().sin_port));  // 输出连接信息// 处理接收和响应循环while (true){char buffer[1024];  // 用于接收数据的缓冲区memset(buffer, 0, sizeof(buffer));  // 初始化缓冲区为0int n = recv(sockfd, buffer, sizeof(buffer), 0);  // 接收客户端数据// 构建客户端信息字符串string sender = "[" + addr.ip() + ":" + to_string(addr.port()) + "]#";if (n == -1)  // 如果接收失败{perror("recv");  // 输出错误信息break;}else if (n == 0)  // 客户端关闭了连接{LOG(INFO, "client close");break;}else  // 数据接收成功{buffer[n] = 0;  // 确保接收的数据是一个合法的C字符串LOG(INFO, "%s%s", sender.c_str(), buffer);  // 打印接收到的数据string echoserver = "[echo server]#" + string(buffer);  // 构建回显信息// 获取服务器端新套接字的本地地址和端口struct sockaddr_in local_addr;socklen_t len = sizeof(local_addr);if (getsockname(sockfd, (struct sockaddr*)&local_addr, &len) == -1){perror("Getsockname failed");return;}// 输出本地地址和端口信息printf("New socket local address: %s:%d\n", inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));// 发送回显消息到客户端send(sockfd, echoserver.c_str(), echoserver.size(), 0);}}close(sockfd);  // 关闭套接字,结束与客户端的通信};// 服务器主循环,不断接收新的连接请求void Loop(){_is_running = true;while (_is_running){struct sockaddr_in peer;  // 存储客户端的地址信息socklen_t len = sizeof(peer);// 等待并接受新的连接请求int sockfd = ::accept(_listensock, (struct sockaddr*)&peer, &len);cout << "建立新连接成功" << endl;if (sockfd == -1)  // 如果接收连接失败,输出错误信息{perror("accept");break;}InetAddr addr(peer);  // 将客户端地址封装到InetAddr对象中// 版本1:直接调用Service处理连接(不建议这种方式,因为它会阻塞并限制并发)// Service(sockfd, addr);  // 每次只能处理一个连接,无法同时处理多个连接// 版本2:使用线程池处理连接(推荐的方式,支持并发)bool ret = ThreadPoolModule::ThreadPool<funccommunicate>::GetInstance()->EnqueueTask(bind(&TcpServer::Service, this, sockfd, addr));}_is_running = false;}// 析构函数,关闭监听套接字~TcpServer(){if (_listensock != -1){close(_listensock);}}
};
getsockname函数介绍
  • int getsockname(int sockfd, struct sockaddr *restrict addr,socklen_t *restrict addrlen);
    • 函数功能:返回当前套接字bind的地址。
    • 参数
      • int sockfd:要查看bind地址的套接字描述符。
      • struct sockaddr *restrict addr:输出型参数,该函数会把地址写进这个变量指向的空间中。
      • socklen_t *restrict addrlen:指向保存结构体大小变量的指针,输入型参数。
    • 返回值:成功0被返回。否则-1被返回,errno被设置。
    • 头文件<sys/socket.h>
客户端

TcpClient.cc:

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>using namespace std;// Usage函数:如果程序参数不正确,打印如何使用该程序的提示信息
void Usage(char* s)
{cout << "Usage:\n\t" << s << " serverip serverport" << endl;exit(1);
}int main(int argc, char* argv[])
{// 检查传入的参数数量,若参数不正确,则调用Usage函数if(argc != 3){Usage(argv[0]);  // 打印使用帮助信息并退出return 1;}// 从命令行参数获取服务器IP地址和端口号string ip = argv[1];  // 服务器IP地址uint16_t port = stoi(argv[2]);  // 服务器端口号,将字符串转换为整数// 创建套接字,使用IPv4地址族和TCP协议(SOCK_STREAM表示流式套接字)int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字if(sockfd == -1)  // 如果创建套接字失败,打印错误并返回{perror("socket create error");  // 输出错误信息return 1;}// 客户端不需要bind,bind通常用于服务器端// 客户端也不需要listen,监听请求是服务器端的工作// 设置服务器的地址信息struct sockaddr_in addr;  // sockaddr_in结构体用于存储服务器的网络地址addr.sin_family = AF_INET;  // 使用IPv4地址族addr.sin_port = htons(port);  // 设置服务器的端口号(htons将端口号转换为网络字节序)inet_pton(AF_INET, ip.c_str(), &addr.sin_addr.s_addr);  // 将IP地址字符串转换为网络字节序的二进制格式// 连接到服务器if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1)  // 调用connect连接服务器{perror("connect error");  // 如果连接失败,输出错误信息return 1;}// 客户端和服务器之间进行通信while(true){string message;cout << "please input message:";  // 提示用户输入消息getline(cin, message);  // 从标准输入获取一行字符串作为消息// 将输入的消息发送到服务器send(sockfd, message.c_str(), message.size() + 1, 0);  // 发送消息到服务器,+1用于包括消息结尾的'\0'// 接收服务器返回的消息char buffer[1024];  // 定义接收缓冲区,大小为1024字节memset(buffer, 0, sizeof(buffer));  // 将缓冲区初始化为0int n = recv(sockfd, buffer, sizeof(buffer), 0);  // 从服务器接收数据if(n == -1)  // 如果接收数据失败{perror("recv error");  // 输出错误信息break;  // 跳出循环,关闭连接}else if(n == 0)  // 如果服务器关闭了连接{cout << "server close" << endl;  // 打印提示信息break;  // 跳出循环,结束通信}else  // 数据接收成功{buffer[n] = 0;  // 确保接收到的数据是一个合法的C字符串(添加终止符'\0')cout << buffer << endl;  // 输出服务器返回的消息}}// 关闭套接字,结束与服务器的通信close(sockfd);  // 关闭套接字return 0;  // 程序正常结束
}
效果展示

image-20250115191358332


  • 打印服务器端与客户端通信的socket套接字描述符的地址,发现端口一样(8080),但是和虚拟机客户端和本地的客户端通信的服务器端的套接字bindIP地址不同,这是因为服务器bind的IP地址是0.0.0.0,表示监听主机内所以网络接口的流量,虚拟机客户端访问和本地访问的流量进入主机内,流量会经过不同的网络接口,所以与他们通信的套接字的IP地址会不同。

对比两组函数

  1. recvrecvformread

    • recvread

      • 相似之处:都是从文件描述符🀄️读取数据。

      • 不同之处recv是专门用于网络套接字中读取数据,而read更加通用,可以读取任何类型的文件描述符。允许指定标志(flags)来控制接收操作的行为。例如,标志可以指定如何处理数据或是否采用非阻塞模式等。

    • recvfromrecv

      • recvfromrecvfrom() 是设计用来接收数据包并且能够获取发送方的地址信息的。常常在UDP中使用,在TCP中也可以接收数据,但它不会返回对端的地址信息。

        image-20250115195824850

      • recvrecv是专门用于TCP接收数据,它是从一个已经建立的连接中获取数据,因此不需要提供发送方的地址信息。recv也能在UDP中接收数据,但它无法获取发送方的地址信息。

      image-20250115195840621

  2. sendsendtowrite

    • 相同点:这一组函数都是用于发送数据的。

    • 不同点sendsendto用于网络通信,从套接字描述符中读取数据。而write更加的通用。send用于TCP通信,面向连接,不需要指定客户端地址。而sendto需要指定客户端的地址。

      image-20250116161803708

      • 尽管从用户角度看它们的功能略有重叠,网络相关的功能通常会选择 send 和 sendto,因为它们支持更多与网络协议相关的选项。

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

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

相关文章

RV1126解码(1)

比如我们现在要拉一个流&#xff0c; 拉一个rtmp或者拉一个rtsp的流&#xff0c;让它显示到显示屏上面去&#xff0c;此时就要用到我们这个解码模块了&#xff0c;把它个解出来并且发到其他模块去。 主要功能是通过FFMPEG的API读取每一帧的音视频数据&#xff0c;并通过RV1126的…

js实现点击音频实现播放功能

目录 1. HTML 部分&#xff1a;音频播放控件 2. CSS 部分&#xff1a;样式设置 3. JavaScript 部分&#xff1a;音频控制 播放和暂停音频&#xff1a; 倒计时更新&#xff1a; 播放结束后自动暂停&#xff1a; 4. 总结&#xff1a; 完整代码&#xff1a; 今天通过 HTML…

kotlin标准库里面也有很多java类

Kotlin 标准库中确实存在许多与 Java 类直接关联或基于 Java 类封装的结构&#xff0c;但这并不是“问题”&#xff0c;而是 Kotlin 与 JVM 生态深度兼容和互操作性的体现。以下从技术原理和设计哲学的角度详细解释&#xff1a; 一、Kotlin 与 JVM 的底层关系 Kotlin 代码最终…

【DeepSeek】从文本摘要到对话生成:DeepSeek 在 NLP 任务中的实战指南

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…

亚博microros小车-原生ubuntu支持系列 27、手掌控制小车运动

背景知识 本节跟上一个测试类似&#xff1a;亚博microros小车-原生ubuntu支持系列&#xff1a;26手势控制小车基础运动-CSDN博客 都是基于MediaPipe hands做手掌、手指识别的。 为了方便理解&#xff0c;在贴一下手指关键点分布。手掌位置就是靠第9点来识别的。 2、程序说明…

2025-02-13 学习记录--C/C++-PTA 7-17 爬动的蠕虫

一、题目描述 ⭐️ 二、代码&#xff08;C语言&#xff09;⭐️ #include <stdio.h>int main() {int N, U, D; // N: 井的总高度&#xff0c;U: 每分钟向上爬的高度&#xff0c;D: 每分钟滑下的高度int height 0; // 蠕虫当前的高度int minute 0; // 蠕虫爬行的时间sc…

多模态识别和自然语言处理有什么区别

在科技飞速发展的当下&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面。不知道大家有没有这样的经历&#xff1a;早上醒来&#xff0c;对着智能音箱说 “播放今天的新闻”&#xff0c;音箱不仅能识别你的语音&#xff0c;还能在播放新闻的同时&am…

RAG入门: RetroMAE、BGE、M3、MemoRAG

RAG实际上第一步都是先做Retrieval&#xff0c;关于Retrieval的思路有很多&#xff0c;持续更新&#xff1a; RetroMAE &#xff08;论文RetroMAE: Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder&#xff09; RetraoMAE包括两个模块&#xff0c;…

【MySQL例题】我在广州学Mysql 系列——有关数据备份与还原的示例

ℹ️大家好&#xff0c;我是练小杰&#xff0c;今天周二&#xff0c;明天就是元宵节了呀&#xff01;&#xff01;&#x1f606; 俗话说“众里寻他千百度。蓦然回首&#xff0c;那人却在&#xff0c;灯火阑珊处。” 本文主要对数据库备份与还原的知识点例题学习~~ 前情回顾&…

DeepSeek大模型一键部署解决方案:全平台多机分布式推理与国产硬件优化异构计算私有部署

DeepSeek R1 走红后&#xff0c;私有部署需求也随之增长&#xff0c;各种私有部署教程层出不穷。大部分教程只是简单地使用 Ollama、LM Studio 单机运行量化蒸馏模型&#xff0c;无法满足复杂场景需求。一些操作配置也过于繁琐&#xff0c;有的需要手动下载并合并分片模型文件&…

头歌实验---C/C++程序设计

目录 实验1&#xff1a;C语言程序设计编辑与调试环境 第1关&#xff1a;打印输出 Hello World 任务描述 答案代码 第2关&#xff1a;打印输出图形 任务描述 答案代码 第3关&#xff1a;求3个数的最大值 任务描述 答案代码 第4关&#xff1a;熟悉C语言调试过程 任务描…

Mysql进阶篇(mysqlcheck - 表维护程序)

mysqlcheck的作用 mysqlcheck客户端用于执行表维护&#xff0c;可以对表进行&#xff1a;分析、检查、优化或修复操作。 &#xff08;1&#xff09;分析的作用是查看表的关键字分布&#xff0c;能够让 sql 生成正确的执行计划&#xff08;支持 InnoDB&#xff0c;MyISAM&#x…

单调栈及相关题解

单调递增栈&#xff1a;栈中数据入栈单调递增序列(栈底到栈顶是单调递增)&#xff1b; 单调递减栈&#xff1a;栈中数据入栈单调递减序列(栈底到栈顶是单调递减)。 单调递增栈&#xff1a; 维护单调递增栈:遍历数组中每一个元素&#xff0c;执行入栈&#xff1a;每次入栈前先…

细胞计数专题 | LUNA-FX7™新自动对焦算法提高极低细胞浓度下的细胞计数准确性

现代细胞计数仪采用自动化方法&#xff0c;在特定浓度范围内进行细胞计数。其上限受限于在高浓度条件下准确区分细胞边界的能力&#xff0c;而相机视野等因素则决定了下限。在图像中仅包含少量可识别细胞或特征的情况下&#xff0c;自动对焦可能会失效&#xff0c;从而影响细胞…

P1878 舞蹈课(详解)c++

题目链接&#xff1a;P1878 舞蹈课 - 洛谷 | 计算机科学教育新生态 1.题目解析 1&#xff1a;我们可以发现任意两个相邻的都是异性&#xff0c;所以他们的舞蹈技术差值我们都要考虑&#xff0c;4和2的差值是2&#xff0c;2和4的差值是2&#xff0c;4和3的差值是1&#xff0c;根…

基于HAL库的按钮实验

实验目的 掌握STM32 HAL库的GPIO输入配置方法。 实现通过按钮控制LED亮灭&#xff08;支持轮询和中断两种模式&#xff09;。 熟悉STM32CubeMX的外部中断&#xff08;EXTI&#xff09;配置流程。 实验硬件 开发板&#xff1a;STM32系列开发板&#xff08;如STM32F103C8T6、N…

如何使用智能化RFID管控系统,对涉密物品进行安全有效的管理?

载体主要包括纸质文件、笔记本电脑、优盘、光盘、移动硬盘、打印机、复印机、录音设备等&#xff0c;载体&#xff08;特别是涉密载体&#xff09;是各保密、机要单位保证涉密信息安全、防止涉密信息泄露的重要信息载体。载体管控系统主要采用RFID射频识别及物联网技术&#xf…

Spring Cloud-Sentinel

Sentinel服务熔断与限流 Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。 官网地址&#xff1a;home | Sentinelhttps://sen…

土星云边缘计算微服务器 SE110S-WA32加持DeepSeek,本地部署企业私有推理大模型!

模型介绍 DeepSeek-R1-Distill-Qwen-7B是一款高性能的语言模型&#xff0c;基于DeepSeek-R1的推理能力&#xff0c;通过蒸馏技术将推理模式迁移到较小的Qwen模型上&#xff0c;在保持高性能的同时&#xff0c;显著降低了资源消耗&#xff0c;更适合在资源受限的环境中部署。 该…

React进阶之React核心源码解析(二)

React核心源码解析 diff单一节点比较diff多节点比较diff两轮遍历比较第一轮比较第二轮比较 Update 状态更新Concurrent Mode diff 一共两个阶段 render&#xff1a;内存中的更新&#xff0c;主要是通过递归的过程&#xff0c;来将react变化的部分&#xff0c;在内存中找到哪些…