demo版的udp网络通信实现

news/2024/5/18 11:52:18 标签: udp, 网络协议, 网络, 学习笔记, 学习, linux

 

目录

 ​编辑

一,引言

 二,服务端

1,server类

2,构造函数

 3,初始化服务函数

初始化函数整体代码:

4,启动函数

三,客户端

四,main函数调用


 

一,引言

今天我们要写的demo框架是基于CS架构的。也就是说要实现网络通信就必须要实现两端:1,客户端   2,服务端。这两端的任务如下:

服务端:

接收客户端发来的消息,并且打印显示出来,然后再向客户端反馈已收到消息。

客户端:

 用户写下消息,并发送到服务端请求服务器处理。

 二,服务端

1,server类

要实现网络通信必须要有的便是ip和端口号,所以我们的server类的成员当中一定要有的便是ip和端口号。并且我们的网络通信是依靠套接字完成的,所以在这个类中必不可少的还有套接字描述符。

所以类成员如下:

class UdpServer
{

    private:
        std::string ip_;//ip地址,ip地址一般都是点分十进制的所以用string
        uint16_t port_;//端口号
        int socketfd_;//套接字描述符
};

2,构造函数

 server端的构造函数实现非常的简单,就是简单的让类内的成员被赋值。这一步可有可无,但是为了完整性还是写下。

 代码:

UdpServer()
        : port_(0), socketfd_(0)
    {
    }

 3,初始化服务函数

初始化服务有以下两个步骤:1,创建套接字   2,绑定ip和port

创建套接字 :

 使用socket函数创建:


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

int socket(int domain, int type, int protocol);

​

