【网络编程】之Udp网络通信步骤
【网络编程】之Udp网络通信步骤
- TCP网络通信
- TCP网络通信的步骤
- 对于服务器端
- 对于客户端
- TCP实现echo功能
- 代码实现
- 服务器端
- getsockname函数介绍
- 客户端
- 效果展示
- 对比两组函数
TCP网络通信
TCP网络通信的步骤
对于服务器端
-
创建监听套接字。(调用
socket
函数)- 使用
socket
函数创建一个 TCP 套接字,为服务器提供网络通信的基础。该套接字将用于监听客户端的连接请求。例如:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
- 使用
-
显式
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));
- 使用
-
设置监听套接字为监听状态,监听客户端请求。
- 使用
listen
函数将套接字转换为监听状态,服务器开始等待客户端的连接请求。
listen(server_fd, 5); // 最大监听队列长度为 5
- 使用
-
接受客户端的连接请求。(同时会创建一个专门与这个客户端通信的套接字)
- 使用
accept
函数接受客户端的连接请求。在此期间,服务器会阻塞,直到有客户端发起连接请求。接受客户端请求后,函数会返回一个新的套接字,该套接字专门用于与客户端通信。
int client_fd = accept(server_fd, NULL, NULL);
最后两个参数是与正在请求连接的客户端地址相关的参数,如果你不需要发送数据,可以都传
NULL
。- 返回的套接字(即与客户端通信的套接字)在大多数情况下会继承 监听套接字 的 本地地址和端口。也就是说它并不需要重复
bind
。
- 使用
-
收发数据:
-
服务器通过
recv
接收客户端发送的数据,并通过send
向客户端发送响应数据。此时,通信已经通过与客户端建立的专用套接字进行。recv(client_fd, buffer, sizeof(buffer), 0); // 接收数据 send(client_fd, response, strlen(response), 0); // 发送数据
-
细节:
listen
函数的作用是将监听套接字设置为监听状态,并不会阻塞,当监听套接字设置为监听状态后,服务器端才可以监听客户端请求,进而建立连接。- 当客户端向服务器发起连接请求时,这些请求不会直接被服务器立即处理,而是由操作系统暂时存放在监听队列中。监听队列的长度
backlog
,由用户指定。但是它只影响监听队列的大小,而不限制已经成功建立的连接数量。 - 在默认情况下,
accept
函数会阻塞,直到有客户端的连接请求到来并完成三次握手。
对于客户端
和服务器端的行为类似,不同的是:
- 客户端是请求连接方,网络中不会有进程与它主动建立连接,所以它不需要监听套接字,进而也不需要调用
listen
函数。 - 它需要主动调用
connect
函数与服务器端发起连接请求。
步骤:
- 创建通信的套接字。
bind
(不用显式bind
,当客户端发起连接时,OS会自动bind
)。- 向服务器发起连接请求(
connect
函数)。 - 收发数据。
- 关闭通信套接字。
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; // 程序正常结束
}
效果展示
- 打印服务器端与客户端通信的
socket套接字描述符
的地址,发现端口一样(8080),但是和虚拟机客户端和本地的客户端通信的服务器端的套接字bind
的IP
地址不同,这是因为服务器bind
的IP地址是0.0.0.0
,表示监听主机内所以网络接口的流量,虚拟机客户端访问和本地访问的流量进入主机内,流量会经过不同的网络接口,所以与他们通信的套接字的IP地址
会不同。
对比两组函数
-
recv
、recvform
、read
。-
recv
与read
:-
相似之处:都是从文件描述符🀄️读取数据。
-
不同之处:
recv
是专门用于网络套接字中读取数据,而read
更加通用,可以读取任何类型的文件描述符。允许指定标志(flags)来控制接收操作的行为。例如,标志可以指定如何处理数据或是否采用非阻塞模式等。
-
-
recvfrom
与recv
:-
recvfrom
:recvfrom()
是设计用来接收数据包并且能够获取发送方的地址信息的。常常在UDP中使用,在TCP中也可以接收数据,但它不会返回对端的地址信息。 -
recv
:recv
是专门用于TCP接收数据,它是从一个已经建立的连接中获取数据,因此不需要提供发送方的地址信息。recv
也能在UDP中接收数据,但它无法获取发送方的地址信息。
-
-
-
send
、sendto
、write
:-
相同点:这一组函数都是用于发送数据的。
-
不同点:
send
与sendto
用于网络通信,从套接字描述符中读取数据。而write
更加的通用。send
用于TCP通信,面向连接,不需要指定客户端地址。而sendto
需要指定客户端的地址。- 尽管从用户角度看它们的功能略有重叠,网络相关的功能通常会选择 send 和 sendto,因为它们支持更多与网络协议相关的选项。
-
相关文章:

