简单的Udp服务器

目录

简单的UDP网络程序

1.1 UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include <strings.h>
#include <functional>
#include <cstring>
#include <unordered_map>

const static int NUM = 1024;
const static string DEFAULT_IP = "0.0.0.0";
const static uint16_t DEFAULT_PORT=8080;

using func_t=function<string(string)>;

Log log;



class UdpServer
{
public:
    UdpServer(func_t func,uint16_t port=DEFAULT_PORT,string ip = DEFAULT_IP)
        : _ip(ip), _port(port), _sockid(-1),_func(func)
    {
    }

    ~UdpServer()
    {
        if (_sockid > 0)
        {
            close(_sockid);
        }
    }

    void Init()
    {
        // 创建套接字
        _sockid = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockid < 0)
        {
            log(Fatal, "socket failed");
            exit(2);
        }
        log(Info,"create socket successful, sockid:%d",_sockid);

        // 绑定
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        //绑定任意地址,可以接收任意发送给该主机的信息,而不是绑定一个具体的IP地址
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(_sockid, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            log(Fatal, "bind failed, errno:%d, error code:%s",errno,strerror(errno));
            exit(3);
        }
        log(Info, "Server bind successful");
    }

    //通过哈希表检查用户是否已经连上,如果没有就添加到连接的列表中
    void CheckUser(const struct sockaddr_in& client)
    {
        string clientIp=inet_ntoa(client.sin_addr);
        auto it=_online_client.find(clientIp);
        if(it==_online_client.end())
        {
            _online_client.insert({clientIp,client});
            std::cout << "[" << clientIp << ":" << ntohs(client.sin_port) << "] add to online user." << std::endl;
        }
    }

    //广播给所有人,即给所有连上该服务器的人都发送这条信息,类似于我们的微信群,
    //自己发出的信息所有人都能看见
    void BroadCast(const string& info,const string& clientip,const uint16_t& clientport)
    {
        for(const auto& it:_online_client)
        {
            string message="client";
            message+='[';
            message+="clientip:";
            message+=clientip;
            message+=' ';
            message+="clientport";
            message+=":";
            message+=to_string(clientport);
            message+="]# ";
            message+=info;

            sendto(_sockid,message.c_str(),message.size(),0,(struct sockaddr*)(&it.second),sizeof(it.second));
        }

    }

    //启动服务器
    void Run()
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        bzero(&client, sizeof(client));
        char buffer[NUM];
        bzero(buffer, sizeof(buffer));
        while (true)
        {
            ssize_t s = recvfrom(_sockid, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &len);
            if (s > 0)
            {
                buffer[s] = '\0';
                //cout << "client# " << buffer << endl;
                //printf("client[ip:%d,port:%d]# \n",client.sin_addr.s_addr,client.sin_port);
                char* clientip=inet_ntoa(client.sin_addr);
                //检查
                CheckUser(client);
                cout << "client[ip:"<<clientip<<" port:"<<ntohs(client.sin_port)<<"]# " << buffer << endl;

                uint16_t clientport=ntohs(client.sin_port);
                //广播给所有人
                BroadCast(buffer,clientip,clientport);
            }
            else if(s==0)
            {
                log(Warning,"client quit...");
                break;
            }
            else
            {
                log(Fatal,"recvfrom failed...");
                break;
            }

            //string ret=_func(buffer);
            //sendto(_sockid, ret.c_str(), ret.size(), 0, (struct sockaddr *)&client, len);
        }
    }

private:
    string _ip;
    uint16_t _port;
    int _sockid;
    //回调函数
    func_t _func;
    //通过IP地址映射标识一个已经连上服务器的客户端
    unordered_map<string,struct sockaddr_in> _online_client;
};

1.2 UdpClient.cc

cpp 复制代码
#include <iostream>
using namespace std;

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <pthread.h>
#include <cstring>

//   重定向:2>/dev/pts/(1,2,3,4)

