C/C++ Linux网络编程2 - Socket编程与简单UDP服务器客户端 - 教程

news/2026/1/22 15:18:01/文章来源:https://www.cnblogs.com/yangykaifa/p/19517336

上篇文章:Linux网络编程 - 1网络编程基础-CSDN博客

代码仓库:橘子真甜 (yzc-YZC) - Gitee.com

目录

一. Socket编程API与结构体

1.1 Socketaddr结构体

二. UDP网络编程API⭐

2.1 socket

2.2 bind

2.3 sendto

2.4 recvfrom

三. UDP服务端代码

3.1 udpserver.hpp

3.2 udpserver.cc

四. UDP客户端代码

4.1 udpclient.hpp

4.2 udpclient.cc

五. 测试


一. Socket编程API与结构体

          socket是插座的意思,插就是应用程序,座就是网络socket。通信双方都通过socket进行通信。

1.1 Socketaddr结构体

        有三套结构体,sockaddr sockaddr_in sockaddr_un

        sockaddr:原始套接字,可以通过传输层控制底层结构,常用于网络中的监听,抓包等功能。结构如下:

        sockaddr_in:网络套接字,常用于跨主机之间的网络通信。内部集合了主机的IP和端口,在大部分网络编程API中,需要强制转化为原始套接字。结构如下:

Unix域间套接字:用于主机内部的通信,类似于管道和共享内存。结构如下:

        在使用这些结构体的时候,通过将 sockaddr_in sockaddr_un转化为 sockaddr即可实现相应类型的通信。

        其实就是一种多态的思想,soackaddr就是基类,剩下两种是派生类。

二. UDP网络编程API⭐

        UDP的特点是不保证可靠传输,面向数据报,无连接。

所以:

我们每一次接收数据的时候,应该将一个完整的报文读取完毕。

由于无连接,发送数据的时候需要带上自己的ip和端口等信息

收数据的时候也要定义对象来获取对方的ip和端口等信息

2.1 socket

//头文件
#include 
#inclyde 
//函数原型,用于创建一个用于网络通信的套接字fd
int socket(int domin, int type, int protocol)
//参数说明
//domin : 域,表示是需要网络通信还是主机内部通信
//type  : 通信的类型
//SOCK_STREAM    是流式套接字通信,即TCP通信
//SOCK_DGRAM     是报文套接字通信,即UDP通信
// protocol,
// 分为tcpprotocol udpprotoclo 但是我们一般设置为0,因为type参数选择之后,这个就会自动固定
//返回值,返回一个用于通信的文件fd,其实socket与open是非常类似的

        Linux中一切皆文件,打开网络与打开普通文件是一样的。读写网络数据与读写文件数据也是非常类似的

2.2 bind

        用于绑定sockfd与IP端口

#include 
#inckude 
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
sockfd : 使用socket获取的文件描述符
addr : 传入的结构体,该结构体是你需要通信的方式,内部包含了IP和端口等信息
addrlen : 传入结构体的长度,需要注意类型
返回值:绑定成功返回0,失败返回-1
//注意,我们定义下面三种结构体的时候需要带上头文件#include 
struct sockaddr s;
s.sin_family = AF_INET    表示协议家族,我们需要传入协议,如AF_INET表示使用网络通信
s.sin_port                表示绑定端口号
s.sin_addr                存储了IP号码
s.sin_addr.s_addr         即可获取IP地址

2.3 sendto

        这个接口常用于UDP服务器/客户端之间的发送信息

#include 
#include 
用于发送信息到网络中
//函数原型
ssize_t sendto(int sockfd, void *buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t *addrlen);
//将  len长度的buf数据通过sockfd这个套接字发送到网络中
flags为0表示阻塞
//输入形参数
dest_addr 对方接收数据的IP地址和端口
addrlen   对方接收数据结构体长度

2.4 recvfrom

        用于UDP通信时候接收数据

