Linux网络-Socket 编程 UDP

大家好,上次我们简单介绍了Linux网络的知识,今天我们来继续上次的学习。

目录

[1. socket 编程接口](#1. socket 编程接口)

[1.1 socket函数](#1.1 socket函数)

[1.2 bind函数](#1.2 bind函数)

[1.3 构造简单UDP服务器](#1.3 构造简单UDP服务器)

[1.4 字节序转换函数](#1.4 字节序转换函数)

[1.5 IPv4 地址操作工具](#1.5 IPv4 地址操作工具)

[2. echo server](#2. echo server)

[2.1 recvfrom函数](#2.1 recvfrom函数)

[2.2 sendto函数](#2.2 sendto函数)

[2.3 INADDR_ANY](#2.3 INADDR_ANY)

[3. DictServer](#3. DictServer)

[4. 简单聊天室](#4. 简单聊天室)


1. socket****编程接口

上次我们只是介绍了有关socket编程的接口,今天我们首先来学习一下这些接口:

1.1 socket函数
复制代码
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

socket函数是创建套接字(Socket)的核心系统调用,用于初始化一个网络通信的端点。套接字是网络通信的基础,无论是 TCP 还是 UDP 协议,都需要先通过 socket 函数创建套接字描述符,再进行后续的连接、绑定、发送 / 接收数据等操作。

socket 函数的三个参数决定了套接字的类型、地址族和协议,具体含义如下:

1. domain(地址族 / 协议族)

指定套接字使用的地址类型,决定了网络地址的格式(如 IPv4、IPv6 或本地通信)。常见取值:

AF_INET:IPv4 地址族(最常用),对应的地址格式为 struct sockaddr_in

AF_INET6:IPv6 地址族,对应的地址格式为 struct sockaddr_in6

AF_UNIX(或 AF_LOCAL):本地进程间通信(非网络),对应的地址格式为 struct sockaddr_un(基于文件系统路径)。

2. type(套接字类型)

指定套接字的通信方式,决定了数据传输的特性(如是否可靠、是否面向连接)。常见取值:

SOCK_STREAM:流式套接字(面向连接),对应 TCP 协议。特点:可靠传输、字节流、无边界、需建立连接(如 connect)。

SOCK_DGRAM:数据报套接字(无连接),对应 UDP 协议。特点:不可靠传输、有边界、无需连接,直接发送数据报。

SOCK_RAW:原始套接字,允许直接操作底层协议(如 IP 协议),通常用于网络工具(如 ping、traceroute),需要 root 权限。

3. protocol(协议)

指定具体使用的协议,通常填 0(让系统根据前两个参数自动选择默认协议)。若需显式指定,常见取值:

type=SOCK_STREAM 时,protocol 可设为 IPPROTO_TCP(TCP 协议)。

type=SOCK_DGRAM 时,protocol 可设为 IPPROTO_UDP(UDP 协议)。

返回值

成功:返回一个非负整数(套接字描述符,类似文件描述符,用于后续操作)。

失败:返回 -1,并设置全局变量 errno(可通过 perror 打印错误信息)。

复制代码
#include<iostream>
#include<sys/socket.h>

int main()
{
    // 1. 创建 socket,就是创建了文件细节
    int _sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(_sockfd<0)
    {
        perror("创建套接字socket失败");
        return 1;
    }
    
    return 0;
}
1.2 bind函数
复制代码
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

bind 函数用于将一个套接字(socket)与特定的网络地址(IP 地址 + 端口号)绑定,确保该套接字后续只能通过绑定的地址接收或发送数据。这一步是服务器端编程的关键(服务器需要固定端口监听连接),客户端通常无需显式绑定(系统会自动分配临时端口)。

bind 函数的三个参数分别指定 "要绑定的套接字""目标地址""地址结构长度",具体含义如下:

1. sockfd(套接字描述符)

socket 函数返回的套接字描述符(Linux 中是 int 类型,Windows 中是 SOCKET 类型),表示要绑定地址的套接字。

2. addr(地址结构指针)

指向一个特定协议的地址结构 (需强制转换为 struct sockaddr* 类型,因为函数要求统一的地址结构接口)。

地址结构的类型由创建套接字时的 domain(地址族)决定:

domain=AF_INET(IPv4):使用 struct sockaddr_in 结构,定义如下:

复制代码
struct sockaddr_in {
    sa_family_t     sin_family;   // 地址族(必须为 AF_INET)
    in_port_t       sin_port;     // 端口号(需转换为网络字节序)
    struct in_addr  sin_addr;     // IPv4 地址(需转换为网络字节序)
    char            sin_zero[8];  // 填充字段(需设为 0,保持与 sockaddr 大小一致)
};
struct in_addr {
    in_addr_t s_addr;  // IPv4 地址(32 位整数,如 INADDR_ANY 表示绑定所有本地地址)
};

domain=AF_INET6(IPv6):使用 struct sockaddr_in6 结构(类似 IPv4,但地址为 128 位)。

domain=AF_UNIX(本地通信):使用 struct sockaddr_un 结构(基于文件路径)。

3. addrlen(地址结构长度)

指定 addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in))。

作用:告诉系统如何解析 addr 指向的内存(不同地址族的结构长度不同)。

返回值

成功:返回

失败:

Linux 中返回 -1,并设置 errno(可通过 perror 打印错误)。

Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。

复制代码
#include<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>

int main()
{
    // 1. 创建 socket,就是创建了文件细节
    int _sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(_sockfd < 0)
    {
        perror("创建套接字socket失败");
        return 1;
    }

    // 2. 绑定指定网络信息
    struct sockaddr_in local;
    bzero(&local,sizeof(local)); //填充字段设为 0
    local.sin_family = AF_INET;
    local.sin_port = htons(8080);
    local.sin_addr.s_addr = INADDR_ANY;
    
    int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
    if(n < 0)
    {
        perror("bind失败");
    }
    return 0;
}

目前学习这两个函数就足够支撑我们建立一个简单的server服务器了。下面我们建立一个服务器。

1.3 构造简单UDP服务器

下面是一个简单的建立服务器的代码:

复制代码
#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include"Log.hpp"

const int defaultfd = -1;

using namespace LogModule;
    
class UDPServer{
public:
    UDPServer(const std::string ip , uint16_t port)
    :_sockfd(defaultfd)
    ,_ip(ip)
    ,_port(port)
    {}

    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET , SOCK_DGRAM , 0);
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL)<< "socket failed" ;
            exit(1);
        } 
        LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;
      
        // 2. 绑定socket信息,ip和端口
        struct sockaddr_in local;
        bzero(&local , sizeof(local));
        local.sin_family = AF_INET;
        // IP信息和端口信息,一定要发送到网络
        // 本地格式->网络序列
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
    }

private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
};

由于要将ip地址和port端口号也发送到网络中,所以我们要对port端口号和ip地址进行格式转换,下面我们介绍一下关于这一部分的接口:

1.4 字节序转换函数
复制代码
#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
函数名 功能描述 处理数据长度 转换方向
htonl 将无符号长整数从主机字节序→网络字节序 32 位(4 字节) Host to Network Long
htons 将无符号短整数从主机字节序→网络字节序 16 位(2 字节) Host to Network Short
ntohl 将无符号长整数从网络字节序→主机字节序 32 位(4 字节) Network to Host Long
ntohs 将无符号短整数从网络字节序→主机字节序 16 位(2 字节) Network to Host Short

服务器绑定端口bind 函数中,端口号需通过 htons 转为网络字节序。

复制代码
struct sockaddr_in addr;
addr.sin_port = htons(8080); // 将主机字节序的8080转为网络字节序

解析收到的 IP / 端口 :从网络中收到的 IP 或端口,需通过 ntohl/ntohs 转回主机字节序才能本地使用。

复制代码
uint16_t port = ntohs(net_port); // 将网络字节序的端口转回主机字节序
1.5 IPv4 地址操作工具
复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);

| 函数名 | 功能描述 | 现代替代(支持 IPv6) | 备注 |
| inet_aton | 将点分十进制 IP 字符串 (如"192.168.1.1")转换为struct in_addr(网络字节序整数) | inet_pton | 线程安全,推荐使用 |
| inet_addr | 将点分十进制 IP 字符串转换为网络字节序整数in_addr_t类型) | inet_pton | 缺陷:无法区分"255.255.255.255"和错误,已不推荐 |
| inet_network | 将点分十进制 IP 字符串转换为网络字节序整数(通常用于子网地址处理) | - | 使用率低,逐渐被替代 |
| inet_ntoa | 将struct in_addr(网络字节序 IP)转换为点分十进制字符串 | inet_ntop | 线程不安全、不支持 IPv6,已弃用 |
| inet_makeaddr | (已弃用)将 "网络地址 + 主机地址" 组合成struct in_addr | - | 因子网划分方式演进,已无实用价值 |
| inet_lnaof | (已弃用)从struct in_addr中提取主机部分地址 | - | 同上 |

inet_netof (已弃用)从struct in_addr中提取网络部分地址 -

以上就是建立一个简单服务器所需要的操作,下面让我们实践几个服务器。

2. echo server

简单的回显服务器和客户端代码

UDP_Server.hpp

复制代码
#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include<functional>
#include"Log.hpp"

using func_t = std::function<std::string(const std::string&)>;

const int defaultfd = -1;

using namespace LogModule;
    
class UDPServer{
public:
    UDPServer(const std::string ip , uint16_t port , func_t func)
    :_sockfd(defaultfd)
    ,_ip(ip)
    ,_port(port)
    ,_func(func)
    {}

    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET , SOCK_DGRAM , 0);
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL)<< "socket failed" ;
            exit(1);
        } 
        LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;
      
        // 2. 绑定socket信息,ip和端口
        struct sockaddr_in local;
        bzero(&local , sizeof(local));
        local.sin_family = AF_INET;
        // IP信息和端口信息,一定要发送到网络
        // 本地格式->网络序列
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<< "bind failed" ;
            exit(1);
        }
        LOG(LogLevel::INFO)<< "bind success, ip: " << _ip << ", port: " << _port;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 收消息
            ssize_t s = recvfrom(_sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr*)&peer , &len);
            if(s > 0)
            {
                int peer_port = ntohs(peer.sin_port);
                std::string peer_ip = inet_ntoa(peer.sin_addr);

                buffer[s] = 0;
                std::string result = _func(buffer);
                // 2. 发消息
                sendto(_sockfd , result.c_str() , result.size() , 0 , (struct sockaddr*)&peer , len);
            }        
        }
    }

    ~UDPServer()
    {}
