Linux网络套接字编程——UDP服务器

ops/2025/3/15 21:07:20/

Linux网络套接字编程——创建并绑定-CSDN博客

前面已经介绍了网络套接字的创建和绑定,这篇文章会通过UDP套接字实现一个UDP服务器

先介绍将使用的接口。

recvfrom

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

第一个表示从哪个套接字获取信息,第二个是缓冲区,len是缓冲区长度,数据就会被读到缓冲区中,flags可直接填0。

src_addr是输出型参数,将会返回是哪个客户端发送的信息,addrlen指向src_addr结构的结构体大小,若这两个参数为NULL,则不获取客户端的相关信息。

使用这个函数时,需要自己定义sockaddr_in结构体,并且定义一个变量为sockaddr_in的大小。

然后将其传入,以获得客户端的IP地址和端口。

返回值为实际收到的字节数,出现错误则返回-1。

sendto

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

sockfd是服务器绑定的套接字的文件描述符,buf是你将要发送的信息,len为buf的长度,flags设为0, dest_addr表示要发消息给谁,addrlen是传入的结构体的长度。

返回值为发送的字节数,出现错误则返回-1。

服务器代码

现在已经可以实现一个UDP服务器

#include <iostream>
#include <string>
#include <sys/types.h>
#include <cstdlib>
#include <sys/socket.h>
#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>
//服务器将绑定的IP地址
const std::string IP = "0.0.0.0";
//缓冲区的大小
const int MAXBUFSIZE = 1024;
//服务器对发送过来的信息进行何种处理后返回处理后的信息
using func_t = std::function<std::string(const std::string&)>;
class server
{
public://创建并绑定套接字server(uint16_t port, func_t handle, std::string ip = IP);//服务器运行,并且接收信息,向客户端返回处理后的信息void run();
private://服务器如何处理收到的信息,由程序员自己定义func_t _handle;//打开的套接字的文件描述符int _sock_fd;//服务器是否运行bool running;//服务器绑定的端口uint16_t _port;//服务器绑定的IP地址std::string _ip;
};

这里解释一下0.0.0.0IP地址,它意味着服务器绑定本机上的所有网络接口,一般服务器主机会有不止一块网卡,需要将所有网络接口都绑定到服务器上,而不是某一个IP地址。