#include 
#include 
用于接收来自网络的信息
//函数原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen)
//参数
sockfd     接收信息的文件描述符
bur        接收信息写入的缓冲区    len 接收信息的长度
flags      接收信息的方式,一般默认设置为0    表示阻塞方式,没有信息阻塞等待
//下面两个参数是输入输出形参数,是为了获取对方的IP信息与端口号码,协议方式
src_addr    对方IP的信息
addrlen     该信息的长度,注意这个参数在定义的时候不需要初始化为src_addr的长度 addrlen = sizeof(src_addr)

三. UDP服务端代码

        这个服务器用于接收客户端的代码,然后打印输出。

3.1 udpserver.hpp

        服务器的代码框架如下:

#pragma once
#include 
#include 
#include 
#include 
static const std::string defaultIp = "0.0.0.0";
class udpServer
{
public:udpServer(uint16_t port, const std::string &ip = defaultIp): _sockfd(-1), _ip(ip), _port(port) {}void init() {}void run() {}
private:int _sockfd; // 套接字std::string _ip; // 服务器ipuint16_t _port;  // 绑定的端口
};

需要变量来存储服务器的 通信fd,ip和端口。        

init用于初始化服务器:包含socket,bind

run:用于服务器的运行,一个服务器一般是一个死循环执行代码的。

一般来说,一台主机是有很多ip的,为了能够保证接收数据不丢失,服务器绑定的IP一般都是"0.0.0.0"表示服务器可以接收底层任意ip地址的网络数据

init代码:

  void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("socket success\n");// 2.绑定fd和端口// 首先要设置好通信结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);if (n < 0){perror("bind");exit(errno);}printf("bind success\n");// UDP在绑定好之后,就能运行了}

