当前位置: 首页 > article >正文

【计网】UDP Echo Server与Client实战:从零开始构建简单通信回显程序

目录

前言:

1.实现udpserver类

1.1.创建udp socket 套接字 --- 必须要做的

socket()讲解

代码实现:​编辑

代码讲解:

1.2.填充sockaddr_in结构

代码实现:

代码解析:

1.3.bind sockfd和网络信息(IP + Port)

bind函数解析:

代码展现:

1.4.总代码

2.echo_server主体实现

2.1. 我们要让server先收数据

recvfrom 函数

2.2. 我们要将server收到的数据,发回给对方

sendto函数

2.3.代码

3.client客户端的实现

3.1.创建socket

3.2.填充sockaddr_in结构

3.3.client要不要bind?

3.4.直接通信

3.5.代码

4.效果展现

server端为什么不需要IP端口?

前言:

我们之前讲解了关于socket编程的一些基础知识和接口函数,今天我们就来小试牛刀一下,自己编写一个简单的echo_server程序,将客户端的数据在服务端打印出来(利用udp协议实现)!

1.实现udpserver类

1.1.创建udp socket 套接字 --- 必须要做的

socket()讲解

NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

socket函数是一个系统调用函数,用于创建一个新的套接字。该函数返回一个套接字文件描述符,如果创建失败则返回-1。

参数讲解:

 domain: 选择通信方式 — 本地通信与网络通信
type: 选择协议— UDP/TCP
protocol: 默认使用0、
返回值是创建的socket文件操作符socketfd

代码实现:

代码讲解:

  • AF_INET:这是socket()函数的第一个参数,指定了地址族(Address Family)。AF_INET表示使用IPv4地址,它是Internet地址族的简写。

  • SOCK_DGRAM:这是socket()函数的第二个参数,指定了套接字类型。SOCK_DGRAM表示数据报套接字,这是一种无连接的、固定最大长度的消息服务。它常用于UDP(用户数据报协议)通信。

  • 0:这是socket()函数的第三个参数,通常用于指定协议。当使用AF_INETSOCK_DGRAM时,这个参数通常为0,表示自动选择对应的协议(在这种情况下是UDP

1.2.填充sockaddr_in结构

sockaddr_in结构体通常用于网络编程中表示IPv4地址和端口便于我们进行网络通信。

sockaddr_in是一个在<netinet/in.h>(或<arpa/inet.h>,取决于您的系统)头文件中定义的结构体,用于存储IPv4地址和端口信息。

代码实现:

代码解析:

htons()函数将主机序列,转成网络序列,填充sockaddr_in结构

1.3.bind sockfd和网络信息(IP + Port)

bind函数解析:

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

bind绑定 ,将socket文件与IP地址绑定和端口号,也就是将进程与文件进行绑定。这样当数据包到达该端口和地址时,操作系统知道应该将数据传递给哪个应用程序。

代码展现:

1.4.总代码

    void InitServer()
    {
        // 1. 创建udp socket 套接字 --- 必须要做的
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);

        // 2.0 填充sockaddr_in结构
        struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。int a = 100; a = 20;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port); // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列
        // a. 字符串风格的点分十进制的IP地址转成 4 字节IP
        // b. 主机序列,转成网络序列
        // in_addr_t inet_addr(const char *cp) -> 同时完成 a & b
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP
        local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY);

        // 2.1 bind sockfd和网络信息(IP(?) + Port)
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    }

2.echo_server主体实现

2.1. 我们要让server先收数据

recvfrom 函数

用于从一个套接字(由 _sockfd 标识)接收数据。

NAME
       recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

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

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

一般使用recvfrom函数,从socket文件中获取数据,并可以得到发送者的信息

  1. sockfd:从指定的socket文件中读取数据
  2. buf:缓冲区,将数据读取到这里
  3. len:缓冲区的长度
  4. src_addr:输出型参数,获取发送者的信息
  5. addrlen:输出型参数,获取发送者结构体的长度

2.2. 我们要将server收到的数据,发回给对方

sendto函数

