微信支付 网站开发,建立网站一般多少钱,做网页素材,沈阳网站设计开发公司阅读导航 引言一、UDP协议二、UDP网络程序模拟实现1. 预备代码⭕makefile文件⭕打印日志文件⭕打开指定的终端设备文件#xff0c;并将其作为标准错误输出的目标文件描述符 2. UDP 服务器端实现#xff08;UdpServer.hpp#xff09;3. UDP 客户端实现#xff08;main函数并将其作为标准错误输出的目标文件描述符 2. UDP 服务器端实现UdpServer.hpp3. UDP 客户端实现main函数 温馨提示 引言
在前一篇文章中我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。本文将延续上文内容重点讨论简单的UDP网络程序模拟实现。通过本文的学习读者将能够深入了解UDP协议的实际应用并掌握如何编写简单的UDP网络程序。让我们一起深入探讨UDP网络程序的实现细节为网络编程的学习之旅添上一份精彩的实践经验。
一、UDP协议
UDPUser Datagram Protocol是一种无连接的、轻量级的网络传输协议它提供了快速、简单的数据传输服务。下面是一个简单的UDP程序实现示例包括一个UDP服务器和一个UDP客户端。详介绍可以看上一篇文章UDP协议介绍 | TCP协议介绍 | UDP 和 TCP 的异同
二、UDP网络程序模拟实现
1. 预备代码
⭕makefile文件
.PHONY:all
all:udpserver udpclientudpserver:Main.ccg -o $ $^ -stdc11
udpclient:UdpClient.ccg -o $ $^ -lpthread -stdc11.PHONY:clean
clean:rm -f udpserver udpclient这段代码是一个简单的 Makefile 文件用于编译 UDP 服务器udpserver和 UDP 客户端udpclient的程序。在这个 Makefile 中定义了两个规则
all表示默认的目标依赖于 udpserver 和 udpclient 目标即执行 make 命令时会编译 udpserver 和 udpclient。clean用于清理生成的可执行文件 udpserver 和 udpclient。
在 Makefile 中使用了一些特殊的关键字和变量
.PHONY声明 all 和 clean 是伪目标不是真正的文件名。$表示目标文件名。$^表示所有依赖文件列表。-stdc11指定 C 的编译标准为 C11。-lpthread链接 pthread 库用于多线程支持。
⭕打印日志文件
#pragma once#include iostream
#include time.h
#include stdarg.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include stdlib.h#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile log.txtclass Log
{
public:Log(){printMethod Screen; // 默认输出方式为屏幕打印path ./log/; // 默认日志文件存放路径}void Enable(int method){printMethod method; // 设置日志输出方式屏幕、单个文件、分类文件}std::string levelToString(int level){switch (level){case Info:return Info;case Debug:return Debug;case Warning:return Warning;case Error:return Error;case Fatal:return Fatal;default:return None;}}void printLog(int level, const std::string logtxt){switch (printMethod){case Screen:std::cout logtxt std::endl; // 屏幕打印日志信息break;case Onefile:printOneFile(LogFile, logtxt); // 将日志信息追加写入单个文件break;case Classfile:printClassFile(level, logtxt); // 将日志信息追加写入分类文件break;default:break;}}void printOneFile(const std::string logname, const std::string logtxt){std::string _logname path logname; // 构建日志文件的完整路径int fd open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // 打开文件如果文件不存在则创建if (fd 0)return;write(fd, logtxt.c_str(), logtxt.size()); // 将日志信息写入文件close(fd);}void printClassFile(int level, const std::string logtxt){std::string filename LogFile;filename .;filename levelToString(level); // 构建分类文件名例如log.txt.Debug/Warning/FatalprintOneFile(filename, logtxt); // 将日志信息追加写入分类文件}~Log(){}void operator()(int level, const char *format, ...){time_t t time(nullptr);struct tm *ctime localtime(t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), [%s][%d-%d-%d %d:%d:%d], levelToString(level).c_str(),ctime-tm_year 1900, ctime-tm_mon 1, ctime-tm_mday,ctime-tm_hour, ctime-tm_min, ctime-tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式默认部分自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), %s %s, leftbuffer, rightbuffer);printLog(level, logtxt); // 打印日志信息}private:int printMethod; // 日志输出方式std::string path; // 日志文件存放路径
};该代码实现了一个简单的日志记录类(Log)其中包括设置日志输出方式屏幕、单个文件、分类文件和打印日志信息的功能。
Log 类是一个用于记录日志的类。Enable 函数用于设置日志输出方式可以选择屏幕打印、单个文件或分类文件。printLog 函数根据设置的日志输出方式将日志信息打印到屏幕、追加写入单个文件或分类文件。printOneFile 函数用于将日志信息追加写入单个文件。printClassFile 函数用于将日志信息追加写入分类文件。levelToString 函数将日志级别转换为对应的字符串表示。operator() 函数是重载的函数调用运算符用于打印日志信息。path 是日志文件存放路径默认为./log/。printMethod 是日志输出方式默认为屏幕打印。SIZE 定义了缓冲区大小。Info、Debug、Warning、Error、Fatal 是日志级别的定义。Screen、Onefile、Classfile 是日志输出方式的定义。LogFile 是单个文件名的定义。
⭕打开指定的终端设备文件并将其作为标准错误输出的目标文件描述符
#include iostream
#include string
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h// 定义要打开的终端设备文件路径
std::string terminal /dev/pts/6;// 打开指定的终端设备文件并将其作为标准错误输出的目标文件描述符
int OpenTerminal()
{// 使用open函数以只写方式打开终端设备文件int fd open(terminal.c_str(), O_WRONLY);if(fd 0){// 如果打开终端设备文件失败则输出错误信息到标准错误输出std::cerr open terminal error std::endl;return 1; // 返回错误代码}// 将终端设备文件的文件描述符复制给标准错误输出的文件描述符// 这样标准错误输出就会重定向到指定的终端设备上dup2(fd, 2);// 如果需要在此处输出信息到标准错误输出可以使用printf等函数// 关闭文件描述符// close(fd);return 0; // 返回成功代码
}
这段代码的作用是打开一个终端设备文件 “/dev/pts/6”将其作为标准错误输出stderr的目标文件描述符实现将错误信息输出到指定的终端设备上。
terminal 变量存储了要打开的终端设备文件路径 “/dev/pts/6”。OpenTerminal 函数尝试打开指定的终端设备文件并将其作为标准错误输出的目标文件描述符。 首先使用 open 函数打开终端设备文件以只写方式O_WRONLY。如果成功打开终端设备文件则将其文件描述符复制给标准错误输出的文件描述符2即 dup2(fd, 2)这样标准错误输出就会重定向到该终端设备上。如果打开终端设备文件失败则输出错误信息到标准错误输出并返回错误代码 1。最后函数返回0表示成功。
2. UDP 服务器端实现UdpServer.hpp
#pragma once#include iostream
#include string
#include strings.h
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include functional
#include unordered_map
#include Log.hpp// 使用Log类记录日志信息
Log lg;enum {SOCKET_ERR 1,BIND_ERR
};uint16_t defaultport 8080;
std::string defaultip 0.0.0.0;
const int size 1024;class UdpServer {
public:UdpServer(const uint16_t port defaultport, const std::string ip defaultip): sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init() {// 1. 创建UDP socketsockfd_ socket(AF_INET, SOCK_DGRAM, 0); // PF_INETif (sockfd_ 0) {lg(Fatal, socket create error, sockfd: %d, sockfd_);exit(SOCKET_ERR);}lg(Info, socket create success, sockfd: %d, sockfd_);// 2. 绑定socketstruct sockaddr_in local;bzero(local, sizeof(local));local.sin_family AF_INET;local.sin_port htons(port_); // 端口号需要转换为网络字节序local.sin_addr.s_addr inet_addr(ip_.c_str()); // 将IP地址转换为网络字节序if (bind(sockfd_, (const struct sockaddr*)local, sizeof(local)) 0) {lg(Fatal, bind error, errno: %d, err string: %s, errno, strerror(errno));exit(BIND_ERR);}lg(Info, bind success, errno: %d, err string: %s, errno, strerror(errno));}void CheckUser(const struct sockaddr_in client, const std::string clientip, uint16_t clientport) {// 检查用户是否已经存在在线用户列表中auto iter online_user_.find(clientip);if (iter online_user_.end()) {online_user_.insert({clientip, client});std::cout [ clientip : clientport ] add to online user. std::endl;}}void Broadcast(const std::string info, const std::string clientip, uint16_t clientport) {// 广播消息给所有在线用户for (const auto user : online_user_) {std::string message [;message clientip;message :;message std::to_string(clientport);message ]# ;message info;socklen_t len sizeof(user.second);sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(user.second), len);}}void Run() {isrunning_ true;char inbuffer[size];while (isrunning_) {struct sockaddr_in client;socklen_t len sizeof(client);// 接收客户端发送的消息ssize_t n recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)client, len);if (n 0) {lg(Warning, recvfrom error, errno: %d, err string: %s, errno, strerror(errno));continue;}// 获取客户端的IP地址和端口号uint16_t clientport ntohs(client.sin_port);std::string clientip inet_ntoa(client.sin_addr);// 检查用户是否已经存在在线用户列表中CheckUser(client, clientip, clientport);std::string info inbuffer;// 将接收到的消息广播给所有在线用户Broadcast(info, clientip, clientport);}}~UdpServer() {if (sockfd_ 0)close(sockfd_);}private:int sockfd_; // 网络文件描述符std::string ip_; // 服务器IP地址uint16_t port_; // 服务器端口号bool isrunning_; // 服务器运行状态std::unordered_mapstd::string, struct sockaddr_in online_user_; // 在线用户列表
};Log.hpp 是用于记录日志信息的头文件。lg 是一个 Log 类的对象用于输出日志信息。enum 定义了两个错误类型SOCKET_ERR 和 BIND_ERR分别表示 socket 创建错误和绑定错误。defaultport 和 defaultip 分别设置默认的端口号和 IP 地址。size 定义接收缓冲区的大小为 1024 字节。UdpServer 类封装了一个 UDP 服务器。构造函数 UdpServer 接受端口号和 IP 地址作为参数并初始化成员变量。Init 函数用于初始化 UDP 服务器其中 创建 UDP socket并检查创建是否成功。绑定 socket 到指定的 IP 地址和端口号并检查绑定是否成功。 CheckUser 函数用于检查用户是否已经存在在线用户列表中如果不存在则将其添加到列表中。Broadcast 函数用于向所有在线用户广播消息其中 消息格式为 [发送者IP:发送者端口号]# 消息内容。使用 sendto 函数发送消息给每个在线用户。 Run 函数是 UDP 服务器的主循环其中 循环接收客户端发送的消息并将其广播给所有在线用户。对每个客户端获取其 IP 地址和端口号并进行用户检查和消息广播。 ~UdpServer 析构函数关闭网络文件描述符。sockfd_ 是网络文件描述符用于创建和管理网络连接。ip_ 是服务器的 IP 地址。port_ 是服务器的端口号。isrunning_ 表示服务器的运行状态用于控制循环退出。online_user_ 是一个无序映射用于保存在线用户的 IP 地址和对应的 sockaddr_in 结构体。
3. UDP 客户端实现main函数
#include iostream
#include cstdlib
#include unistd.h
#include strings.h
#include string.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include pthread.h
#include Terminal.hppusing namespace std;// 函数声明打印程序的使用方法
void Usage(std::string proc);// 结构体用于传递线程参数
struct ThreadData
{struct sockaddr_in server; // 服务器地址结构体int sockfd; // socket 文件描述符std::string serverip; // 服务器 IP 地址
};// 线程函数接收消息
void *recv_message(void *args);// 线程函数发送消息
void *send_message(void *args);// 主函数
int main(int argc, char *argv[])
{if (argc ! 3){Usage(argv[0]); // 打印使用方法exit(0);}// 解析命令行参数std::string serverip argv[1]; // 服务器 IP 地址uint16_t serverport std::stoi(argv[2]); // 服务器端口号// 初始化 ThreadData 结构体struct ThreadData td;bzero(td.server, sizeof(td.server)); // 清零服务器地址结构体td.server.sin_family AF_INET; // 设置地址族为 IPv4td.server.sin_port htons(serverport); // 设置端口号转换为网络字节序td.server.sin_addr.s_addr inet_addr(serverip.c_str()); // 设置服务器 IP 地址转换为网络字节序// 创建 UDP sockettd.sockfd socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd 0){cout socket error endl;return 1;}td.serverip serverip; // 存储服务器 IP 地址pthread_t recvr, sender; // 定义接收消息和发送消息的线程pthread_create(recvr, nullptr, recv_message, td); // 创建接收消息线程pthread_create(sender, nullptr, send_message, td); // 创建发送消息线程// 等待接收消息和发送消息的线程退出pthread_join(recvr, nullptr);pthread_join(sender, nullptr);close(td.sockfd); // 关闭 socketreturn 0;
}// 函数实现打印程序的使用方法
void Usage(std::string proc)
{std::cout \n\rUsage: proc serverip serverport\n std::endl;
}// 线程函数实现接收消息
void *recv_message(void *args)
{ThreadData *td static_castThreadData *(args); // 强制类型转换为 ThreadData 结构体指针char buffer[1024]; // 接收消息的缓冲区while (true){memset(buffer, 0, sizeof(buffer)); // 清空缓冲区struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(td-sockfd, buffer, 1023, 0, (struct sockaddr *)temp, len); // 接收消息if (s 0){buffer[s] 0;cerr buffer endl; // 输出接收到的消息}}
}// 线程函数实现发送消息
void *send_message(void *args)
{ThreadData *td static_castThreadData *(args); // 强制类型转换为 ThreadData 结构体指针string message; // 存储用户输入的消息socklen_t len sizeof(td-server); // 服务器地址的长度// 发送欢迎消息std::string welcome td-serverip comming...;sendto(td-sockfd, welcome.c_str(), welcome.size(), 0, (struct sockaddr *)(td-server), len);while (true){cout Please Enter ;getline(cin, message); // 获取用户输入的消息sendto(td-sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)(td-server), len); // 发送消息给服务器}
}
温馨提示
感谢您对博主文章的关注与支持如果您喜欢这篇文章可以点赞、评论和分享给您的同学这将对我提供巨大的鼓励和支持。另外我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新不要错过任何精彩内容
再次感谢您的支持和关注。我们期待与您建立更紧密的互动共同探索Linux、C、算法和编程的奥秘。祝您生活愉快排便顺畅