run:

        服务器运行的时间是很长的,所以我们需要使用一个死循环来读取数据。

        void run(){char buffer[1024];while (true){// 收数据,要定义对象获取对方的ip和端口struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(clientaddr));socklen_t clientaddrlen = sizeof(clientaddr);// recvfrom接收数据int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);if (count > 0){// 通过设置好的sockaddr获取ip,端口等信息std::string clientip = inet_ntoa(clientaddr.sin_addr);uint16_t clientport = ntohs(clientaddr.sin_port);// 打印收到数据的信息printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);}// 清空 buffermemset(buffer, 0, sizeof(buffer));}}

总代码如下:

namespace YZC
{static const std::string defaultIp = "0.0.0.0";class udpServer{public:udpServer(uint16_t port, const std::string &ip = defaultIp): _sockfd(-1), _ip(ip), _port(port) {}void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("socket success\n");// 2.绑定fd和端口// 首先要设置好通信结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要转化为大端数据serveraddr.sin_port = htons(_port);                  // 需要转化为大端数据socklen_t len = sizeof(serveraddr);                  // 设置bind第三个参数int n = bind(_sockfd, (struct sockaddr *)&serveraddr, len);if (n < 0){perror("bind");exit(errno);}printf("bind success\n");// UDP在绑定好之后,就能运行了}void run(){char buffer[1024];while (true){// 收数据,要定义对象获取对方的ip和端口struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(clientaddr));socklen_t clientaddrlen = sizeof(clientaddr);// recvfrom接收数据int count = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&clientaddr, &clientaddrlen);if (count > 0){// 通过设置好的sockaddr获取ip,端口等信息std::string clientip = inet_ntoa(clientaddr.sin_addr);uint16_t clientport = ntohs(clientaddr.sin_port);// 打印收到数据的信息printf("client[%s][%d] --> server:%s\n", clientip.c_str(), clientport, buffer);}// 清空 buffermemset(buffer, 0, sizeof(buffer));}}private:int _sockfd; // 套接字std::string _ip; // 服务器ipuint16_t _port;  // 绑定的端口};
}

3.2 udpserver.cc

        这个代码包含main函数,用于服务器的启动和测试

#include 
#include 
#include "udpserver.hpp"
// 设置使用方法
void Usage(const char *s)
{printf("Usage:\r\n%s serverport\n", s);
}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(-1);}// 使用智能指针管理服务器,可以自动销毁。防止内存泄漏std::unique_ptr usptr(new YZC::udpServer(atoi(argv[1])));usptr->init();usptr->run();return 0;
}

四. UDP客户端代码

        这个客户端用于向服务端发送数据

思考一下:服务端为何需要bind绑定fd和端口?

        因为,如果没有指明端口,其他客户端如何连接服务器?

思考一下:客户端需要显示绑定fd和端口吗?

        不需要,因为通信是由客户端发起的,服务端被动接收。此时服务端无需知道客户端具体的ip和端口信息

        服务端可以直接通过recvfrom获取我的ip/端口等信息

4.1 udpclient.hpp

        框架与server类似

namespace YZC
{class udpClient{public:udpClient(std::string &ip, uint16_t port): _ip(ip), _port(port) {}void init(){}void start(){}private:int _sockfd; // 监听套接字std::string _ip; // 目的ipuint16_t _port;  // 目的端口};
}

init : 上面讨论过了,只需要socket,无需显示bind。如果没有bind,在发送数据的时候内核会自动帮我们找到没有使用的port进行bind

        void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("client socket success\n");// bind由OS自行决定}

start:

        用于发送数据

  void start(){// 发送数据,需要指定对方的ip和端口struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));// 设置ip/端口/通信等信息。注意转化为大端数据serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());serveraddr.sin_port = htons(_port);socklen_t clientaddrlen = sizeof(serveraddr);// 可以发送数据char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));std::cout << "请输入你想要向server发送的数据;";std::cin >> buffer;int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);if (count <= 0){printf("数据发送异常,client quit\n");close(_sockfd);exit(-1);}printf("数据发送成功\n");}}

总代码如下:

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
namespace YZC
{class udpClient{public:udpClient(std::string &ip, uint16_t port): _ip(ip), _port(port) {}void init(){// 1.socket创建套接字,udp需要使用 报文_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){perror("socket");exit(errno);}printf("client socket success\n");// bind由OS自行决定}void start(){// 发送数据,需要指定对方的ip和端口struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));// 设置ip/端口/通信等信息。注意转化为大端数据serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(_ip.c_str());serveraddr.sin_port = htons(_port);socklen_t clientaddrlen = sizeof(serveraddr);// 可以发送数据char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));std::cout << "请输入你想要向server发送的数据;";std::cin >> buffer;int count = sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&serveraddr, clientaddrlen);if (count <= 0){printf("数据发送异常,client quit\n");close(_sockfd);exit(-1);}printf("数据发送成功\n");}}private:int _sockfd; // 监听套接字std::string _ip; // ipuint16_t _port;  // 绑定的端口};
}

4.2 udpclient.cc

#include 
#include 
#include "udpclient.hpp"
// 设置使用方法
void Usage(const char *s)
{printf("Usage:\r\n%s serverip serverport\n", s);
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(-1);}// 使用智能指针管理服务器,可以自动销毁。防止内存泄漏std::unique_ptr ucptr(new YZC::udpClient(argv[1], atoi(argv[2])));ucptr->init();ucptr->start();return 0;
}

五. 测试

        使用上面的代码来测试服务端server与客户端client 能否通信

这里首先了解一下本地回环地址 127.0.0.1。使用命令 ifconfig查看网卡信息

下面的数据是我在自己的云服务器输入ifconfig获取的。

eth0: flags=4163  mtu 1500inet 172.17.52.126  netmask 255.255.192.0  broadcast 172.17.63.255inet6 fe80::216:3eff:fe0a:5e1e  prefixlen 64  scopeid 0x20ether 00:16:3e:0a:5e:1e  txqueuelen 1000  (Ethernet)RX packets 1944787  bytes 1196104294 (1.1 GiB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 1537251  bytes 634615558 (605.2 MiB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
lo: flags=73  mtu 65536inet 127.0.0.1  netmask 255.0.0.0inet6 ::1  prefixlen 128  scopeid 0x10loop  txqueuelen 1000  (Local Loopback)RX packets 746248  bytes 164466741 (156.8 MiB)RX errors 0  dropped 0  overruns 0  frame 0TX packets 746248  bytes 164466741 (156.8 MiB)TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以看第二段的第二行,本地回环地址是 127.0.0.1

首先要启动服务器

然后启动客户端

运行结果如下:

        可以看到,我们的数据被客户端正常发送了。服务端也接收到来正确的数据

这样一来就能保证 server - client之间的通信,当然这份代码是比较简单的。

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

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

相关文章

那曲市英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜推荐

经教育部教育考试院备案、全国雅思教学质量评估中心独家指导,参照《2025-2026中国大陆雅思备考趋势白皮书》核心指标,结合那曲市色尼区、安多县、聂荣县、班戈县、比如县等区县9000份考生调研问卷、95家教育机构实测…

人工磨题 VS 宏智树 AI 设计?实证调研的效率革命藏在这里

做社科类实证论文时&#xff0c;你是否陷入过这样的两难&#xff1a;手动设计问卷&#xff0c;耗一周打磨题目仍被导师批 “信效度不足”&#xff1b;用普通工具生成&#xff0c;又担心题目诱导性强、逻辑漏洞多&#xff0c;最后收集的数百份数据全部作废&#xff1f;作为深耕论…

【Jmeter】深度解剖Jmeter的二次开发

JMeter 是一个功能强大的性能测试工具&#xff0c;但它可能无法满足特定项目或组织的特定需求。通过进行二次开发&#xff0c;可以定制 JMeter&#xff0c;使其适应具体项目的需求。例如&#xff0c;可能需要添加自定义的测试元件、报告生成器或结果分析器等。二次开发三种方式…

621-3580RC输入模块

621-3580RC 输入模块621-3580RC 是工业自动化控制系统中的 数字/离散输入模块&#xff0c;用于接收现场开关、传感器或其他数字设备的信号&#xff0c;并将其传递给控制系统进行处理。核心作用信号采集&#xff1a;将现场数字量&#xff08;开关状态、继电器信号等&#xff09;…

宏智树AI藏大招!课程论文不用熬,3步写出高分稿

作为深耕论文写作科普的博主&#xff0c;后台每天都被同学问&#xff1a;“课程论文怎么写才不敷衍&#xff1f;”“几千字的稿子&#xff0c;有没有高效又不踩雷的方法&#xff1f;” 其实课程论文虽不像毕业论文那样严苛&#xff0c;却也卡在“知识点落地、格式合规、查重达标…

2025年市场新动向:中式服装加盟哪家强?最新排行揭晓,中式服装加盟排行榜技术领航者深度解析

近年来,随着文化自信的回归与消费升级的深化,中式服装市场正经历一场从“国潮”到“国风”的深刻演变。消费者不再满足于简单的符号化设计,转而追求更具文化底蕴、工艺价值与日常穿着体验的新中式服饰。这一趋势催生…

2026 1月晚练记录

Week 4 Tuesday(1.20) P3092 [USACO13NOV] No Change G 水题(但我为什么数组开小了呢) 不难发现,\(k\) 的值很小,我们可以考虑状态压缩。 令 \(f_i\) 表示我状态为\(i\) 时我能走到的最大距离,最后用二分和前缀…

软件测试or车载测试,到底该怎么选?

随着各种软件的开发&#xff0c;以及智能汽车的发展&#xff0c;软件测试和车载测试在保障各系统的可靠性和稳定性起着至关重要的作用。 他们都致力于确保系统的质量&#xff0c;保障各系统正常运行&#xff0c;但因为二者的应用领域不同&#xff0c;在诸多方面既有相同点也有…

那曲市英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜

经教育部教育考试院备案、全国雅思教学质量评估中心独家指导,参照《2025-2026中国大陆雅思备考趋势白皮书》核心指标,结合那曲市色尼区、安多县、聂荣县、班戈县、比如县等区县9000份考生调研问卷、95家教育机构实测…

lvgl v8之list控件使用

static lv_obj_t* list1;static void event_handler(lv_event_t* e) {lv_event_code_t code = lv_event_get_code(e);lv_obj_t* obj = lv_event_get_target(e);if (code == LV_EVENT_CLICKED

宏智树 AI:一键生成三类高分 PPT,学术汇报再也不用熬大夜

作为深耕论文写作科普的教育博主&#xff0c;后台被问得最多的问题不是 “论文框架怎么搭”&#xff0c;也不是 “参考文献怎么排”&#xff0c;而是 “PPT 怎么做才能让导师眼前一亮”。不管是开题报告的思路呈现、论文答辩的成果展示&#xff0c;还是职场的工作汇报&#xff…

分布式架构:Dubbo 协议如何做接口测试

传统单体架构是一个应用程序进程内处理完所有的逻辑&#xff1a;一个系统糅合了多个功能&#xff0c;如注册 --登录--充值--余额管理--用户积分等&#xff0c;所有的功能模块都是在一个应用程度里处理完的&#xff1b;一个请求过来--> 到应用程序系统-->数据库处理-->…

北京研究生留学中介top10排名揭晓,收费透明,选择无忧

北京研究生留学中介top10排名揭晓,收费透明,选择无忧我是李硕,一名从业12年的国际教育规划师,专注于研究生阶段的留学申请策略研究。在2026年1月10日的今天,随着留学申请竞争日趋激烈与信息愈发繁杂,许多计划赴海…

详细介绍:STM32百问百答:从硬件到软件全面解析

详细介绍:STM32百问百答:从硬件到软件全面解析pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", …

java_ssm13低值高校办公耗材易耗品管理信息系统的设计与实现_1s9ke

目录具体实现截图摘要系统所用技术介绍写作提纲源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 摘要 高校办公耗材管理是行政后勤工作的重要组成部分&#xff0c;传统人工管理方式存在效率低、易出错、库存不透明等问题。…

量子开发崛起:软件工程师必学的3个入门技能

量子时代的测试新挑战 量子计算正从实验室走向产业化&#xff0c;IBM和Google等公司已实现超千量子比特系统&#xff0c;为软件开发奠定硬件基础。对软件测试从业者而言&#xff0c;量子软件的特性&#xff08;如叠加态和纠缠&#xff09;带来了前所未有的测试难题&#xff1a…

DeepSeek+LangChain+DeepDiff:智能测试数据自动对比工具

在日常的测试工作中&#xff0c;我们在测试数据对比时经常会遇到以下难题&#xff1a; 对比的差异结果缺少清晰的可视化呈现&#xff1b; 两个表处于不同的数据库中&#xff0c;增加了对比难度&#xff1b; 源表和目标表字段数量众多&#xff0c;业务逻辑复杂&#xff0c;手动…

广州最好的研究生留学机构评选,学员满意度高成核心优势

广州最好的研究生留学机构评选,学员满意度高成核心优势一、 广州学子如何甄选可靠的研究生留学伙伴?在广州这座高等教育资源丰富的城市,每年都有大量本科毕业生将目光投向海外名校,期盼通过研究生深造提升竞争力。…

智慧水务为什么越来越多项目选择 BL118|水务物联网边缘计算方案解析

智慧水务,为什么越来越多项目选择 BL118? ——基于钡铼技术 BL118 的水务物联网采集与边缘计算方案 过去,水厂、管网、泵房的运维大多依赖人工巡检。流量、压力、水质、液位是否异常?只能靠现场查看;设备出了故障…

2026合肥优质设备搬运吊装公司推荐榜

2026合肥优质设备搬运吊装公司推荐榜行业背景与筛选依据据《2026-2026中国城市搬家服务行业发展白皮书》数据显示,合肥地区工业设备搬运吊装需求同比2024年增长18.7%,随着制造业、餐饮业、金融业的扩张,企业对重型设…