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

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


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;
}

猜你喜欢

转载自blog.csdn.net/weixin_60478154/article/details/130534801