【网络编程】之Udp网络通信步骤
【网络编程】之Udp网络通信步骤 TCP网络通信TCP网络通信的步骤对于服务器端对于客户端 TCP实现echo功能代码实现服务器端getsockname函数介绍 客户端效果展示 对比两组函数 TCP网络通信 TCP网络通信的步骤 对于服务器端 创建监听套接字。(调用socket函数ÿ…...

Java 基于 SpringBoot+Vue 的家政服务管理平台设计与实现
博主介绍:✌程序员徐师兄、8年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战*✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…...

架构——Nginx功能、职责、原理、配置示例、应用场景
以下是关于 Nginx 的功能、职责、原理、配置示例、应用场景及其高性能原因的详细说明: 一、Nginx 的核心功能 1. 静态资源服务 功能:直接返回静态文件(如 HTML、CSS、JS、图片、视频等)。配置示例:server {listen 80…...

Spring Boot中使用Flyway进行数据库迁移
文章目录 概要Spring Boot 集成 FlywayFlyway 其他用法bug错误Flyway版本不兼容数据库存在表了Flyway 的校验和(Checksum)不匹配 概要 在 Spring Boot 项目开发中,数据库的变更不可避免。手动执行 SQL 脚本不仅容易出错,也难以维…...

CAS单点登录(第7版)9.属性
如有疑问,请看视频:CAS单点登录(第7版) 属性 属性定义 概述 属性定义 从身份验证或属性存储库源获取和解析 CAS 中属性的定义时,往往使用其名称进行定义和引用,而无需任何其他元数据或修饰。例如&#…...

137,【4】 buuctf web [SCTF2019]Flag Shop
进入靶场 都点击看看 发现点击work会增加¥ 但肯定不能一直点下去 抓包看看 这看起来是一个 JWT(JSON Web Token)字符串。JWT 通常由三部分组成,通过点(.)分隔,分别是头部(Header&…...

P9853 [入门赛 #17] 方程求解
P9853 [入门赛 #17] 方程求解 - 洛谷 题目描述 小A有n个关于x的方程,第i个方程形如aixibici。方程的解x均为正整数,例如下面几个方程都是符合要求的方程: 2x 4 10 -3x 13 10 4x - 8 16 其中,第一组方程的解为x1…...
【网络安全 | 漏洞挖掘】跨子域账户合并导致的账户劫持与删除
未经许可,不得转载。 文章目录 概述正文漏洞成因概述 在对目标系统进行安全测试时,发现其运行着两个独立的域名——一个用于司机用户,一个用于开发者/企业用户。表面上看,这两个域名各自独立管理账户,但测试表明它们在处理电子邮件变更时存在严重的逻辑漏洞。该漏洞允许攻…...

spring集成activiti流程引擎(源码)
前言 activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,请假审批demo从流程绘制到审批结束实例。 源码获取:本文末个人名片直接获取。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器,流行…...

ROS基本功能
1.Topic话题与Message消息(主要通讯方式) 基本规则 发布消息的步骤 常用工具 话题的订阅 使用launch启动多个节点...
C++基础系列【13】类的成员初始化
博主介绍:程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章,首发gzh,见文末👇…...

Redis 03章——10大数据类型概述
一、which10 (1)一图 (2)提前声明 这里说的数据类型是value的数据类型,key的类型都是字符串 官网:Understand Redis data types | Docs (3)分别是 1.3.1redis字符串࿰…...
Ubuntu 上安装 Elasticsearch 7.6.0
要在 Ubuntu 24.04 上安装 Elasticsearch 7.6.0,可以按照以下步骤进行: 步骤 1: 更新系统依赖 确保系统是最新的,并安装必要的依赖包: sudo apt update sudo apt upgrade -y sudo apt install -y apt-transport-https openjdk-1…...
Android ListPreference使用
Android ListPreference使用 参考 添加链接描述 导入 androidx.preference.ListPreferenceListPreference是Android中的一个Preference子类,用于显示一个可选择的列表,并且可以保存用户所选择的值。它继承自DialogPreference,可以在用户点击时弹出一个对话框,显示可选择的…...

Java 大视界 -- 绿色大数据:Java 技术在节能减排中的应用与实践(90)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...

计算四个锚点TOA定位中GDOP的详细步骤和MATLAB例程
该MATLAB代码演示了在三维空间中,使用四个锚点的TOA(到达时间)定位技术计算几何精度衰减因子(GDOP)的过程。如需帮助,或有导航、定位滤波相关的代码定制需求,请联系作者 文章目录 DOP计算原理MATLAB例程运行结果示例关键点说明扩展方向另有文章: 多锚点Wi-Fi定位和基站…...

英码科技基于昇腾算力实现DeepSeek离线部署
DeepSeek-R1 模型以其创新架构和高效能技术迅速成为行业焦点。如果能够在边缘进行离线部署,不仅能发挥DeepSeek大模型的效果,还能确保数据处理的安全性和可控性。 英码科技作为AI算力产品和AI应用解决方案服务商,积极响应市场需求࿰…...

CTex安装和使用(1)
CTeX是一款基于TeX/LaTeX的集成开发环境(IDE),主要用于文档排版,特别是在处理复杂的数学公式和学术论文方面具有显著优势。以下是CTeX的一些基本信息: 功能 文档编辑 :提供了一个友好的界面用于编辑LaTeX…...

Oracle序列(基础操作)
序列概念 序列是用于生成唯一、连续序号的对象。 序列可以是升序的,也可以是降序的。 使用CREATE SEQUENCE语句创建序列。 start with 1 指定第一个序号从1开始 increment by 1 指定序号之间的间隔为1 increment by -1 降序1000 999 998这样 maxvalue 2000 表…...

Unity Shader Graph 2D - Procedural程序化图形循环的箭头
前言 箭头在游戏开发中也是常见的一种图形之一,在游戏中箭头通常会用作道路引导或者指示,告诉玩家前进的方向,是比较重要的提示信号。本文将通过使用程序化图形来实现循环滚动的箭头效果,实践和熟悉Shader Graph的相关节点。 首先创建一个Shader Graph文件命名为Mo…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...