//UdpClient.cc

const static int NUM = 1024;

//客户端使用手册
void Usage(string argv)
{
    cout << "\n\t"
         << "Usage:" << argv << " ServerIp ServerPort" << endl<<endl;
}

struct ThreadData
{
    int sockid;
    struct sockaddr_in server;
    string ip;
};

//读取信息
void* recver_message(void* argv)
{
    //线程分离
    pthread_detach(pthread_self());

    ThreadData* td=static_cast<ThreadData*>(argv);

    char buffer[4096];
    memset(buffer,0,sizeof(buffer));
    while(true)
    {
        struct sockaddr_in t;
        socklen_t len=sizeof(t);
        ssize_t s=recvfrom(td->sockid,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&t,&len);
        string tip=inet_ntoa(t.sin_addr);
        if(s>0)
        {
            buffer[s]='\0';
            // cout<<"server# "<<tmp<<endl;
            printf("server[ip:%s,port:%d]# %s\n",tip.c_str(),ntohs(t.sin_port),buffer);
        }
    }

    return nullptr;
}


//发送信息
void * sender_message(void* argv)
{
    pthread_detach(pthread_self());
    ThreadData* td=static_cast<ThreadData*>(argv);

    std::string welcome = td->ip;
    welcome += " comming...";
    sendto(td->sockid, welcome.c_str(), welcome.size(), 0, (struct sockaddr *)&(td->server), sizeof(td->server));

    string buffer;
    while(true)
    {
        cerr<<"Please Enter# ";
        getline(cin,buffer);
        sendto(td->sockid,buffer.c_str(),buffer.size(),0,(struct sockaddr*)(&(td->server)),sizeof(td->server));
    }
    return nullptr;
}

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

    string ServerIp=argv[1];
    string str = argv[2];
    uint16_t ServerPort = (uint16_t)stoi(str.c_str());

    ThreadData td;

    //创建套接字
    int sockid=socket(AF_INET,SOCK_DGRAM,0);

    td.server.sin_family = AF_INET;
    td.server.sin_addr.s_addr=inet_addr(ServerIp.c_str());
    td.server.sin_port=htons(ServerPort);
    td.ip=ServerIp;
    td.sockid=sockid;
    socklen_t len=sizeof(td.server);

    pthread_t recver,sender;
    pthread_create(&recver,nullptr,recver_message,&td);
    pthread_create(&sender,nullptr,sender_message,&td);    

    while(true)
    {
        sleep(1);
    }

    close(sockid);
}

1.3 main.cc

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>
#include "UdpServer.hpp"
#include <vector>
#include <memory>

//服务器的启动方式
void Usage(string argv)
{
    cout << "\n\t"
         << "Usage:" << argv << " ServerPort" << endl
         << endl;
}

string func(string s)
{
    return s + " already handled\n";
}

//安全检查
bool SafeCheck(const string &cmd)
{
    //把客户端发过来的信息当作命令来解析,检查该信息是否合法
    vector<string> key_word = {"rm", "mv", "cp", "kill", "sudo", "unlink", "uninstall",
                               "yum", "top", "while"};
    for(const auto& s:key_word)
    {
        auto pos = cmd.find(s);
        if(pos!=string::npos)
        {
            return false;
        }
    }

    return true;
}

//执行指令
string ExcuteCommand(string cmd)
{
    if (!SafeCheck(cmd))
    {
        return "bad man\n";
    }
    //popen函数会自己创建子进程,创建管道,让子进程执行cmd.c_str()命令,
    //并通过管道把执行cmd命令的结果读取到FILE*的结构体对象中
    FILE *p = popen(cmd.c_str(), "r");
    if (nullptr == p)
    {
        perror("popen failed");
        exit(5);
    }
    string ret="\n";
    char buffer[4096];
    while (true)
    {
        //把执行命令后的结果按行读取出来
        char *s = fgets(buffer, sizeof(buffer) - 1, p);
        if (nullptr == s)
        {
            break;
        }
        ret += buffer;
    }
    pclose(p);
    return ret;
}

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

    string str = argv[1];
    uint16_t ServerPort = (uint16_t)stoi(str.c_str());

    unique_ptr<UdpServer> svr(new UdpServer(func, ServerPort));
    svr->Init();
    svr->Run();

    return 0;
}

