linux socket编程之udp(实现客户端和服务端消息的发送和接收)

目录

一.创建socket套接字(服务器端)

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

2.2bind绑定端口

三.直接通信(服务器端)

3.1接收客户端发送的消息

3.2给客户端发送消息

四.客户端通信

4.1创建socket套接字

4.2客户端bind问题

4.3直接通信即可

4.3.1构建目标主机的socket信息

4.3.2给服务端发送消息

4.3.3.接收服务端发送过来的消息

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

6.2UdpClient.cc

6.3InetAddr.hpp

6.4LockGuard.hpp

6.5Log.hpp

6.6main.cc

6.7makefile


一.创建socket套接字(服务器端)

int socket(int domain, int type, int protocol);

domain:选择你要使用的网络层协议 一般是ipv4,也就是AF_INET

type:选择你要使用的应用层协议,这里我们选择udp,也就是SOCK_DGRAM

protocol:这里我们先设置成0

成功返回文件描述符,失败返回-1

//1.创建socket套接字
_socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(_socketfd < 0)
{LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);
}

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

uint16_t htons(uint16_t hostshort);//将端口号从主机序列转成网络序列
in_addr_t inet_addr(const char *cp);//将ip从主机序列转成网络序列 + 字符串风格ip转成点分十进制ip
uint16_t ntohs(uint16_t netshort);//将端口号从网络序列转成主机序列
char *inet_ntoa(struct in_addr in);//将ip从网络序列转成主机序列 + 点分十进制ip转成字符串风格ip

网络通信:struct sockaddr_in

本地通信:sockaddr_un

16位地址类型表明了他们是网络通信还是本地通信 

16位地址类型:sin_family

16位端口号:sin_port

32位ip地址:sin_addr.s_addr

//填充sockaddr_in结构
struct sockaddr_in local;
local.sin_family = AF_INET;//表明是网络通信
local.sin_port = htons(_prot);//将主机序列转成网络序列
local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列

2.2bind绑定端口

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd:要绑定的socket套接字的文件描述符

struct sockaddr *:包含ip地址+端口号的结构体(类型不一样需要进行强转)

socklen_t addrlen:sockaddr_in结构体的大小

//bind绑定端口号
int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddr
if(n < 0)
{LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);
}

三.直接通信(服务器端)

3.1接收客户端发送的消息

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:套接字的文件描述符

buf:缓存区

len:缓存区的大小,单位是字节

flags:暂时设置为0

src_addr:数据来自于哪                                

addrlen:struct sockaddr结构体的大小

//接收客户端发送来的消息
char buffer[1024];
sockaddr_in peer;
socklen_t len = sizeof(peer);
int n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(sockaddr*)&peer,&len);  

3.2给客户端发送消息

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd:套接字的文件描述符

buf:缓存区

len:缓存区的大小,单位是字节

flags:设置为0

src_addr:要将数据发送给谁

addrlen:struct sockaddr结构体的大小

//处理客户端发送的消息,并且将结果返回给客户端
buffer[n] = 0;
sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);

四.客户端通信

4.1创建socket套接字

// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{std::cerr << "socket error" << std::endl;
}

4.2客户端bind问题

客户端不需要显示的bind,os会自动帮你绑定端口号!!!

试想一下,你的手机上有抖音和微信两个客户端小程序,如果抖音客户端bind了8080这个端口,微信也想要bind8080这个端口,那么这时候就会出现一个问题,一个端口号被两个进程竞争!!!结果就是,抖音和微信不可能同时启动。

所以解决方法就是:udp client首次发送数据的时候,OS会自己自动随机的给client进行bind

4.3直接通信即可

4.3.1构建目标主机的socket信息

struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());

4.3.2给服务端发送消息

//给服务端发送消息
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

4.3.3.接收服务端发送过来的消息

//接收服务端发送过来的消息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;
}

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"enum 
{SOCKET_ERROR = 1,//创建套接字失败BIND_ERROR,//bind绑定端口失败USAGE_ERROR//启动udp服务失败
};int default_socketfd = -1;
class UdpServer
{
public:UdpServer(uint16_t prot): _socketfd(default_socketfd),_prot(prot){}   ~UdpServer(){}void Init(){//1.创建socket套接字_socketfd = socket(AF_INET,SOCK_DGRAM,0);if(_socketfd < 0){LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _socketfd);//2.bind 将socket套接字和端口号进行绑定//填充sockaddr_in结构struct sockaddr_in local;local.sin_family = AF_INET;//表明是网络通信local.sin_port = htons(_prot);//将主机序列转成网络序列local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列//local.sin_addr.s_addr = INADDR_ANY;//bind绑定端口号int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddrif(n < 0){LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Stat(){//3.直接开始通信while(true){//接收客户端发送来的消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);           ssize_t n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);  if(n > 0){InetAddr addr(peer);LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);//处理客户端发送的消息,并且将结果返回给客户端buffer[n] = 0;sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}}private:uint16_t _prot;int _socketfd;
};

6.2UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候// 构建目标主机的socket信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());std::string message;// 2. 直接通信即可while(true){//给服务端发送消息std::cout << "Please Enter# ";std::getline(std::cin, message);std::cout<<message<<std::endl;sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));//接收服务端发送过来的消息struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

6.3InetAddr.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

6.4LockGuard.hpp

#include <iostream>
#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);// 析构释放锁}
private:pthread_mutex_t *_mutex;
};

