UDP Echo Server(学习版)

一、核心功能说明

UDP Echo Server(UDP 回显服务器)是 UDP 网络编程的一个基础案例,核心逻辑是:服务器绑定指定端口后,持续接收客户端发送的 UDP 数据报,然后将接收到的数据原封不动地回传给客户端。

二、前置知识

2.1 创建UDP套接字

进行 UDP 通信前,需通过socket()系统调用创建套接字

c 复制代码
#include <sys/types.h> 
#include <sys/socket.h> 
int socket(int domain, int type, int protocol);

参数说明

  • domain:套接字域,使用AF_INET(粗体强调)表示 IPv4 网络通信。
  • type:套接字类型,使用SOCK_DGRAM(粗体强调)表示面向数据报的 UDP 协议(无连接、不可靠、有最大数据长度限制)。
  • protocol:指定具体协议,UDP 通信直接填0即可(由前两个参数确定 UDP 协议)。

返回值

  • 成功:返回套接字文件描述符(fd)。
  • 失败:返回-1,并设置错误码。

常用调用示例

c 复制代码
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
2.2 绑定端口(bind)

服务器必须将套接字与「IP + 端口」绑定,否则客户端无法定位到该服务进程。

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

参数说明

  • sockfd:创建好的 UDP 套接字文件描述符。
  • addr:指向struct sockaddr_in(IPv4)的指针,需提前填充 IP 和端口信息。
  • addrlenaddr结构体的长度(如sizeof(struct sockaddr_in))。

返回值

  • 成功:返回0
  • 失败:返回-1,并设置错误码。
2.3 接收数据(recvfrom)

UDP 是面向数据报的协议,不能使用普通文件的read()/write(),需用recvfrom()接收数据,同时能获取发送方的地址信息。

c 复制代码
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);

参数说明

  • sockfd:UDP 套接字文件描述符;
  • buf:输出型参数,存储接收的数据;
  • len:缓冲区大小(建议填sizeof(buf)-1,预留字符串结束符位置);
  • flags:读取方式,默认填0(阻塞读取);
  • src_addr:输出型参数,存储发送方的 IP 和端口信息;
  • addrlen:输入输出型参数,需先初始化为src_addr的长度。

返回值

  • 成功:返回实际接收的字节数;
  • 失败:返回-1,并设置错误码。
2.4 发送数据(sendto)

recvfrom()对应,UDP 通过sendto()发送数据,需指定接收方的地址信息。

c 复制代码
#include <sys/types.h> 
#include <sys/socket.h> 
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);

参数说明

  • 前 4 个参数与recvfrom()一致。
  • dest_addr:接收方的 IP 和端口信息(可直接复用recvfrom()获取的src_addr)。
  • addrlendest_addr结构体的长度。

返回值

  • 成功:返回实际发送的字节数。
  • 失败:返回-1,并设置错误码。

三、完整源代码

3.1 地址封装类:InetAddr.hpp

该文件封装了 IPv4 地址结构体sockaddr_in,简化了 IP 和端口的解析、获取操作,避免重复编写地址转换逻辑。

c++ 复制代码
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.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;
};
3.2 UDP 服务器核心类:UdpServer.hpp

该文件是 UDP 回显服务器的核心实现,包含套接字创建、端口绑定、数据接收与回显的完整逻辑。

c++ 复制代码
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "InetAddr.hpp"

enum
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    USAGE_ERROR
};
const static int defaultfd=-1;
class UdpServer
{
public:
    UdpServer(uint16_t port)
    :_sockfd(defaultfd)
    ,_port(port)
    ,_isrunning(false)
    {}
    void InitServer()
    {
        // 1.创建UDP套接字
        _sockfd=socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd<0)
        {
            std::cout<<"socket create error!"<<std::endl;
            exit(SOCKET_ERROR);
        }
        std::cout<<"socket create success, sockfd:"<<_sockfd<<std::endl;

        // 2.填充sockaddr_in结构
        struct 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(字符串)转换成四字节ip,再把四字节ip转换成网络序列
        local.sin_addr.s_addr=INADDR_ANY;

