【网络】UDP网络服务器简单模拟实现

news2024/11/18 3:25:11

【网络】UDP网络服务器简单模拟实现

文章目录

  • makefile
  • 服务端udpServer
    • udpServer.cc
    • udpServer.hpp
      • 初始化
      • 启动
      • 测试
  • 客户端udpClient
    • udpClient.cc
    • udpClient.hpp
      • 初始化
      • 启动
  • 整体代码

UDP的封装:

UDP网络服务器模拟实现:主要分为makefile文件进行编译

UDP客户端:udpClient.cc(客户端的调用),udpClient.hpp(客户端的实现)

UDP服务端:udpServer.cc(服务端的调用),udpServer.hpp(服务端的实现)

makefile

创建makefile文件:

makefile里可以定义变量,如cc=g++

image-20230505222243055

服务端udpServer

udpServer.cc

客户端进行调用的逻辑代码:构建udpServer的对象,然后进行初始化,在进行启动起来;调用逻辑如下:

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

using namespace std;
using namespace Server;

static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"  local_port\n\n";
}
//  ./udpServer  port
int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    std::unique_ptr<udpServer> usvr(new udpServer(port));

    usvr->initServer();
    usvr->start();
    return 0;
}

udpServer.hpp

客户端的实现代码逻辑:对外提供了一个初始化接口以及启动接口。

作为一款服务器:要有自己的服务端口号uint16_t _port,同时网络服务器需要有对应的string _ip地址,文件描述符_sockfd:进行各种各样的数据通信,在类内进行读写操作

对于ip地址的类型:字符串型只在我们用户层作为参数传递,这个不用去管,调用接口转换即可

image-20230505232656063

初始化

对于UDP服务器如何初始化:完成两步即可:1.创建套接字socket2.绑定端口号port和ip

1.创建套接字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);

参数据数domain:域,未来套接字是进行网络通信还是本地通信,主要使用下面这两种
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
参数type:套接字提供服务的类型,如SOCK_STREAM:流式服务TCP策略,SOCK_DGRAM:数据报服务,UDP策略

参数protocol:缺省为0,可由前两个类型确定

返回值:失败返回-1,成功返回文件描述符

2.绑定端口号和ip: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);

参数sockfd:文件描述符进行通信,也就是调用socket的返回值

参数addr:利用struct sockaddr_in强转

参数addrlen:结构体的长度

返回值:成功返回0,失败返回-1

定义一个sockaddr_in结构体填充数据,传递进去:

img

我们第三个参数也就是sockaddr_in结构体的内部:

struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

__SOCKADDR_COMMON的定义:

typedef unsigned short int sa_family_t;
#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

##是将字符串合并,也就是将接收到的sin_与family合并起来,形成了sin_family

创建结构体后要先清空数据(初始化),可以用memset,系统也提供了接口:

#include <strings.h>
void bzero(void *s, size_t n);

inet_addr:1.把字符串转化成整数;2.再把整数转化成对应的网络序列

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
//const char*cp:点分十进制风格的IP地址

代码实现

	void initServer()
        {
            //1.创建套接字
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success " << " : " << _sockfd << endl;
            //2.绑定端口号port,ip
            struct sockaddr_in local;//定义了一个变量
            //结构体清空
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;//协议家族
            local.sin_port=htons(_port);//给别人发消息,port和ip也要发给对方,需要大小端转换
            local.sin_addr.s_addr = inet_addr(_ip.c_str());//1.ip转成整数 2.整数要转成网络序列:htonl();
            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n==-1)
            {
                cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            //UDP Server 的预备工作完成
        }

启动

服务器的本质就是一个死循环,死循环不退出的就是常驻内存的进程。OS本质就是一个死循环

客户端发来的消息,服务端可能对这个消息做一点处理,然后发会给客户端;所以得先读取数据recvfrom

#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:特定的套接字,buf:读取到特定缓冲区,len:多长

flags:读取的方式,默认为0,阻塞读取

src_addr:收到消息除了本身,还得知道是谁发的,输入输出型参数,返回对应的消息内容是从哪一个client发来的,len:大小是多少

返回-1表示失败,成功返回字节数

同时我们得知道是谁发过来的,所以我们可以通过sockaddr_in这个结构体得知

        void start()
        {
            //服务器的本质其实就是一个死循环
            char buffer[gnum];
            for(;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);//必填
                ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                //1.数据是什么?2.谁发的
                if(s>0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
                    cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
                }

            }
        }