6.5Log.hpp

#pragma once
#include <cstdio>
#include <iostream>
#include <string>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <fstream>#include "LockGuard.hpp"bool IsSave = false;//是否向文件中写入
const std::string logname = "log.txt";//日志信息写入的文件路径// 日志是有等级的
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 将日志的登记由整形转换为字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}// 获取时间
std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None"; // 没有获取成功char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900, // 这里的year是减去1900之后的值,需要加上1900format_time->tm_mon + 1,     // 这里的mon是介于0-11之间的,需要加上1format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}//将日志信息写入到文件中
void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义锁 支持多线程
// 日志是有格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;//定义一个void* 指针va_start(arg, format);//初始化指针,将指针指向可变参数列表开始的位置vsnprintf(buffer, sizeof(buffer), format, arg);//将可变参数列表写入到buffer中va_end(arg);//将指针置空std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";LockGuard lockguard(&lock);if (!issave){std::cout << message;//将日志信息打印到显示器中}else{SaveFile(logname, message);//将日志信息写入到文件}
}// C99新特性__VA_ARGS__
#define LOG(level, format, ...)  do{ LogMessage(__FILE__, __LINE__,IsSave,level, format, ##__VA_ARGS__); }while(0)
#define EnableFile()    do{ IsSave = true; }while(0)
#define EnableScreen()  do{ IsSave = false; }while(0) 

6.6main.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->Init();usvr->Stat();return 0;
}

6.7makefile

.PHONY:all
all:udpserver udpclient
udpclient:UdpClient.ccg++ -o udpclient UdpClient.cc -std=c++14
udpserver:main.ccg++ -o udpserver main.cc -std=c++14
.PHONY:clean
clean:rm -f udpserver

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

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

相关文章

第1期:Python基础语法入门

1.1 Python简介 Python是一种解释型、面向对象、动态数据类型的高级编程语言。它设计简洁&#xff0c;易于学习&#xff0c;适合初学者。Python广泛应用于数据科学、人工智能、Web开发、自动化脚本等领域。它的语法简洁易懂&#xff0c;强调代码的可读性。 1.2 安装Python与配…

使用EXCEL绘制平滑曲线

播主播主&#xff0c;你都多少天没更新了&#xff01;&#xff01;&#xff01;泥在干什么&#xff1f;你还做这个账号麻&#xff1f;&#xff01;&#xff01;&#xff01; 做的做的&#xff08;哭唧唧&#xff09;&#xff0c;就是最近有些忙&#xff0c;以及…… 前言&…

当算力遇上马拉松:一场科技与肉身的极限碰撞

目录 一、从"肉身苦修"到"科技修仙" 二、马拉松的"新大陆战争" 三、肉身会被算法"优化"吗? 马拉松的下一站是"人机共生"时代 当AI能预测你的马拉松成绩,算法能规划最佳补给方案,智能装备让训练效率翻倍——你还会用传…

MLLMs for TSAD ?

项目链接:Multimodal LLMs Advance Time Series Analysis 代码链接:https://github.com/mllm-ts/VisualTimeAnomaly 出处:ICLR 2025 一 文章动机 多模态 LLM (MLLM) 通过 “视觉” 方式处理时序的潜力仍未充分探索; 人类检测 “时序异常” 的自然方式:可视化、文本描…

开发基于python的商品推荐系统,前端框架和后端框架的选择比较

开发一个基于Python的商品推荐系统时&#xff0c;前端和后端框架的选择需要综合考虑项目需求、开发效率、团队熟悉度以及系统的可扩展性等因素。 以下是一些推荐的框架和建议&#xff1a; 后端框架 Flask 优点&#xff1a; 轻量级&#xff1a;Flask的核心非常简洁&#xff0c;…

chili3d调试笔记2+添加web ui按钮

onclick 查找 打个断点看看 挺可疑的&#xff0c;打个断点看看 挺可疑的&#xff0c;打个断点看看 打到事件监听上了 加ui了 加入成功 新建弹窗-------------------------------------- 可以模仿这个文件&#xff0c;写弹窗 然后在这里注册一下&#xff0c;外部就能调用了 对了…

【重学Android】1.关于@Composer注解的一点知识笔记

最新因为一些原因&#xff0c;开始重新学习Android及kotlin编程&#xff0c;也觉得可以顺带记录下这个过程中的一些知识点&#xff0c;也可以用作日后自己查找复习。 Composable 注解在 Android 开发中的使用 Composable 是 Jetpack Compose&#xff08;Android 的现代声明式…

qt+mingw64+cmake+libqrencode项目编译和搭建成功记录