private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;

    bool _isrunning;
    func_t _func;
};

UDP_Server.cc

复制代码
#include<iostream>
#include<memory>
#include"UDP_Server.hpp"

std::string defaulthandler(const std::string& message)
{
    std::string hello = "hello ";
    hello += message;
    return hello;
}

int main(int argc , char* argv[])
{
    if(argc!=3)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
    }
    std::string ip=argv[1];
    uint16_t port = std::stoi(argv[2]);
    //Enable_Console_Log_Strategy();
    std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(ip, port, defaulthandler);
    usvr->Init();
    usvr->Start();
    return 0;
}

UDP_Client.cc

复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

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

    // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联呢?
    // 问题:client要不要bind?需要bind.
    //   client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
    //   为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
    //   client端的端口号是几,不重要,只要是唯一的就行!
    // 填写服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    while(true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);

        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&server, sizeof(server));
        (void)n;

        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
        if(m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }


    return 0;
}

这里又要学到两个新的函数,分别是收消息和发消息:

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

recvfrom 是网络编程中用于接收无连接协议(如 UDP)数据报 的系统调用,核心功能是从指定套接字接收数据,并同时获取发送方的网络地址(IP + 端口)。由于 UDP 是无连接的,接收数据时必须通过该函数获取发送方信息,才能进行回复(如用 sendto 回传数据)。

