【计网】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,代表绑定自己的所有网卡信息,所以就不要我们自己手动填写啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900718.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux中级wed服务器(https搭建加密服务器)

一。非对称加密算法&#xff1a; 公钥&#xff1a;公共密钥&#xff0c;开放 私钥&#xff1a;私有密钥&#xff0c;保密 1.发送方用自己的公钥加密&#xff0c;接受方用发送方的私钥解密&#xff1a;不可行 2.发送方用接受方的公钥加密&#xff0c;接受方用自己的私钥解密…

ffmpeg视频滤镜: 色温- colortemperature

滤镜简述 colortemperature 官网链接 》 FFmpeg Filters Documentation 这个滤镜可以调节图片的色温&#xff0c;色温值越大显得越冷&#xff0c;可以参考一下下图&#xff1a; 咱们装修的时候可能会用到&#xff0c;比如选择灯还有地板的颜色的时候&#xff0c;选暖色调还是…

【论文+源码】基于spring boot的垃圾分类网站

创建一个基于Spring Boot的垃圾分类网站涉及多个步骤&#xff0c;包括环境搭建、项目创建、数据库设计、后端服务开发、前端页面设计等。下面我将引导您完成这个过程。 第一步&#xff1a;准备环境 确保您的开发环境中安装了以下工具&#xff1a; Java JDK 8 或更高版本Mav…

Unity URP ShaderGraph 基本设置

先简单了解一下各种渲染管线之间的区别 Unity 从 2019.3 版本开始正式支持通用渲染管线&#xff08;URP&#xff0c;Universal Render Pipeline&#xff09;。URP 是轻量渲染管线&#xff08;LWRP&#xff0c;Lightweight Render Pipeline&#xff09;的升级和重命名版本&…

【解决】使用Hypermark将Markdown文件转化为HTML文件

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、文件准备&#xff08;一&#xff09;HTML模板文件&#xff08;二&#xff09;MD文件夹和储存文件夹 二、文件转…

COSCon'24 志愿者招募令:共创开源新生活!

亲爱的开源爱好者们&#xff0c; 第九届中国开源年会&#xff08;COSCon24&#xff09;即将在北京中关村国家自主创新示范区会议中心于2024年11月2日至3日隆重举行。今年的主题是“Open Source, Open Life&#xff5c;开源新生活”&#xff0c;旨在探索开源技术如何在各个领域推…

日常记录,使用springboot,vue2,easyexcel使实现字段的匹配导入

目前的需求是数据库字段固定&#xff0c;而excel的字段不固定&#xff0c;需要实现excel导入到一个数据库内。 首先是前端的字段匹配&#xff0c;显示数据库字段和表头字段 读取表头字段&#xff1a; 我这里实现的是监听器导入&#xff0c;需要新建一个listen类。 读Excel …

uniApp 加载google地图 并规划路线

uniApp 加载google地图 并规划路线 备注:核心代码实例 备注: 打开谷歌地图失败的话 参考google开发文档 https://developers.google.com/maps/documentation/urls/ios-urlscheme?hlzh-cn#swift核心代码 mounted() {this.loadGoogleMapsScript(); }, methods: {//加载loadGo…

AI服务器HBA卡的国产PCIe4.0/5.0 switch信号完整性设计与实现,支持定制(二)

表 &#xff12; 展示了 &#xff30;&#xff23;&#xff22; 板所选介质材料 &#xff30;&#xff33;&#xff32;&#xff14;&#xff10;&#xff10;&#xff10;&#xff21;&#xff35;&#xff33;&#xff17;&#xff10;&#xff13; &#xff0c; &#xff3…

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库 常见的解决方案有两种&#xff0c;分别…

使用DolphinScheduler接口实现批量导入工作流并上线

使用DS接口实现批量导入工作量并上线脚本 前面实现了批量生成DS的任务&#xff0c;当导入时发现只能逐个导入&#xff0c;因此通过接口实现会更方便。 DS接口文档 DS是有接口文档的地址是 http://IP:12345/dolphinscheduler/swagger-ui/index.html?languagezh_CN&lang…

安全见闻---清风

注&#xff1a;本文章源于泷羽SEC&#xff0c;如有侵权请联系我&#xff0c;违规必删 学习请认准泷羽SEC学习视频:https://space.bilibili.com/350329294 安全见闻1 泷哥语录&#xff1a;安全领域什么都有&#xff0c;不要被表象所迷惑&#xff0c;无论技术也好还是其他方面…

网站建设中需要注意哪些安全问题?----雷池社区版

服务器与应用安全指南 1. 服务器安全 1.1 操作系统安全 及时更新补丁&#xff1a;确保操作系统始终安装最新补丁&#xff0c;以防范系统漏洞。例如&#xff0c;Windows Server 定期推送安全更新&#xff0c;修复如远程代码执行等潜在威胁。优化系统服务配置&#xff1a;关闭不…

PoissonRecon学习笔记

1. Screened Poisson Reconstruction (SPR) 源码&#xff1a;https://github.com/mkazhdan/PoissonRecon However, as noted by several researchers, it suffers from a tendency to over-smooth the data. 泊松重建存在过度平滑的现象。 方法&#xff1a;position and gradi…

【C++】一文带你深入理解C++异常机制

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言一、C语言处理错误的方式二、C异常三、异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3 异常安全3.4 异常规范 四、自定义异…

擎创科技声明

近日&#xff0c;我司陆续接到求职者反映&#xff0c;有自称是擎创科技招聘人员&#xff0c;冒用“上海擎创信息技术有限公司”名义&#xff0c;用“126.com”的邮箱向求职者发布招聘信息&#xff0c;要求用户下载注册APP&#xff0c;进行在线测评。 对此&#xff0c;我司郑重…

7、哈希表

7、哈希表 哈希表最主要的作用就是把一个比较庞大的空间或者值域 映射到比较小的值域 (0-n) 就是将-10^9 ~10^9 映射到 0 ~10^5 一、存储结构 映射的方法可以是 h(x) x mod 10^5 但是这样映射会出现一个问题 可能会有重复的数字出现 所以就引出了两个方法 开放寻址法 和…

【Javaee】网络原理—TCP协议的核心机制

前言 TCP/IP五层协议是互联网中的主流模型&#xff0c;为网络通信提供了一个稳固的框架。 主要包含了应用层&#xff0c;传输层&#xff0c;网络层&#xff0c;数据链路层&#xff0c;物理层。 本篇主要介绍传输层的TCP协议的核心机制 一. 确认应答&#xff08;ack&#xf…

SQL 干货 | SQL 半连接

大多数数据库开发人员和管理员都熟悉标准的内、外、左和右连接类型。虽然可以使用 ANSI SQL 编写这些连接类型&#xff0c;但还有一些连接类型是基于关系代数运算符的&#xff0c;在 SQL 中没有语法表示。今天我们将学习一种这样的连接类型&#xff1a;半连接&#xff08;Semi …

内网穿透:如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)

内网穿透&#xff1a;如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)-含详细原理配置说明介绍 前言 远程桌面协议(RDP, Remote Desktop Protocol)可用于远程桌面连接&#xff0c;Windows系统&#xff08;家庭版除外&#xff09;也是支持这种协议的&#xff0c;无需安装…