下面给出服务器初始化的代码

    server(uint16_t port, func_t handle, std::string ip = IP):_port(port),_ip(ip){running = false;_handle = handle;//创建UDP套接字_sock_fd = socket(AF_INET,SOCK_DGRAM,0);if(_sock_fd < 0){std::cout<<"socket error"<<std::endl;exit(1);}//绑定套接字sockaddr_in svr_addr;svr_addr.sin_family = AF_INET;//将端口由主机字节序转为网络字节序svr_addr.sin_port = htons(_port);//将字符串风格的IP地址转为网络字节序的32位svr_addr.sin_addr.s_addr = inet_addr(_ip.c_str());socklen_t len = sizeof(svr_addr);//由于bind的参数是sockaddr*类型,这里需要强转int err = bind(_sock_fd, (sockaddr*)&svr_addr, len);if(err != 0){std::cout<<"bind error"<<std::endl;exit(1);}}

 然后是服务器运行的代码

    void run(){running = true;//保存客户端的IP地址和端口号,以便于将处理后的信息发给他sockaddr_in client_addr;//用于接收数据的缓冲区char buf[MAXBUFSIZE];std::cout << "Server running..." << std::endl;while(running){memset(buf, 0, MAXBUFSIZE);socklen_t len = sizeof(client_addr);ssize_t recv_size = recvfrom(_sock_fd, buf, sizeof(buf) - 1,0,(sockaddr*)&client_addr, &len);client_addr.sin_port = ntohs(client_addr.sin_port);client_addr.sin_addr.s_addr = ntohl(client_addr.sin_addr.s_addr);if(recv_size < 0){std::cout<<"recv error"<<std::endl;continue;}//程序员自定义的处理数据函数std::string ret = _handle(std::string(buf));std::cout << ret << std::endl;memset(buf, 0, MAXBUFSIZE);//向客户端发送处理后的信息ssize_t send_size = sendto(_sock_fd, ret.c_str(), ret.size(), 0, (sockaddr*)&client_addr, len);if(send_size < 0){std::cout<<"send error"<<std::endl;continue;}}}

然后是main函数

//服务器程序像这样使用 ./server [port]
//server是我自己的可执行程序名称,你可以定义自己的
void Usage()
{std::cout<< "Usage: " << "./server" << " port"<<std::endl;
}
//只是简单的将信息回显
std::string handle(const std::string& s)
{return "Server recv:" + s;
}int main(int argc,char* argv[])
{if(argc != 2){Usage();return 1;}uint16_t port = atoi(argv[1]);server svr(port, handle);svr.run();return 0;
}

结合前面的讲解,这些代码应该可以理解,如果还不理解,建议自己动手敲一遍,百分百能理解。

只有服务端还不够完整,不能直观的看到运行效果,下面再给出客户端代码

客户端代码

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>const int MAXBUFSIZE = 1024;
class client
{
public:client();void SendTo(const sockaddr_in& server_addr, const std::string& msg);private:int _sock_fd;
};

客户端初始化

    client(){_sock_fd = socket(AF_INET,SOCK_DGRAM,0);if(_sock_fd < 0){std::cerr<<"socket error"<<std::endl;exit(1);}}

客户端发送消息

    void SendTo(const sockaddr_in& server_addr, const std::string& msg){int ret = sendto(_sock_fd, msg.c_str(), msg.size(), 0, (sockaddr*)&server_addr, sizeof(server_addr));if(ret < 0){std::cerr<<"sendto error"<<std::endl;exit(2);}}

 main函数

void Usage()
{std::cout<< "Usage: " << "./server" << " port"<<std::endl;
}std::string handle(const std::string& s)
{return "Server recv:" + s;
}
int main(int argc,char* argv[])
{if(argc != 2){Usage();return 1;}uint16_t port = atoi(argv[1]);server svr(port, handle);svr.run();return 0;
}

有了服务器的编写经验,客户端代码应该很容易看懂,你也可以自己写一个运行在电脑上看效果。

你可以使用本地回环地址 127.0.0.1对代码进行测试,用法如下,假设端口是8080

./client 127.0.0.1 8080 


http://www.ppmy.cn/ops/166040.html

相关文章

2011-2020年 全国省市县-数字普惠金融指数数字经济指数绿色金融指数县域数字乡村指数

2011-2020 全国省市县-数字普惠金融指数&数字经济指数&绿色金融指数&县域数字乡村指数https://download.csdn.net/download/2401_84585615/90214687 https://download.csdn.net/download/2401_84585615/90214687 在2011年至2020年期间&#xff0c;中国各省、市、县…

Java---JavaSpringMVC解析(1)

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC" 1.MVC MVC是Model View Controller的缩写&#…

数组的介绍

1.数组的概念 数组是一组相同类型元素的集合&#xff0c;从这个描述中我们知道&#xff1a; 数组中存放1个或多个数据&#xff0c;但是数组的元素个数不为0。数组中存放的多个数据&#xff0c;类型是相同的。 数组分为一维数组和多维数组&#xff0c;多维数组一般比较多见的…

大模型微调中warmup(学习率预热)是什么

大模型微调中warmup(学习率预热)是什么 在大模型微调中,添加warmup(学习率预热)是指在训练初期逐步增加学习率,避免直接使用高学习率导致参数震荡。 🔧 为什么需要warmup? 大模型参数敏感:预训练模型的参数已接近最优,初期用大学习率可能剧烈扰动参数(如“急刹车…

3.JVM-内部结构

1.栈结构 1.1 动态链接 栈中的对象指向堆中的实际引用 符号引用: 比如一个类的名称 直接引用: 具体堆中数据信息 1.2 方法返回 栈中上一层的结果和下一层的指令 1.3 操作数栈 1.4 局部变量 该线程中需要的变量 2. 虚拟机栈是如何工作 程序计数器:存当前执行到那一步 操作…

手写一个Tomcat

Tomcat 是一个广泛使用的开源 Java Servlet 容器&#xff0c;用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂&#xff0c;但通过手写一个简易版的 Tomcat&#xff0c;我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat&#xff0c;并深…

绝美焦糖暖色调复古风景画面Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 通过 Lr 软件丰富的工具和功能&#xff0c;对风景照片在色彩、影调等方面进行调整。例如利用基本参数调整选项&#xff0c;精准控制照片亮度、对比度、色温、色调等基础要素&#xff1b;运用 HSL 面板可对不同色彩的色相、饱和度以及明亮度进行单独调节&#xff1b;利…

3.angular表单验证

更多用法参考&#xff1a;https://www.jb51.net/article/97552.htm $valid/$invalid // 表单验证通过/表单验证不通过, true/false$pristine/$dirty // 表单验证值是否是初始值 $pristine 初始值, $dirty就是只要改变就为true$error // 验证信息 有错误的才展示里面true 上面…