1.4 makefile

cpp 复制代码
.PHONY:all
all:Client Server

Client:UdpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread

Server:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f Client Server

1.5 log.hpp

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <time.h>
#include <stdarg.h>

// 日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define OneFile 2
//向多个文件打印
#define Classfile 3
#define SIZE 1024

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }

    void Enable(int mothod)
    {
        printMethod = mothod;
    }

    string LevelToString(int level)
    {
        switch (level)
        {
        case Info:
        {
            return "Info";
        }
        case Debug:
        {
            return "Debug";
        }
        case Warning:
        {
            return "Warning";
        }
        case Error:
        {
            return "Error";
        }
        case Fatal:
        {
            return "Fatal";
        }
        default:
        {
            return "None";
        }
        }
    }

    void printlog(int level,const string& logtxt)
    {
        switch(printMethod)
        {
        case Screen:
        {
            cout<<logtxt<<endl;
            break;
        }
        case OneFile:
        {
            PrintOneFile(LogFile,logtxt);
            break;
        }
        case Classfile:
        {
            PrintClassfile(level,logtxt);
            break;
        }
        default:
        {
            break;
        }
        }
    }

    void PrintOneFile(const string& logname,const string& logtxt)
    {
        string _logname=path+logname;
        int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd<0)
        {
            perror("open fail");
            return;
        }

        write(fd,logtxt.c_str(),logtxt.size());

        close(fd);

    }

    void PrintClassfile(int level,const string& logtxt)
    {
        string filename=LogFile;
        filename+='.';
        filename+=LevelToString(level);
        PrintOneFile(filename,logtxt);
    }

    void operator()(int level,const char* format,...)
    {
        time_t t=time(nullptr);
        struct tm* ctime=localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer,SIZE,"[%s][%d-%d-%d %d:%d:%d]",LevelToString(level).c_str(),
        ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,
        ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        va_list s;
        va_start(s,format);
        char rightbuffer[SIZE]={0};
        vsnprintf(rightbuffer,SIZE,format,s);
        va_end(s);


        char logtxt[SIZE*2];
        snprintf(logtxt,sizeof(logtxt),"%s %s",leftbuffer,rightbuffer);

        printlog(level,logtxt);
    }

    ~Log()
    {
    }

private:
    // 打印方法
    int printMethod;
    string path;
};
相关推荐
鲁正杰3 分钟前
在一个服务器上抓取 Docker 镜像并在另一个服务器上运行
运维·服务器·docker
煤泥做不到的!27 分钟前
挑战一个月基本掌握C++(第十一天)进阶文件,异常处理,动态内存
开发语言·c++
F-2H30 分钟前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
重生之我是数学王子32 分钟前
单片机 STM32入门
stm32·单片机·嵌入式硬件
aherhuo33 分钟前
基于openEuler22.09部署OpenStack Yoga云平台(一)
linux·运维·服务器·openstack
WebDeveloper200136 分钟前
如何使用美国域名中心US Domain Center和WordPress创建商业网站
运维·服务器·css·网络·html
axxy20001 小时前
leetcode之hot100---24两两交换链表中的节点(C++)
c++·leetcode·链表
檀越剑指大厂1 小时前
【Linux系列】Shell 脚本中的条件判断:`[ ]`与`[[ ]]`的比较
linux·运维·服务器
若亦_Royi2 小时前
C++ 的大括号的用法合集
开发语言·c++
2301_819287123 小时前
ce第六次作业
linux·运维·服务器·网络