参数详解

1.sockfd(套接字描述符)

  • 已创建并绑定(bind)的 UDP 套接字描述符(Linux 中为 int,Windows 中为 SOCKET),表示从哪个套接字接收数据。

2. buf(接收缓冲区)

  • 指向一块内存区域的指针,用于存储接收的数据(需提前分配内存,如 char buf[1024])。

3. len(缓冲区长度)

  • 接收缓冲区的最大容量(字节数),用于限制接收数据的大小(避免缓冲区溢出)。
  • 若实际接收的数据超过 len,超出部分会被截断(且不会保留后续数据)。

4. flags(接收标志)

  • 控制接收行为的标志,通常设为 0(默认行为)。常见非零标志:
    • MSG_PEEK:"偷看" 数据(读取数据但不从套接字缓冲区移除,下次调用仍能读到该数据),用于预览数据。
    • MSG_OOB:接收带外数据(仅用于 TCP 紧急数据,UDP 中无意义)。
    • MSG_WAITALL:等待直到接收满 len 字节数据(但可能被信号中断,不保证一定填满)。

5. src_addr(发送方地址)

  • 指向一个地址结构(如 struct sockaddr_in 用于 IPv4)的指针,用于存储发送方的网络地址(IP + 端口)。
  • 若不需要获取发送方地址,可设为 NULL(但 UDP 通常需要,否则无法回复)。