NAME

 send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

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

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

  1. sockfd: socket文件操作符,绑定了确定的IP地址与端口,保证数据按照该文件绑定的方式进行通信
  2. buf:指向包含要发送数据的缓冲区的指针。这个缓冲区应该已经填充了您想要发送的数据。
  3. len:buf指向的缓冲区中数据的长度,以字节为单位。这个值告诉sendto函数要发送多少字节的数据。
  4. flags:这个参数通常设置为0,表示没有特殊的发送选项。不过,它可以是一些标志的组合,比如 MSG_CONFIRM(用于TCP,确认路径是有效的)或MSG_DONTROUTE(数据不应该通过网关发送)。
  5. dest_addr:指向sockaddr结构体的指针,该结构体包含了数据将要发送到的目标地址和端口。对于IPv4,这通常是一个sockaddr_in结构体,而对于IPv6,则是一个sockaddr_in6结构体。
  6. addrlen:dest_addr指向的sockaddr结构体的大小,以字节为单位。这确保了无论在何种平台上,传递给sendto的都是正确的字节大小。

我们一般使用sendto函数来进行发送数据

2.3.代码

    void Start()
    {
        // 一直运行,直到管理者不想运行了, 服务器都是死循环
        // UDP是面向数据报的协议
        _isrunning = true;
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 必须初始化成为sizeof(peer)
            // 1. 我们要让server先收数据
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
                // 2. 我们要将server收到的数据,发回给对方
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
        _isrunning = false;
    }

3.client客户端的实现

3.1.创建socket

跟server端一样,第一步首先要上来创建socket,

3.2.填充sockaddr_in结构

这一步骤也是跟server端一样。

3.3.client要不要bind?

一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!

  1.  如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind ---为什么?防止client port冲突。比如抖音和淘宝使用了同一个端口造成冲突!要bind,必然要和port关联!
  2. 什么时候bind呢?首次发送数据的时候

3.4.直接通信

流程如下:

  1. 客户端先输入数据,发送到服务端
  2. 服务端接收数据
  3. 服务端再将接收到的数据发送给客户端
  4. 最后客户端在屏幕回显出自己原本发送的数据

3.5.代码

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    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::cerr << "socket error" << std::endl;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 
    //为什么?防止client port冲突。比如抖音和淘宝使用了同一个端口造成冲突!要bind,必然要和port关联!
    // b. 什么时候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;
    // 2. 直接通信即可
    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;
}

4.效果展现

成功!

server端为什么不需要IP端口?

因为server默认绑定的就是0.0.0.0,代表绑定自己的所有网卡信息,所以就不要我们自己手动填写啦。


http://www.kler.cn/a/370980.html

相关文章:

  • 【DB-GPT】开启数据库交互新篇章的技术探索与实践
  • 人工智能任务19-基于BERT、ELMO模型对诈骗信息文本进行识别与应用
  • vim使用指南
  • 蓝桥杯备赛:顺序表和单链表相关算法题详解(上)
  • Java中的注解:如何自定义注解并实现功能
  • Redis复制(replica)
  • 探索Konko AI:快速集成大语言模型的最佳实践
  • Pr 视频效果:闪光灯
  • CodeQL学习笔记(2)-QL语法(递归)
  • 【亲测】mini版centos7.9配置网络基础ssh等直接使用
  • STM32外设应用
  • 2024性价比家居好物有哪些?推荐五款值得每个家庭拥有的好物品牌!
  • 【UE5.3 Cesium for Unreal】编译GlobePawn
  • WPF+MVVM案例实战(十)- 水波纹按钮实现与控件封装
  • 【微服务】Feign 远程调用
  • OSError: image file is truncated
  • Apache paimon-CDC
  • DNS污染?SNI阻断?全新网络协议保护隐私安全
  • 基于Springboot+微信小程序的“学课助手”小程序 (含源码数据库)
  • 荣誉证书PSD素材(59套免费)
  • 八大排序-冒泡排序
  • 【论文阅读】ESRGAN
  • Qt/C++ 调用迅雷开放下载引擎(ThunderOpenSDK)下载数据资源
  • 阿里云项目启动OOM问题解决
  • 数据结构 - 并查集
  • HexForge:一款用于扩展安全汇编和十六进制视图的IDA插件