        // 2.bind sockfd和网络信息(ip+port)
        int n=bind(_sockfd, (struct sockaddr*)&local, sizeof(local));   // 成功返回0,失败返回-1
        if(n<0)
        {
            std::cout<<"bind error!"<<std::endl;
            exit(BIND_ERROR);
        }
        std::cout<<"socket bind success!"<<std::endl;
    }
    void Start()
    {
        _isrunning=true;
        while(_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer); // 此处必须初始化为peer结构体的长度,表明为sockaddr_in结构
            // 1.先让server能接数据
                // upd是面向数据报的
            ssize_t n=recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            if(n>0)
            {
                buffer[n]=0;
                InetAddr addr(peer);

                std::cout<<"get a message:"<<buffer<<", from ip:["<<addr.Ip()<<":"<<addr.Port()<<"]"<<std::endl;
                // 2.再让server收到的数据,发给对方
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);
            }

        }
    }
    ~UdpServer()
    {}
private:
    int _sockfd;
    // std::string _ip;
    uint16_t _port; // 端口号
    bool _isrunning;
};
3.3 服务器入口程序:Main.cc

该文件是 UDP 服务器的启动入口,负责处理命令行参数、创建服务器实例并启动核心服务逻辑。

c++ 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

// ./udpserver ip port
int main(int argc, char* argv[])
{
    if(argc!=2)
    {
        std::cout<<"usage:./udpserver [ip] [port]"<<std::endl;
        exit(USAGE_ERROR);
    }
    uint16_t port=std::stoi(argv[1]);
    // C++14语法
    std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port);

    usvr->InitServer();
    usvr->Start();

    return 0;
}
3.4 UDP 客户端程序:UdpClient.cc

该文件实现了 UDP 客户端的核心功能,能够向指定服务器发送消息,并接收服务器的回显数据。

c++ 复制代码
#include <iostream>
#include <string>
#include <cstring>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

enum
{
    USAGE_ERROR=1
};
// ./udpclient serverip serverport
int main(int argc, char*argv[])
{
    if(argc!=3)
    {
        std::cout<<"usage:./udpclient [serverip] [serverport]"<<std::endl;  
        exit(USAGE_ERROR);
    }
    std::string serverip=argv[1];
    uint16_t serverport=std::stoi(argv[2]);

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

    // 2.client需不需要bind呢?client也有自己的ip和port,不需要显示的bind
    // udp client首次发送数据的时候,OS会自己自动的给client进行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;
    while(true)
    {
        std::cout<<"Please Enter# ";
        std::getline(std::cin, message);
        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;   
}
3.5 编译脚本:makefile

该文件是自动化编译脚本,支持一键编译服务器和客户端程序,同时提供清理编译产物的功能,简化编译操作。

makefile 复制代码
.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
    g++ -o $@ $^ -std=c++14
udpclient:UdpClient.cc
    g++ -o $@ $^ -std=c++14

.PHONY:clean
clean:
    rm -f udpserver udpclient
相关推荐
德迅云安全—珍珍1 小时前
什么是 DNS 缓存投毒攻击,有什么防护措施
网络·缓存
张火火isgudi1 小时前
fedora 下使用 oh-my-posh 美化 bash
linux·bash
weixin_462446231 小时前
使用 pip3 一键卸载当前环境中所有已安装的 Python 包(Linux / macOS / Windows)
linux·python·macos
好奇龙猫1 小时前
【日语学习-日语知识点小记-日本語体系構造-JLPT-N2前期阶段-第一阶段(6):单词语法】
学习
梁洪飞1 小时前
armv7 cache机制
linux·arm开发·嵌入式硬件·arm·memcache
乾元1 小时前
兵器谱——深度学习、强化学习与 NLP 在安全中的典型应用场景
运维·网络·人工智能·深度学习·安全·自然语言处理·自动化
钮钴禄·爱因斯晨1 小时前
操作系统第一章:计算机系统概述
linux·windows·ubuntu·系统架构·centos·鸿蒙系统·gnu
上海云盾安全满满1 小时前
网络安全威胁是什么,类型有哪些
网络·安全·web安全
蜂蜜黄油呀土豆1 小时前
深入解析计算机网络中的应用层知识:HTTP 与 HTTPS
网络协议·计算机网络·http·https·ssl/tls