6. addrlen(地址长度)

  • 输入输出参数
    • 输入时:指定 src_addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in))。
    • 输出时:实际存储的发送方地址结构的长度(可能小于输入值,通常忽略差异)。
  • src_addrNULL,此参数也需设为 NULL

返回值

  • 成功 :返回实际接收的字节数(0 通常表示连接关闭,但 UDP 无连接,0 可能是对方发送了空数据报)。
  • 失败
    • Linux 中返回 -1,并设置 errno(如 EINTR 被信号中断、EAGAIN 非阻塞模式无数据)。
    • Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。
2.2 sendto函数
复制代码
#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);

sendto 是网络编程中用于向指定目标地址发送无连接协议(如 UDP)数据报 的系统调用,与 recvfrom 对应。由于 UDP 是无连接的,每次发送数据都需要显式指定目标的网络地址(IP + 端口),sendto 正是为此设计的核心函数。

参数详解

1.sockfd(套接字描述符)

  • 已创建的 UDP 套接字描述符(Linux 中为 int,Windows 中为 SOCKET),表示通过哪个套接字发送数据。
  • 若为服务器,该套接字需提前通过 bind 绑定本地地址;客户端可直接使用未绑定的套接字(系统会自动分配临时端口)。

2. buf(发送缓冲区)

  • 指向待发送数据的内存区域(如字符串、二进制数据),需提前填充要发送的内容。

3. len(数据长度)

  • 待发送数据的字节数(buf 中有效数据的长度)。
  • 若数据长度超过系统规定的最大 UDP 数据报大小(通常约 65507 字节,受 IP 层 MTU 限制),可能导致数据被分片或丢弃。

4. flags(发送标志)

  • 控制发送行为的标志,通常设为 0(默认行为)。常见非零标志:
    • MSG_DONTROUTE:绕过路由表,仅在本地网络发送(用于调试或特定本地通信)。
    • MSG_OOB:发送带外数据(仅 TCP 有效,UDP 中无意义)。

5. dest_addr(目标地址)

  • 指向一个地址结构(如 struct sockaddr_in 用于 IPv4)的指针,存储接收方的网络地址(IP + 端口)。
  • 地址结构的类型必须与套接字的协议族一致(如 AF_INET 对应 IPv4 地址)。

6. addrlen(地址长度)

  • 指定 dest_addr 指向的地址结构的字节长度(如 sizeof(struct sockaddr_in)),告诉系统如何解析目标地址。

返回值

  • 成功 :返回实际发送的字节数(通常等于 len,但在某些情况下可能小于 len,如非阻塞模式下缓冲区不足)。
  • 失败
    • Linux 中返回 -1,并设置 errno(如 EINTR 被信号中断、EAGAIN 非阻塞模式发送缓冲区满)。
    • Windows 中返回 SOCKET_ERROR,需通过 WSAGetLastError() 获取错误码。

但此时我们发现,server服务器只能接受与其ip地址相同的client客户端发出的数据,并无法接收来自其他IP地址的数据,我们若想收到来自其他ip地址的数据,则需要将server服务器bing绑定的ip设置位INADDR_ANY

2.3 INADDR_ANY
复制代码
server.sin_addr.s_addr = INADDR_ANY;

这表示server服务器会接收来自不同ip地址的数据:

不使用INADDR_ANY时,假设服务器绑定到 192.168.1.100:8080

  • 客户端发送到 192.168.1.100:8080 的数据包:服务器能收到。
  • 客户端发送到 127.0.0.1:8080(本机回环地址):服务器收不到(目标 IP 不匹配)。
  • 客户端发送到 10.0.0.5:8080(本机另一网卡的 IP):服务器收不到(目标 IP 不匹配)。

如果服务器绑定INADDR_ANY:8080,则会接收发送到本机所有 IP 地址(包括 127.0.0.1、所有网卡 IP 等)且目标端口为 8080 的数据包,无需严格匹配某一个 IP。

今天的内容到这里就全部介绍完了,下面是剩余的两个server服务器实现代码:

3. DictServer

实现一个简单的英译汉的网络字典:
dictionary.txt:

复制代码
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

Dict.hpp:

复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"

const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";

using namespace LogModule;

class Dict
{
public:
    Dict(const std::string &path = defaultdict) : _dict_path(path)
    {
    }
    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            // "apple: 苹果"
            auto pos = line.find(sep);
            if (pos == std::string::npos)
            {
                LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
                continue;
            }
            std::string english = line.substr(0, pos);
            std::string chinese = line.substr(pos + sep.size());
            if (english.empty() || chinese.empty())
            {
                LOG(LogLevel::WARNING) << "没有有效内容: " << line;
                continue;
            }