最近要使用高拍仪拍照获取照片&#xff0c;然后识别照片中的二维码数据、使用QZxing只能识别出一个条码、另外一个条码准备测试用其他的开源项目&#xff08;如libqrencode-4.1.1&#xff09;来进行测试&#xff0c;故进行本文的项目环境搭建测试&#xff0c;最后成功。 本机开…

【今日三题】判断是不是平衡二叉树(递归) / 最大子矩阵(二维前缀和) / 小葱的01串(滑动窗口)

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;每日两三题 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 判断是不是平衡二叉树(递归)最大子矩阵(二维前缀和)小葱的01串(滑动窗口) 判断是不是平衡二叉树(递归) 判断是不是平衡二叉…

【Linux】线程ID、线程管理、与线程互斥

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f310; C 语言 上篇文章&#xff1a; 【Linux】线程&#xff1a;从原理到实战&#xff0c;全面掌握多线程编程&#xff01;-CSDN博客 下…

定制一款国密浏览器(10):移植SM2算法前,解决错误码的定义问题

上一章中,我给大家介绍了 SM4 在 BoringSSL 上的移植要点,本来计划本章介绍 SM2 算法的移植要点。在移植 SM2 过程中,遇到了一个拦路虎,所以先扫除这个拦路虎,这就是错误码的定义问题。 在铜锁中,引入了几个错误码和错误字符串,在文件 sm2_err.c 中: static const ER…

JDOM处理XML:Java程序员的“乐高积木2.0版“

各位代码建筑师们&#xff01;今天我们要玩一款比原生DOM更"Java友好"的XML积木套装——JDOM&#xff01;它像乐高得宝系列&#xff08;Duplo&#xff09;一样简单易用&#xff0c;却能让你的XML工程稳如霍格沃茨城堡&#xff01;&#xff08;温馨提示&#xff1a;别…

【后端开发】Spring日志

文章目录 Spring日志日志作用日志测试日志信息日志级别日志配置配置日志级别日志持久化日志文件分割 注解的使用 Spring日志 日志作用 系统监控&#xff1a;可以通过日志记录这个系统的运行状态&#xff0c;对数据进行分析&#xff0c;设置不同的规则&#xff0c;超过阈值时进…

探索大语言模型(LLM):Transformer 与 BERT从原理到实践

Transformer 与 BERT&#xff1a;从原理到实践 前言一、背景介绍二、核心公式推导1. 注意力机制&#xff08;Attention Mechanism&#xff09;2. 多头注意力机制&#xff08;Multi-Head Attention&#xff09;3. Transformer 编码器&#xff08;Transformer Encoder&#xff09…

计算机网络八股——HTTP协议与HTTPS协议

目录 HTTP1.1简述与特性 1. 报文清晰易读 2. 灵活和易于扩展 3. ⽆状态 Cookie和Session 4. 明⽂传输、不安全 HTTP协议发展过程 HTTP/1.1的不足 HTTP/2.0 HTTP/3.0 HTTPS协议 HTTP协议和HTTPS协议的区别 HTTPS中的加密方式 HTTPS中建立连接的方式 前言&#xff…

QML中的3D功能--入门开发

Qt Quick 提供了强大的 3D 功能支持,主要通过 Qt 3D 模块实现。以下是 QML 中开发 3D 应用的全面指南。 1. 基本配置 环境要求 Qt 5.10 或更高版本(推荐 Qt 6.x) 启用 Qt 3D 模块 支持 OpenGL 的硬件 项目配置 在 .pro 文件中添加: QT += 3dcore 3drender 3dinput 3dex…

Git合并分支的两种常用方式`git merge`和`git cherry-pick`

Git合并分支的两种常用方式git merge和git cherry-pick 写在前面1. git merge用途工作方式使用git命令方式合并使用idea工具方式合并 2. git cherry-pick用途工作方式使用git命令方式合并使用idea工具方式合并 3. 区别总结 写在前面 一般我们使用git合并分支常用的就是git mer…

Web三漏洞学习(其三:rce漏洞)

靶场&#xff1a;NSSCTF 三、RCE漏洞 1、概述 在Web应用开发中会让应用调用代码执行函数或系统命令执行函数处理&#xff0c;若应用对用户的输入过滤不严&#xff0c;容易产生远程代码执行漏洞或系统命令执行漏洞 所以常见的RCE漏洞函数又分为代码执行函数和系统命令执行函数…

从零开始:Python运行环境之VSCode与Anaconda安装配置全攻略 (1)

从零开始&#xff1a;Python 运行环境之 VSCode 与 Anaconda 安装配置全攻略 在当今数字化时代&#xff0c;Python 作为一种功能强大且易于学习的编程语言&#xff0c;被广泛应用于数据科学、人工智能、Web 开发等众多领域。为了顺利开启 Python 编程之旅&#xff0c;搭建一个稳…

从FPGA实现角度介绍DP_Main_link主通道原理

DisplayPort&#xff08;简称DP&#xff09;是一个标准化的数字式视频接口标准&#xff0c;具有三大基本架构包含影音传输的主要通道&#xff08;Main Link&#xff09;、辅助通道&#xff08;AUX&#xff09;、与热插拔&#xff08;HPD&#xff09;。 Main Link&#xff1a;用…