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

server/2025/3/15 19:48:14/

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/server/175244.html

相关文章

电脑突然没有声音的可能原因与应对方法

电脑突然没有声音&#xff0c;可能由多种原因引起。以下是一些可能的原因及相应的应对方法&#xff1a; 可能原因 音量设置问题&#xff1a; 系统音量被意外调低或设置为静音。特定应用程序的音量被调低或设置为静音。 音频设备连接问题&#xff1a; 外部音频设备&#xff08;…

百度百科更新!树莓集团宜宾项目的深远影响与意义

百度百科对树莓集团宜宾项目的更新&#xff0c;让我们更清晰地认识到该项目的深远影响与意义。 从产业发展角度看&#xff0c;树莓集团宜宾项目带动了当地数字产业的蓬勃发展。通过建设产业园区&#xff0c;吸引了大量数字企业集聚&#xff0c;形成了完整的数字经济产业链。从…

Pytest自动化测试框架pytest-xdist分布式测试插件

平常我们功能测试用例非常多时&#xff0c;比如有1千条用例&#xff0c;假设每个用例执行需要1分钟&#xff0c;如果单个测试人员执行需要1000分钟才能跑完&#xff1b; 当项目非常紧急时&#xff0c;会需要协调多个测试资源来把任务分成两部分&#xff0c;于是执行时间缩短一…

C++11多线程,锁与条件变量

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;C学习笔记 欢迎大家点赞收藏评论&#x1f60a; 目录 线程库1. thread类的简单介绍1.1constructor构造函数1.2 线程函数参数2. 原子性操作库(atomic)3. lock_guard与unique_lock3.1 mutex的种类3.…

sublime text 中添加copilot

截至我的知识更新时间&#xff08;2023年之前&#xff09;&#xff0c;Sublime Text 原生并不支持类似于 GitHub Copilot 的代码自动完成功能。GitHub Copilot 是一个由 GitHub 开发的基于 OpenAI Codex 的代码自动完成工具&#xff0c;它通过机器学习模型来帮助开发者更快地编…

嵌入式硬件: GPIO与二极管基础知识详解

1. 前言 在嵌入式系统和硬件开发中&#xff0c;GPIO&#xff08;通用输入输出&#xff09;是至关重要的控制方式&#xff0c;而二极管作为基础电子元件&#xff0c;广泛应用于信号整流、保护电路等。本文将从基础原理出发&#xff0c;深入解析GPIO的输入输出模式&#xff0c;包…

计算机网络-网络存储技术

分类 直连式存储&#xff08;DAS&#xff09;&#xff08;SAS&#xff09;&#xff1a; 也叫服务器附加存储&#xff0c;将存储设备通过I/O设备&#xff08;一般常用SCSI&#xff09;附加在服务器上。本身是硬件的堆叠 无法跨平台进行存储 网络附加存储&#xff08;NAS&…

ADMM(交替方向乘子法详解与实现)

ADMM&#xff08;Alternating Direction Method of Multipliers&#xff0c;交替方向乘子法&#xff09;是一种强大的优化算法&#xff0c;广泛用于解决约束优化问题&#xff0c;尤其是在机器学习、大规模数据分析和信号处理领域。以下是对ADMM迭代算法的详细解析&#xff0c;包…