测试

在这里插入图片描述

本地环回:服务器代码的测试

./udpServer 127.0.0.1 8080

查看网络情况就可以用指令netstat

-a:显示所有连线中的Socket;
-e:显示网络其他相关信息;
-i:显示网络界面信息表单;
-l:显示监控中的服务器的Socket;
-n:直接使用ip地址(数字),而不通过域名服务器;
-p:显示正在使用Socket的程序识别码和程序名称;
-t:显示TCP传输协议的连线状况;
-u:显示UDP传输协议的连线状况;

image-20230506134553093

现在如果绑定云服务器IP地址:

./udpServer 43.143.177.75 8080

image-20230506134837052

云服务器是虚拟化的服务器,不能直接bind你的公网IP,可以绑定内网IP(ifconfig);如果是虚拟机或者独立真实的Linux环境,你可以bind你的IP;如何保证云服务器能够被别人访问:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,服务器IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,访问不出来,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind

image-20230506140814619

客户端udpClient

udpClient.cc

客户端如何去调用:./udpClient server_ip server_port,客户端想连接服务器,必须得知道对方的IP(公网IP);调用逻辑如下:

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

using namespace Client;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
    ucli->initClient();
    ucli->run();
    return 0;
}

udpClient.hpp

作为一个客户端:对外提供一个初始化接口,以及一个启动run接口

初始化

对于初始化接口:服务端有套接字,客户端也必须得有

  • 客户端不需要显示的bind

在服务端bind的时候,最重要的不是绑定IP,而是绑定端口,客户端需要显示地绑定端口是为了服务器未来要明确的port,不能随意改变

而客户端需要端口,但是不重要,自己有端口号就可以,不需要显示地绑定:写服务器的是一家公司,写client的是无数家公司;由OS自动形成端口进行bind

代码实现

       void initClient()
        {
            //客户端创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(2);
            }
            cout<<"socket success "<<" : "<<_sockfd<<endl;
            //2.client必须要bind,client不要显示地bind,不需要程序员自己bind,由OS自动形成端口号进行bind
        }

启动

对于run接口:需要发送数据,发送数据需要用到接口sendto

#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);

sockfd:哪个套接字,buf:缓冲区,len:长度,flags:0,有数据就发,没数据就阻塞

dest_addr:这个结构体也是由struct sockaddr_in强转,向谁发,填充对方的IP和端口

addrlen:没有指针,输入型参数

代码实现

	    void run()
        {
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);
            string message;
            while(!_quit)
            {
                cout<<"Please Enter# ";
                cin>>message;

                //发送给远端服务器
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
            }
        }

最后运行起来测试一下(这里是同一台主机之间测试,如果是不同的机器,我们传递参数的时候就要传递公网IP)

整体代码

//udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <functional>
namespace Server
{
    using namespace std;
    static const string defaultIP = "0.0.0.0";
    static const int gnum = 1024;
    enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};

    class udpServer
    {
    public:
        udpServer(const uint16_t&port,const string&ip = defaultIP)
        :_port(port),_ip(ip),_sockfd(-1)
        {}
        void initServer()
        {
            //1.创建套接字
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success " << " : " << _sockfd << endl;
            //2.绑定端口号port,ip
            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());
            local.sin_addr.s_addr = htons(INADDR_ANY);
            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n==-1)
            {
                cerr<<"bind error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
        }
        void start()
        {
            char buffer[gnum];
            for(;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(s>0)
                {
                    buffer[s] = 0;
                    string clientip = inet_ntoa(peer.sin_addr);//1.网络序列转化成本地序列2.4字节整数IP转化成点分十进制
                    uint16_t clientport = ntohs(peer.sin_port);
                    string message = buffer;
                    cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
                }
            }
        }
    private:
        uint16_t _port;//端口号
        string _ip;//ip地址
        int _sockfd;//文件描述符
    };
}

//udpServer.cc
#include "udpServer.hpp"
#include <memory>
using namespace std;
using namespace Server;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"  local_port\n\n";
}
//  ./udpServer  port
int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<udpServer> usvr(new udpServer(port));
    usvr->initServer();
    usvr->start();
    return 0;
}


//udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
namespace Client
{
    using namespace std;
    class udpClient
    {

    public:
        udpClient(const string&serverip,const uint16_t &serverport)
        :_serverip(serverip),_serverport(serverport),_sockfd(-1),_quit(false)
        {}
        void initClient()
        {
            //客户端创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"socket error: "<<errno<<" : "<<strerror(errno)<<endl;
                exit(2);
            }
            cout<<"socket success "<<" : "<<_sockfd<<endl;
        }
        void run()
        {
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);
            string message;
            while(!_quit)
            {
                cout<<"Please Enter# ";
                cin>>message;
                sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
            }
        }
    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;
    };
}

//udpClient.cc
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string  proc)
{
    cout<<"\nUsage:\n\t"<<proc<<" server_ip server_port\n\n";
}
//./udpCleint server_ip server_port
int main(int argc,char*argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
    ucli->initClient();
    ucli->run();
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java开发 - 不知道算不算详细的分布式事务详解

前言 前日对JUC进行了一个深度总结&#xff0c;不过现在博主能记得的也不多了&#xff0c;只是这东西&#xff0c;不是看几遍写几遍就能完全记住的&#xff0c;功夫在平时&#xff0c;很多知识点都需要反复的看&#xff0c;不光要看&#xff0c;还要用&#xff0c;这样才能了解…

在CentOS上安装Jenkins并配置Docker

文章目录 步骤1 - 安装Java 11步骤2 - 安装Jenkins步骤3 - 安装Docker步骤4 - 配置Docker Cloud步骤 5 - 验证步骤 6 - 可能会遇到的问题 在本教程中&#xff0c;我们将展示如何在CentOS上安装Jenkins和Docker&#xff0c;并将它们配置在同一台机器上&#xff0c;使Jenkins能够…

《花雕学AI》WeTab+ChatGPT:让浏览器变成你的智能助手

引言&#xff1a; 浏览器是我们日常使用的最重要的工具之一&#xff0c;它可以帮助我们获取信息、娱乐、学习、工作等。但是&#xff0c;传统的浏览器往往不能满足我们的个性化需求&#xff0c;也不能给我们提供智能化的服务。那么&#xff0c;有没有一种浏览器可以让我们的体…

yoloV2细节改进

文章目录 1 v2 细节升级概述2 .网络结构特点3. 架构细节解读4. 基于聚类来选择先验框尺寸5. 偏移量计算方法6. 坐标映射与还原7 感受野8. 特征融合的改进其他知识点filter 是什么&#xff1f; 1 v2 细节升级概述 2 .网络结构特点 使用dropout&#xff0c;杀死部分神经元&#…

Java集合之单列集合

分类 集合分为单列集合&#xff08;Collection&#xff09;和双列集合&#xff08;Map&#xff09; 单列集合的体系结构 List集合和Set集合的区别 List系列集合&#xff1a;添加元素是有序的&#xff08;添加的顺序&#xff0c;而非数据的大小顺序&#xff09;、可重复、有索引…

为什么在Ubuntu系统使用附加驱动更新Nvidia显卡驱动不起作用

1. 硬件环境 CPU&#xff1a;AMD Ryzen 9 5950x 16-core processor 32 GPU&#xff1a;双GeForce RTX 3090 操作系统&#xff1a;Ubuntu 22.04.2 LTS 64 位 主板&#xff1a;ASUS的ROG CROSSHAIR VIII EXTREME 2. 问题描述 使用上图所示的附加驱动程序更新Nvidia显卡驱动&am…

恢复item2和oh-my-zsh的配置

1. 首先正常安装item2 2. 加载onedrive里的传家宝iterm2_default_profile.json&#xff0c;让iterm2的配置生效 2. 然后正常安装oh-my-zsh (官方步骤&#xff1a; sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&q…

C# 学习abstract

abstract 顾名思义&#xff1a;抽象 从微软官方文档来看&#xff1a;abstract 修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract 修饰符来指示某个类仅用作其他类的基类&#xff0c;而不用于自行进行…

linux内核调试的几个方法

参考 以下内容&#xff1a; Linux 笔记&#xff1a; https://xuesong.blog.csdn.net/article/details/109522945?spm1001.2014.3001.5502 printk: printk在内核源码中用来记录日志信息的函数&#xff0c;只能在内核源码范围内使用。用法和printf非常相似&#xff1b; printk…

InsCode体验报告

文章目录 前言一、InsCode是什么&#xff1f;二、体验过程1.创建项目2.在线IDE3.运行和部署项目4.浏览和学习项目5.分享和协作项目6.支持AI助手 三、体验感受优点缺点 总结 官方宣传视频 InsCode-AI 前言 作为一个大三计算机专业的学生&#xff0c;我对编程有着浓厚的兴趣和热…

Triloga 的任务 — Satta 系列来袭!

谁战胜了这些凶兽&#xff0c;谁就获得了力量&#xff0c;让我们通过装备体现出来&#xff01;来自神秘洞穴的疯狂昆虫外壳&#xff0c;黄昏之地燃烧中的部落的骨质盔甲&#xff0c;以及深海的美妙灯光。 Triloga 的任务——Satta 系列已在 The Sandbox 市场平台上架&#xff1…

JVM(类的加载与ClassLoader、双亲委派机制)

文章目录 1. 类的生命周期2. 类的加载过程3. 类加载器&#xff08;classloader)3.1 类加载器的作用3.2 类加载器的分类(JDK8)3.3 双亲委派机制3.3.1 双亲委派机制优势 3.4 查看某个类的类加载器对象3.5 使用ClassLoader获取流 1. 类的生命周期 类在内存中完整的生命周期&#…

( “ 图 “ 之 并查集 ) 684. 冗余连接 ——【Leetcode每日一题】

❓684. 冗余连接 难度&#xff1a;中等 树可以看成是一个连通且 无环 的 无向 图。 给定往一棵 n 个节点 (节点值 1&#xff5e;n ) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间&#xff0c;且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n…

基于 EKS Fargate 搭建微服务性能分析系统

背景 近期 Amazon Fargate 在中国区正式落地&#xff0c;因 Fargate 使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&…

Java阶段二Day14

Java阶段二Day14 文章目录 Java阶段二Day14复习前日知识点SpringFramework版本SpringFramework核心SpringFramework创建工程SpringFramework相关概念bean对象的创建过程xml配置文件中的标签 基于XML管理bean对象类型属性的注入数组类型属性注入集合类型属性注入p命名空间引入外…

ConMask: Open-World Knowledge Graph Completion

目录 Abstract Introduction Model Relationship-Dependent Content Masking Target Fusion Loss Function [1711.03438] Open-World Knowledge Graph Completion (arxiv.org) Abstract 引入一个名为ConMask的开放世界KGC模型&#xff0c;该模型学习实体名称和部分文本…

数据结构与算法基础-学习-23-图之邻接矩阵与邻接表

目录 一、定义和术语 二、存储结构 1、邻接矩阵 1.1、邻接矩阵优点 1.2、邻接矩阵缺点 2、邻接表 3、邻接矩阵和邻接表的区别和用途 3.1、区别 3.2、用途 三、宏定义 四、结构体定义 1、邻接矩阵 2、邻接表 3、网数据类型&#xff08;造测试数据&#xff09; 五…

如何使用TRIZ理论来分析问题和解决问题?

文章目录 TRIZ基础现代TRIZ步骤 TRIZ基础 现代TRIZ 经典的TRIZ方法对专利进行分析,认为专利分为两个部分,一部分是需要解决的问题,一部分是解决问题的解决方案.首先是问题的分析,确定是否是初始问题,比如工具功能分析/特性传递等工具. 步骤 问题识别 主要是识别出初始问题;…

MATLAB实现建筑热平衡模型建立及节能温控方案

全球大约1/3的能源消耗于建筑。在能源紧张的今天&#xff0c;如何减少建筑的能源浪费是一个值得研究的课题。 本文在综合国内外建筑能耗模拟方法的基础上&#xff0c;采用热平衡法&#xff0c;针对一小型建筑建立了热特性仿真模型&#xff0c;选用武汉地区的气象数据&#xff…

JAVA11新特性

JAVA11新特性 概述 2018年9月26日,Oracle官方发布JAVA11.这是JAVA大版本周期变化后的第一个长期支持版本,非常值得关注.最新发布的JAVA11将带来ZGC HttpClient等重要特性,一共17个需要我们关注的JEP,参考文档http://openjdk.java.net/projects/jdk/11/ 181:基于嵌套的访问控制…