domain:标识这个套接字的通信类型(本地/网络

type:套接字提供的服务类型

protocol:协议

一般在实现UDP通信时填上0便可以。

返回值:socket函数的返回值是一个socket文件描述符。

代码:

 socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
 if(socketfd_<0)//创建失败就退出进程
 {
    perror("socket error\n");
    exit(1);
 }

 绑定套接字:

使用bind函数绑定ip和port:

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

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

sockfd:套接字描述符。

sockaddr:套接字地址结构体。

struct sockaddr 
{
   sa_family_t sa_family;
   char sa_data[14];
 }

addrlen:套接字结构体长度。

套接字地址结构体初始化:

 sockaddr_in si;//定义套接字结构体地址
 bzero(&si, sizeof si);  // 清空
 si.sin_family = AF_INET;//协议位ipv4
 si.sin_port = htons(port);//端口号要转化为网络字节序列
 si.sin_addr.s_addr = INADDR_ANY;//服务器端一般都要设置为可以接收任意ip地址发来的消息

 开始绑定:

if(bind(socketfd_, (sockaddr *)&si, sizeof si)<0)//绑定失败就直接退出
{
   perror("bind error\n");
   exit(1);
}
初始化函数整体代码:
//定义一些变量来代表默认值
#define defaultip  "0.0.0.0"
#define defaultport 8008


    void Init(const std::string& ip = defaultip,int port = defaultport)
    {
        socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
        if(socketfd_<0)//创建失败就退出进程
        {
            perror("socket error\n");
            exit(1);
        }

        //创建成功,开始绑定
        sockaddr_in si;//定义套接字结构体地址
        bzero(&si, sizeof si);  // 清空
        si.sin_family = AF_INET;//协议位ipv4
        si.sin_port = htons(port);//端口号要转化为网络字节序列
        si.sin_addr.s_addr = INADDR_ANY;//服务器端一般都要设置为可以接收任意ip地址发来的消息
       if(bind(socketfd_, (sockaddr *)&si, sizeof si)<0)//绑定失败就直接退出
       {
           perror("bind error\n");
           exit(1);
       }
    }

4,启动函数

启动函数的作用如下:

1,接收客户端的请求。

2,将请求打印出来。

3,该函数是一个无限循环的函数。

使用recvfrom函数接收消息:

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

sockfd:服务器端的套接字描述符 。 

 buf:读取消息的缓冲区。

len:buf的长度 。                

flags:读取消息的方式。

src_addr:自定义的地址结构体,用于存放用户端的ip地址和port。

addrlen:srd_addr的长度。 

udp的服务是面向数据报的,也就是: SOCK_DGRAM。所以要使用recvfrom函数接收数据,而不能使用read。

使用sendto发消息:

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

sendto的参数和recvfrom的参数高度相似,这里就不一一介绍了。 

整体代码如下:

 void Run()
    {
        char inbuf[inbufSize];
        sockaddr_in si;
        socklen_t len;
        while (true)
        {
            int r1 = recvfrom(socketfd_, inbuf, sizeof inbuf-1, 0, (sockaddr *)&si, &len);//读取消息
            if(r1<0)//读取消息失败
            {
                perror("recvfrom error");
                exit(10);
            }

            inbuf[r1] = 0;
            std::string message = inbuf;
            std::string tostring = "client say#" + message;
            std::cout << message << std::endl;

            const char *response = "收到消息,正在处理";
            int r2 =  sendto(socketfd_, response, sizeof response, 0, (sockaddr *)&si, sizeof si);
            if(r2<0)
            {
                perror("server send message error");
                continue;
            }
        }
    }

写到这,服务端的代码就算结束了。接下来可以运行这个程序,然后使用netstat -naup指令查看当前服务器的挂起状态:

三,客户端

客户端的代码编写与服务端的代码编写其实差不多。客户端的代码做的事如下:

1,创建套接字。

2,自定义一个套接字结构体,并把该结构体的ip地址和端口号port初始化。

3,user写入消息。

4,将消息发送给服务端。

5,接收服务端的消息并打印出来。

代码如下:

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

#define outbufSize 1024

class Client
{
public:
public:
    Client()
        : port_(0), socketfd_(0)
    {
    }

    void Init(const std::string &ip, int port)
    {
        socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
        if (socketfd_ < 0)                          // 创建失败就退出进程
        {
            perror("socket error\n");
            exit(1);
        }

        port_ = port;
        ip_ = ip;
        // 不用bind
    }

    void Run()
    {
        std::string requestes;
        sockaddr_in si;
        socklen_t len;
        si.sin_family = AF_INET;
        si.sin_port = htons(port_);
        si.sin_addr.s_addr = inet_addr(ip_.c_str());

        char outbuf[outbufSize];
        while (true)
        {
            std::cout << "请输入内容>> ";
            std::getline(std::cin, requestes); // client输入内容
            if (sendto(socketfd_, requestes.c_str(), sizeof requestes, 0, (sockaddr *)&si, sizeof si) < 0) // 发送消息
            {
                continue;
            }

            int r3 = recvfrom(socketfd_, outbuf, sizeof outbuf - 1, 0, (sockaddr *)&si, &len);

            if(r3<0)
            {
                continue;
            }

            outbuf[r3] = 0;

            std::cout << "server say# " << outbuf << std::endl;

            memset(outbuf, 0, sizeof outbuf);
        }
    }

private:
    std::string ip_; // ip地址
    uint16_t port_;  // 端口号
    int socketfd_;   // 套接字描述符
};

客户端的代码与服务端代码基本相似。

四,main函数调用

main函数主要起调用作用,在linux中我们的调用方式一般都是命令行的方式。如何使用命令行呢?在这里就不得不提到main函数的另一种形式了,如下:

int main(int argc,char* argv[])

argc:表示命令行中的字符串的数量。

argv:表示字符串参数。

如命令:

cd udp

argc = 2

argv[0] = cd  argv[1]=udp

所以为了使用命令行的形式来调用服务端和客户端,我们的main函数也要写成这种形式。

Client.cc:

#include "Client.hpp"
#include <memory>

void usage(const std::string &porc)命令行的输入必须符合格式:./可执行程序 ip地址 端口号
{
    std::cout << porc << "ip:"
              << "port"
              << "[1024+]" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    std::string ip = argv[1];
    int port = atoi(argv[2]);
    std::unique_ptr<Client> Cp(new Client);
    Cp->Init(ip,port);
    Cp->Run();
    return 0;
}

Server.cc:

#include"Server.hpp"
#include<memory>

void usage(const std::string &porc)
{
    std::cout << porc << "ip:"
              << "port"
              << "[1024+]" << std::endl;
}

int main(int argc,char*argv[])
{
    if (argc != 2)//命令行的输入必须符合格式:./可执行程序  端口号
    {
        usage(argv[0]);
        exit(1);
    }
    
    int port = atoi(argv[1]);

    std::unique_ptr<UdpServer>Sp(new UdpServer);
    Sp->Init(port);
    Sp->Run();
    return 0;
}

 运行后结果如下:

调用时一定要保证客户端和服务端的端口号相同!!!


http://www.niftyadmin.cn/n/5439222.html

相关文章

Docker 相关内容记录

目录 1、安装docker 2、docker使用相关命令 具体说明如下&#xff1a;具体使用命令的时候最好切号为管理员root权限 使用su命令切号 1、安装docker docker下载地址&#xff1a; Index of / Docker Docs docker-compose和docker版本对应关系如下&#xff1a; Compose file …

Go语言之函数、方法、接口

一、函数 函数的基本语法&#xff1a; func 函数名&#xff08;形参列表&#xff09;&#xff08;返回值列表&#xff09; {执行语句...return 返回值列表 } 1.形参列表&#xff1a;表示函数的输入 2.函数中的语句&#xff1a;表示为了实现某一功能的代码块 3.函数可以有返回…

故障诊断 | 一文解决,RBF径向基神经网络的故障诊断(Matlab)

文章目录 效果一览文章概述专栏介绍模型描述源码设计参考资料效果一览 文章概述

【单点知识】基于实例讲解PyTorch中的Transforms类

文章目录 0. 前言1. 基本用法1.1 转换为Tensor1.2 图像大小调整1.3 随机裁剪1.4 中心裁剪1.5 随机翻转1.6 随机旋转1.7 填充1.8 组合变换 2. 进阶用法2.1 归一化2.2 色彩空间转换2.3 颜色抖动2.4 随机仿射2.5 透视变换2.6 自定义变换 0. 前言 按照国际惯例&#xff0c;首先声明…

基于模糊神经网络的移动机器人路径规划matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 移动机器人路径规划问题概述 4.2 模糊系统与模糊逻辑 5.完整程序 1.程序功能描述 基于模糊神经网络的移动机器人路径规划 1.环境地图中的障碍物为静态、未知障碍物&#xff0c;可以随…

Rust 中Self 关键字的两种不同用法

在 Rust 中&#xff0c;Self 是一个特殊的类型标识符&#xff0c;它代表了当前结构体或枚举类型。在结构体或枚举类型的定义中&#xff0c;Self 可以用于表示该类型的任何地方&#xff0c;包括方法签名、构造函数、类型别名等。 构造函数中的 Self&#xff1a; 在这段代码中&a…

Android Kotlin版封装EventBus

文章目录 Android Kotlin版封装EventBus代码封装添加依赖库定义消息类定义常量值定义注解定义工具类 使用在Activity中在Fragment中发送事件 源码下载 Android Kotlin版封装EventBus 代码封装 添加依赖库 implementation("org.greenrobot:eventbus:3.3.1")定义消息…

Spark-Scala语言实战(3)

在之前的文章中&#xff0c;我们学习了如何在来如何在IDEA离线和在线安装Scala&#xff0c;想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scala语言实…