            _dict.insert(std::make_pair(english, chinese));
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }

        in.close();
        return true;
    }
    std::string Translate(const std::string &word, InetAddr &client)
    {
        auto iter = _dict.find(word);
        if (iter == _dict.end())
        {
            LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->None";
            return "None";
        }
        LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.Ip() << " : " << client.Port() << "]# " << word << "->" << iter->second;
        return iter->second;
    }
    ~Dict()
    {
    }

private:
    std::string _dict_path; // 路径+文件名
    std::unordered_map<std::string, std::string> _dict;
};

InetAddr.hpp:

复制代码
#pragma once

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

class InetAddr{
public:
    InetAddr(struct sockaddr_in addr)
    :_addr(addr)
    {
        _port=ntohs(addr.sin_port);
        _ip=inet_ntoa(addr.sin_addr);
    }

    uint16_t Port(){return _port;}

    std::string Ip(){return _ip;}

    ~InetAddr()
    {}
private:
    struct sockaddr_in _addr;
    uint16_t _port;
    std::string _ip;
};

UDP_Server.hpp:

复制代码
#pragma once

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<arpa/inet.h>
#include<functional>
#include"Log.hpp"
#include"Dict.hpp"
#include"InetAddr.hpp"

using func_t = std::function<std::string(const std::string&,InetAddr&)>;

const int defaultfd = -1;

using namespace LogModule;
    
class UDPServer{
public:
    UDPServer(uint16_t port , func_t func)
    :_sockfd(defaultfd)
    ,_port(port)
    ,_isrunning(false)
    ,_func(func)
    {}

    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET , SOCK_DGRAM , 0);
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL)<< "socket failed" ;
            exit(1);
        } 
        LOG(LogLevel::INFO)<< "socket success,sockfd: "<< _sockfd ;
      
        // 2. 绑定socket信息,ip和端口
        struct sockaddr_in local;
        bzero(&local , sizeof(local));
        local.sin_family = AF_INET;
        // IP信息和端口信息,一定要发送到网络
        // 本地格式->网络序列
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<< "bind failed" ;
            exit(1);
        }
        LOG(LogLevel::INFO)<< "bind success, ip: " << _ip << ", port: " << _port;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 收消息
            ssize_t s = recvfrom(_sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr*)&peer , &len);
            if(s > 0)
            {
                InetAddr client(peer);
                buffer[s]=0;
                std::string result=_func(buffer,client);
                sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);
            }        
        }
    }

    ~UDPServer()
    {}
private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;

    bool _isrunning;
    func_t _func;
};

UDP_Server.cc:

复制代码
#include<iostream>
#include<memory>
#include"UDP_Server.hpp"
#include"Dict.hpp"

int main(int argc , char* argv[])
{
    if(argc!=2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    //Enable_Console_Log_Strategy();

    Dict dict;
    dict.LoadDict();

    std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port,[&dict](const std::string& word,InetAddr& cli)->std::string{
        return dict.Translate(word,cli);
    });
    usvr->Init();
    usvr->Start();
    return 0;
}

UDP_Client.cc:

复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr<< "socket eorrer"<<std::endl;
        exit(1);
    }

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

    while(true)
    {
        std::string input;
        std::cout<<"Please Enter# ";
        std::getline(std::cin,input);

        int n=sendto(sockfd,input.c_str(),input.size(),0,(struct sockaddr*)&server,sizeof(server));
        (void)n;

        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        int m=recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
        if(m>0)
        {
            buffer[m]=0;
            std::cout<<buffer<<std::endl;
        }
    }
    return 0;
}

4. 简单聊天室

UDP_Client.cc:

复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"

using namespace ThreadModule;

int sockfd = 0;
uint16_t server_port = 0;
std::string server_ip;
pthread_t id;

void Send()
{
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    const std::string online = "inline";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));

    while (true)
    {
        std::string input;
        std::cout << "Please Enter# "; // 1
        std::getline(std::cin, input); // 0

        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;
        if (input == "QUIT")
        {
            pthread_cancel(id);
            break;
        }
    }
}

void Recv()
{
    while (true)
    {
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cerr << buffer << std::endl; // 2
        }
    }
}

// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket eorrer" << std::endl;
        return 1;
    }

    Thread recver(Recv);
    Thread sender(Send);

    recver.Start();
    sender.Start();

    id = recver.Id();

    recver.Join();
    sender.Join();
    return 0;
}

UDP_Server.hpp:

复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <strings.h>
#include <string>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
#include "InetAddr.hpp"

using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;

const int defaultfd = -1;

using namespace LogModule;

class UDPServer
{
public:
    UDPServer(uint16_t port, func_t func)
        : _sockfd(defaultfd), _port(port), _isrunning(false), _func(func)
    {
    }

    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket failed";
            exit(1);
        }
        LOG(LogLevel::INFO) << "socket success,sockfd: " << _sockfd;

        // 2. 绑定socket信息,ip和端口
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        // IP信息和端口信息,一定要发送到网络
        // 本地格式->网络序列
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind failed";
            exit(1);
        }
        LOG(LogLevel::INFO) << "bind success, ip: " << _ip << ", port: " << _port;
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 收消息
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                InetAddr client(peer);
                buffer[s] = 0;
                _func(_sockfd, buffer, client);
            }
        }
    }

    ~UDPServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;

    bool _isrunning;
    func_t _func;
};

UDP_Server.cc:

复制代码
#include<iostream>
#include<memory>
#include"UDP_Server.hpp"
#include"ThreadPool.hpp"
#include"Route.hpp"

using namespace ThreadPoolModule;

using task_t = std::function<void()>;

int main(int argc , char* argv[])
{
    if(argc!=2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    //Enable_Console_Log_Strategy();

    Route r;

    auto tp = ThreadPool<task_t>::GetInstance();

    std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port, [&r, &tp](int sockfd, const std::string &message, InetAddr&peer){
        task_t t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
        tp->Enqueue(t);
    });
    usvr->Init();
    usvr->Start();
    return 0;
}

Route.hpp:

复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

class Route
{
public:
    Route()
    {
    }

    void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
    {
        if (!IsExist(peer))
        {
            AddUser(peer);
        }

        std::string send_message = peer.StringAddr() + "#" + message;

        for (auto user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&user.NetAddr(), sizeof(user.NetAddr()));
        }

        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
            DeleteUser(peer);
        }
    }

    ~Route()
    {
    }

private:
    bool IsExist(InetAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
            {
                return true;
            }
        }
        return false;
    }

    void AddUser(InetAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr();
        _online_user.push_back(peer);
    }

    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";
                _online_user.erase(iter);
                break;
            }
        }
    }

private:
    std::vector<InetAddr> _online_user;
};

InetAddr.hpp:

复制代码
#pragma once

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

class InetAddr{
public:
    InetAddr(struct sockaddr_in addr)
    :_addr(addr)
    {
        _port=ntohs(addr.sin_port);
        _ip=inet_ntoa(addr.sin_addr);
    }

    uint16_t Port(){return _port;}

    std::string Ip(){return _ip;}

    const struct sockaddr_in &NetAddr() { return _addr; }

    bool operator==(const InetAddr& addr)
    {
        return addr._ip==_ip&&addr._port==_port;
    }

    std::string StringAddr()
    {
        return _ip+":"+std::to_string(_port);
    }

    ~InetAddr()
    {}
private:
    struct sockaddr_in _addr;
    uint16_t _port;
    std::string _ip;
};

以上是三个server服务器实例,今天的内容就到这里,我们下期再见!

相关推荐
hhhh明4 小时前
quest2+alvr+steamvr
linux·windows·quest2
dqsh064 小时前
树莓派5+Ubuntu24.04 LTS CH348 / CH9344 驱动安装 保姆级教程
linux·c语言·单片机·嵌入式硬件·iot
00后程序员张6 小时前
HTTP抓包工具推荐,Fiddler配置方法、代理设置与使用教程详解(开发者必学网络调试技巧)
网络·http·ios·小程序·fiddler·uni-app·webview
menge23336 小时前
Linux DNS域名解析服务器练习
linux·运维·服务器
wsad05326 小时前
CentOS 7 更换腾讯云 yum 源及 EPEL 源
linux·centos·腾讯云
wdfk_prog6 小时前
[Linux]学习笔记系列 -- [kernel]kallsyms
linux·笔记·学习
emiya_saber7 小时前
Linux 进程调度管理
linux·运维·服务器
不脱发的程序猿7 小时前
嵌入式Linux:线程同步(读写锁)
linux·嵌入式
yangzhi_emo7 小时前
配置dns主从服务
linux·运维·服务器