实用指南:Linux网络Socket编程UDP

Socket预备知识

IP在网络中,用来表示主机的唯一性,数据传输到主机不是目的,而是手段。到达主机内部,在交给主机内的进程, 才是目的。

端口号

端口号(port)是传输层协议的内容:

1.端口号是一个 2 字节 16 位的整数;

2.端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来 处理;

3.IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;

4.一个端口号只能被一个进程占用.

在解包的时候,传输层可以通过相应报头找到对应端口,把信息传输过去,进程通过端口接收。网络通信本质是进程间通信(IP+PORT可以标识互联网中唯一一个进程)。

将IP+PORT这种通信方式叫Socket。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。

服务器启动的时候一定要和一个port关联起来。

端口号范围划分

0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的 端口号都是固定的.

1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作 系统从这个范围分配的.

传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要发给谁"。

网络字节序

网络通信必须大端,即低地址高字节,不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据。

  1. 历史兼容性:早期网络协议由使用大端架构的机器(如IBM大型机)主导,大端序成为自然选择。
  2. 协议一致性:统一字节序可避免不同硬件架构的设备通信时因字节序差异导致的数据解析错误。
  3. 人类可读性:大端序与数字书写顺序一致(从左到右为高位到低位),便于调试和抓包分析。
  4. 数据对齐:大端序在协议头部解析时(如IP地址、端口号),可直接按顺序读取,无需频繁转换。

为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:

#include 
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t hostlong);
uint16_t ntohs(uint16_t hostshort);

Socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6,以及 UNIX Domain Socket。然而,各种网络协议的地址格式并不相同。

UDP网络编程

socket接口:

domain:

type:

第二个无连接,不可靠就是UDP了。

成功返回一个新的文件描述符,失败-1:

地址转换函数

将sin_addr转换成字符串:

inet_ntoa 把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆 盖掉上一次的结果,因此线程不安全,建议用inet_ntop。

将字符串转换为sin_addr:

其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr,因此函数接口是 void *addrptr。

绑定端口

sockaddr的头文件是#include<netinet/in.h>

查看绑定的udp:

netstat -upa

此处的6666端口就是我们启动的服务。

接收数据

发送数据

注意:云服务器上不能直接绑定公网IP,但是也不能绑定内网IP。

UDP编程案例

回显服务器

客户端给服务器发信息,服务器发送同样的信息给客户端。

//Log.hpp
#pragma
#include
#include
#include
#include
#include
#include
#include
enum
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};
const std::string logfile = "log.txt";
pthread_mutex_t _mutex;
#define SCREEN_TYPE 1
#define FILE_TYPE 2
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
        break;
    case INFO:
        return "INFO";
        break;
    case ERROR:
        return "ERROR";
        break;
    case WARNING:
        return "WARNING";
        break;
    case FATAL:
        return "FATAL";
        break;
    default:
        return "UNKONW";
        break;
    }
}
std::string GetTime()
{
    time_t now = time(nullptr);
    struct tm *curr = localtime(&now);
    char buf[128];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d",
             curr->tm_year+1900,
             curr->tm_mon+1,
             curr->tm_mday,
             curr->tm_hour,
             curr->tm_min,
             curr->tm_sec);
    return buf;
}
class LockGuard
{
public:
    LockGuard(pthread_mutex_t* td):_td(td)
    {
        pthread_mutex_lock(_td);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_td);
    }
private:
    pthread_mutex_t *_td;
};
class Logmessage
{
public:
    std::string _level;
    pid_t _id;
    std::string _filename;
    int _filenumber;//行号
    std::string _curr_time;
    std::string _message_info;
};
class Log
{
public:
    Log(const std::string& filename=logfile):_logfile(filename)
    {}
    void Enable(int type)
    {
        _type = type;
    }
    void FlushToScreen(Logmessage& lg)
    {
        printf("[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
    }
    void FlushToFile(Logmessage& lg)
    {
        std::ofstream t(_logfile,std::ios::app);
        if(!t.is_open())
            return;
        char logtxt[1024];
        snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
        t.write(logtxt, strlen(logtxt));
        t.close();
    }
    void FlushLog(Logmessage& lg)
    {
        LockGuard ld(&_mutex);
        // 此处可以加过滤,本代码没加
        if(_type==SCREEN_TYPE)
        {
            FlushToScreen(lg);
        }
        else if(_type==FILE_TYPE)
        {
            FlushToFile(lg);
        }
    }
    void logmessage(int level,std::string filename,int filenumber,const char* format,...)
    {
        Logmessage lg;
        lg._level = LevelToString(level);
        lg._id = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;
        lg._curr_time = GetTime();
        va_list ap;
        va_start(ap, format);
        char info[512];
        vsnprintf(info, sizeof(info), format, ap);
        va_end(ap);
        lg._message_info = info;
        FlushLog(lg);
    }
    ~Log(){}
private:
    int _type=SCREEN_TYPE;
    std::string _logfile;
};
Log lg;
#define LOG(level,format,...) do{lg.logmessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__);}while (0)
#define EnableScreen() do{ lg.Enable(SCREEN_TYPE);} while (0)
#define EnableFILE() do{ lg.Enable(FILE_TYPE);} while (0)
//nocopy.hpp
#pragma
#include
class nocopy
{
public:
    nocopy() {}
    nocopy(const nocopy &) = delete;
    const nocopy &operator=(const nocopy &) = delete;
    ~nocopy(){}
};
//InetAddr.hpp
#pragma
#include
#include
#include
#include
#include
#include
using namespace std;
class InetAddr
{
private:
    void ToHost()
    {
        _port = ntohs(_addr.sin_port);
        _ip = inet_ntoa(_addr.sin_addr);
    }
public:
    InetAddr(const struct sockaddr_in& addr):_addr(addr)
    {
        ToHost();
    }
    string Ip() { return _ip; }
    uint16_t Port() { return _port; }
private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};
//UdpServer.hpp
#pragma
#include"InetAddr.hpp"
#include
#include"nocopy.hpp"
#include
#include
#include
#include
#include
#include"Log.hpp"
#include
using namespace std;
static const int gsockfd = -1;
static const int glocalport = 6666;
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};
class UdpServer : public nocopy
{
public:
    // UdpServer(const string &localip,const int localport=glocalport):_sockfd(gsockfd),_localport(localport),_locapip(localip)
    // {}
    UdpServer(const int localport=glocalport):_sockfd(gsockfd),_localport(localport)
    {}
    void InitServer()
    {
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd<0)
        {
            LOG(FATAL, "socket error\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success,fd:%d\n", _sockfd);
        //绑定端口号
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        //local.sin_addr.s_addr = inet_addr(_localip.c_str());//转4字节IP,需要网络序列的IP
        local.sin_addr.s_addr=INADDR_ANY;//不能绑定固定IP,否则只能收到这个IP发的信息
        int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success\n");
    }
    void Start()
    {
        _isrunning = true;
        char buf[512];
        while (1)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
            if(n>0)
            {
                buf[n] = 0;
                InetAddr ad(peer);
                cout << '[' << ad.Ip() << ':' << ad.Port() << "]:" << buf << endl;
                string msg = "[udpserver-echo]:";
                msg += buf;
                sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                LOG(ERROR, "receive error\n");
            }
        }
    }
    ~UdpServer()
    {
        if(_sockfd>gsockfd)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    int _localport;
    //string _localip;
    bool _isrunning = false;
};
//UdpServerMain.hpp
#include"UdpServer.hpp"
#include
using namespace std;
int main()
{
    EnableScreen();
    //string ip = "0";//一般设为0,bind了任意IP,只要端口是这个就都能接收到
    uint16_t port = glocalport;
    unique_ptr usvr = make_unique(port);
    usvr->InitServer();
    usvr->Start();
    return 0;
}
//UdpClientMain.hpp
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cerr << "usage:" << argv[0] << "error" << endl;
        exit(0);
    }
    string serverip = argv[1];
    uint16_t serverport = htons(stoi(argv[2]));
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    server.sin_port = serverport;
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "create socket error"<< endl;
        exit(1);
    }
    // 客户端一定要知道服务器的ip和端口
    // 客户端的端口号一般不由用户自己设定,而是OS随机选择,服务器一定要自己设定
    // 客户端首次向服务器发送数据的时候,OS会自动给client绑定
    while (1)
    {
        string msg;
        cout << "enter:";
        std::getline(std::cin, msg);
        int n = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&server, sizeof(server));
        if (n > 0)
        {
            char buf[512];
            struct sockaddr_in serverin;
            socklen_t len;
            int m = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&serverin, &len);
            if (m > 0)
            {
                buf[m] = 0;
                cout << buf << endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }
    ::close(sockfd);
}
//makefile
.phony:all
all:udpserver udpclient
udpclient:UdpClientMain.cpp
	g++ -o $@ $^ -std=c++14
udpserver:UdpServerMain.cpp
	g++ -o $@ $^ -std=c++14
.phony:clean
clean:
	rm -rf udpserver udpclient

字典翻译服务器

客户端发送一个英文单词,服务器返回对应的中文,就是在上面回显服务器把回显业务逻辑改为翻译,在此基础上改一下服务器:

//UdpServer.hpp
#pragma
#include"InetAddr.hpp"
#include
#include"nocopy.hpp"
#include
#include
#include
#include
#include
#include"Log.hpp"
#include
#include
using namespace std;
static const int gsockfd = -1;
static const int glocalport = 6666;
using func_t = function;
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};
class UdpServer : public nocopy
{
public:
    // UdpServer(const string &localip,const int localport=glocalport):_sockfd(gsockfd),_localport(localport),_locapip(localip)
    // {}
    UdpServer(func_t func,const int localport=glocalport):_sockfd(gsockfd),_localport(localport),_func(func)
    {}
    void InitServer()
    {
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd<0)
        {
            LOG(FATAL, "socket error\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success,fd:%d\n", _sockfd);
        //绑定端口号
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        //local.sin_addr.s_addr = inet_addr(_localip.c_str());//转4字节IP,需要网络序列的IP
        local.sin_addr.s_addr=INADDR_ANY;//不能绑定固定IP,否则只能收到这个IP发的信息
        int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success\n");
    }
    void Start()
    {
        _isrunning = true;
        char buf[512];
        while (1)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
            if(n>0)
            {
                buf[n] = 0;
                InetAddr ad(peer);
                cout << '[' << ad.Ip() << ':' << ad.Port() << "]:" << buf << endl;
                string msg = _func(buf);
                msg = "[server]:" + msg;
                sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                LOG(ERROR, "receive error\n");
            }
        }
    }
    ~UdpServer()
    {
        if(_sockfd>gsockfd)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    int _localport;
    //string _localip;
    bool _isrunning = false;
    func_t _func;
};
//Dict.hpp
#pragma
#include 
#include 
#include 
#include 
#include"Log.hpp"
const static std::string sep = ": ";
class Dict
{
public:
    void LoadDict()
    {
        std::ifstream in(_path);
        if(!in.is_open())
        {
            LOG(FATAL, "open %s failed\n", _path);
            exit(1);
        }
        std::string line;
        while (std::getline(in,line))
        {
            LOG(INFO, "load info:%s\n", line);
            if(line.empty())
            continue;
            auto pos = line.find(sep);
            if(pos==std::string::npos)
                continue;
            std::string key = line.substr(0, pos);
            if(key.empty())
            continue;
            std::string value = line.substr(pos+sep.size());
            if(value.empty())
            continue;
            _dict.insert(std::make_pair(key, value));
        }
        LOG(INFO, "load dict success\n");
        in.close();
    }
public:
    Dict(const std::string& path):_path(path)
    {
        LoadDict();
    }
    ~Dict() {}
    std::string Translate(std::string word)
    {
        if(word.empty())
            return "None";
        auto iter = _dict.find(word);
        if(iter==_dict.end())
            return "Unfind";
        else
            return iter->second;
    }
private:
    std::unordered_map _dict;
    std::string _path;
};
//UdpServerMain.cpp
#include"Dict.hpp"
#include"UdpServer.hpp"
#include
using namespace std;
int main()
{
    EnableScreen();
    Dict dict("./dict.txt");
    func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1);
    // string ip = "0";//一般设为0,bind了任意IP,只要端口是这个就都能接收到
    uint16_t port = glocalport;
    unique_ptr usvr = make_unique(translate,port);
    usvr->InitServer();
    usvr->Start();
    return 0;
}
//dict.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

客户端和日志还是用上面那一个。

最后使用结果:

网络聊天室

以上都是单进程的,聊天室需要多线程,这次把之前写的线程池带进来,用来转发信息。为了每个用户都看到信息,我们需要维护一个在线用户列表。将转发信息做成路由转发模块,使用在线用户列表转发信息。

对于客户端,需要一个线程发消息,一个线程接收消息。

//InetAddr.hpp
#pragma once
#include
#include
#include
#include
#include
#include
using namespace std;
class InetAddr
{
private:
    void ToHost()
    {
        _port = ntohs(_addr.sin_port);
        //_ip = inet_ntoa(_addr.sin_addr);//但是这个不是线程安全程度
        char ip[32];
        ::inet_ntop(AF_INET, &_addr.sin_addr, ip, sizeof(ip));
        _ip = ip;
    }
public:
    InetAddr(const struct sockaddr_in& addr):_addr(addr)
    {
        ToHost();
    }
    string Ip() { return _ip; }
    uint16_t Port() { return _port; }
    struct sockaddr_in Addr()
    {
        return _addr;
    }
    bool operator ==(const InetAddr& addr)
    {
        //运行一个ip可以开多个客户端
        if(addr._ip==_ip&&addr._port==_port)
        {
            return true;
        }
        return false;
    }
private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};
//Log.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
enum
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};
const std::string logfile = "log.txt";
pthread_mutex_t _mutex;
#define SCREEN_TYPE 1
#define FILE_TYPE 2
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
        break;
    case INFO:
        return "INFO";
        break;
    case ERROR:
        return "ERROR";
        break;
    case WARNING:
        return "WARNING";
        break;
    case FATAL:
        return "FATAL";
        break;
    default:
        return "UNKONW";
        break;
    }
}
std::string GetTime()
{
    time_t now = time(nullptr);
    struct tm *curr = localtime(&now);
    char buf[128];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d",
             curr->tm_year+1900,
             curr->tm_mon+1,
             curr->tm_mday,
             curr->tm_hour,
             curr->tm_min,
             curr->tm_sec);
    return buf;
}
class LockGuard
{
public:
    LockGuard(pthread_mutex_t* td):_td(td)
    {
        pthread_mutex_lock(_td);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_td);
    }
private:
    pthread_mutex_t *_td;
};
class Logmessage
{
public:
    std::string _level;
    pid_t _id;
    std::string _filename;
    int _filenumber;//行号
    std::string _curr_time;
    std::string _message_info;
};
class Log
{
public:
    Log(const std::string& filename=logfile):_logfile(filename)
    {}
    void Enable(int type)
    {
        _type = type;
    }
    void FlushToScreen(Logmessage& lg)
    {
        printf("[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
    }
    void FlushToFile(Logmessage& lg)
    {
        std::ofstream t(_logfile,std::ios::app);
        if(!t.is_open())
            return;
        char logtxt[1024];
        snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
        t.write(logtxt, strlen(logtxt));
        t.close();
    }
    void FlushLog(Logmessage& lg)
    {
        LockGuard ld(&_mutex);
        // 此处可以加过滤,本代码没加
        if(_type==SCREEN_TYPE)
        {
            FlushToScreen(lg);
        }
        else if(_type==FILE_TYPE)
        {
            FlushToFile(lg);
        }
    }
    void logmessage(int level,std::string filename,int filenumber,const char* format,...)
    {
        Logmessage lg;
        lg._level = LevelToString(level);
        lg._id = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;
        lg._curr_time = GetTime();
        va_list ap;
        va_start(ap, format);
        char info[512];
        vsnprintf(info, sizeof(info), format, ap);
        va_end(ap);
        lg._message_info = info;
        FlushLog(lg);
    }
    ~Log(){}
private:
    int _type=SCREEN_TYPE;
    std::string _logfile;
};
Log lg;
#define LOG(level,format,...) do{lg.logmessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__);}while (0)
#define EnableScreen() do{ lg.Enable(SCREEN_TYPE);} while (0)
#define EnableFILE() do{ lg.Enable(FILE_TYPE);} while (0)
//nocopy.hpp
#pragma
#include
class nocopy
{
public:
    nocopy() {}
    nocopy(const nocopy &) = delete;
    const nocopy &operator=(const nocopy &) = delete;
    ~nocopy(){}
};
//Route.hpp
#pragma once
#include 
#include 
#include 
#include
#include "InetAddr.hpp"
#include"ThreadPool.hpp"
using task_t = function;
class Route
{
public:
    Route(){
        pthread_mutex_init(&_mutex, nullptr);
    }
    bool CheckOnlineUsr(InetAddr &who)
    {
        LockGuard t(&_mutex);
        for (auto &usr : _online_usr)
        {
            if (usr == who)
                return true;
        }
        return false;
    }
    void OffLine(InetAddr &who)
    {
        LockGuard t(&_mutex);
        auto it = _online_usr.begin();
        for (; it != _online_usr.end();it++)
        {
            if(*it==who)
            {
                _online_usr.erase(it);
                break;
            }
        }
    }
    void ForwardHelper(int sockfd,const string messages)
    {
        for(auto &usr:_online_usr)
        {
            struct sockaddr_in peer = usr.Addr();
            int n=sendto(sockfd, messages.c_str(), messages.size(), 0, (struct sockaddr *)&peer,sizeof(peer));
            if(n>0) LOG(DEBUG, "send ok\n");
        }
    }
    void Forward(int sockfd, const string &msg, InetAddr &who)
    {
        // 检查是否在用户列表
        if(!CheckOnlineUsr(who))
        {
            _online_usr.push_back(who);
            LOG(DEBUG, "addusr\n");
        }
        if(msg=="QUIT"||msg=="Q")
        {
            OffLine(who);
        }
        string messages='['+who.Ip()+":"+to_string(who.Port())+"]:"+msg;
        task_t t = bind(&Route::ForwardHelper, this, sockfd, messages);
        ThreadPool::GetThreadPool()->Enqueue(t);
        LOG(DEBUG, "ok\n");
    }
    ~Route() {
        pthread_mutex_destroy(&_mutex);
    }
private:
    vector _online_usr;
    pthread_mutex_t _mutex;
};
//Thread.hpp
#pragma once
#include
#include
#include
using namespace std;
using tfunc_t = function;
class Thread
{
public:
    Thread(std::string name,tfunc_t func)
    {
        this->name = name;
        this->func = func;
    }
    void Excute()
    {
        is_running = true;
        func(name);
        is_running = false;
    }
    std::string Status()
    {
        if(is_running)
            return "running";
        else
            return "sleep";
    }
    static void* ThreadRoutime(void* args)
    {
        Thread *self = static_cast(args);
        self->Excute();
        return nullptr;
    }
    bool Start()
    {
        int n=::pthread_create(&tid, nullptr, ThreadRoutime, this);
        if(n!=0)
            return false;
        else
            return true;
    }
    void Stop()
    {
        if(is_running)
        {
            is_running = false;
            ::pthread_cancel(tid);
        }
    }
    void Join()
    {
        if(is_running)
        {
            ::pthread_join(tid, nullptr);
        }
    }
    ~Thread()
    {
        Stop();
    }
private:
    std::string name;
    pthread_t tid;
    bool is_running;
    tfunc_t func;
};
//ThreadPool.hpp
#pragma once
#include
#include
#include
#include"Thread.hpp"
#include
#include
#include"Log.hpp"
using namespace std;
static const int defaultnum = 5;
template 
class ThreadPool
{
private:
//懒汉模式
    ThreadPool(int thread_num=defaultnum):thread_num(thread_num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool &) = delete;
    void operator=(const ThreadPool &) = delete;
    void LockQ()
    {
        pthread_mutex_lock(&_mutex);
    }
    void UnlockQ()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void WakeUp()
    {
        pthread_cond_signal(&_cond);
    }
    void WakeAll()
    {
        pthread_cond_broadcast(&_cond);
    }
    void Sleep()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }
    bool isEmpty()
    {
        return task_queue.empty();
    }
     void Init()
    {
        tfunc_t func = bind(&ThreadPool::Handler, this,placeholders::_1);
        for (int i = 0; i < thread_num; i++)
        {
            string name = "thread-" + to_string(i + 1);
            _threads.emplace_back(name,func);
        }
    }
    void Start()
    {
        isrunning = true;
        for (auto &thread : _threads)
        {
            thread.Start();
        }
    }
    void Handler(const string name)
    {
        while(1)
        {
            LockQ();
            while(isEmpty()&&isrunning)
            {
                sleep_num++;
                Sleep();
                sleep_num--;
            }
            //没任务了而且想退出
            if(!isrunning&&isEmpty())
            {
                LOG(DEBUG,"%s out\n",name.c_str());
                UnlockQ();
                break;
            }
            //有任务
            T t = task_queue.front();
            task_queue.pop();
            UnlockQ();
            t();//不用在临界区,相当于隔绝,自己去处理
        // 检查是否在用户列表
            LOG(DEBUG,"%s finish\n",name.c_str());
        }
    }
public:
//懒汉模式
    static ThreadPool* GetThreadPool()
    {
        //保证单例就一个且获取安全
        if(_tp==nullptr)
        {
            LockGuard(&ThreadPool::sig_mutex);
            if (_tp == nullptr)
            {
                LOG(INFO, "create ThreadPool\n");
                _tp = new ThreadPool();
                _tp->Init();
                _tp->Start();
            }
        }
        return _tp;
    }
    void Stop()
    {
        LockQ();
        isrunning = false;
        WakeAll();
        UnlockQ();
    }
    void Enqueue(const T& in )
    {
        LockQ();
        if(isrunning)
        {
            task_queue.push(in);
            if(sleep_num>0)
            WakeUp();
        }
        UnlockQ();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    int thread_num;
    vector _threads;
    queue task_queue;
    bool isrunning;
    pthread_mutex_t _mutex;//对task_queue保护
    pthread_cond_t _cond;
    int sleep_num=0 ;
    static ThreadPool *_tp;
    static pthread_mutex_t sig_mutex;
};
template 
ThreadPool *ThreadPool::_tp = nullptr;
template 
pthread_mutex_t ThreadPool::sig_mutex = PTHREAD_MUTEX_INITIALIZER;
//UdpServer.hpp
#pragma once
#include"InetAddr.hpp"
#include
#include"nocopy.hpp"
#include
#include
#include
#include
#include
#include"Log.hpp"
#include
#include
using namespace std;
static const int gsockfd = -1;
static const int glocalport = 6666;
using func_t = std::function;
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};
class UdpServer : public nocopy
{
public:
    // UdpServer(const string &localip,const int localport=glocalport):_sockfd(gsockfd),_localport(localport),_locapip(localip)
    // {}
    UdpServer(func_t func,const int localport=glocalport):_func(func),_sockfd(gsockfd),_localport(localport)
    {}
    void InitServer()
    {
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd<0)
        {
            LOG(FATAL, "socket error\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success,fd:%d\n", _sockfd);
        //绑定端口号
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        //local.sin_addr.s_addr = inet_addr(_localip.c_str());//转4字节IP,需要网络序列的IP
        local.sin_addr.s_addr=INADDR_ANY;//不能绑定固定IP,否则只能收到这个IP发的信息
        int n = ::bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success\n");
    }
    void Start()
    {
        _isrunning = true;
        char buf[512];
        while (1)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
            if(n>0)
            {
                buf[n] = 0;
                InetAddr ad(peer);
                cout << '[' << ad.Ip() << ':' << ad.Port() << "]:" << buf << endl;
                _func(_sockfd, buf, ad);
            }
            else
            {
                LOG(ERROR, "receive error\n");
            }
        }
    }
    ~UdpServer()
    {
        if(_sockfd>gsockfd)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    int _localport;
    //string _localip;
    bool _isrunning = false;
    func_t _func;
};
//UdpServerMain.cpp
#include "UdpServer.hpp"
#include 
#include "Route.hpp"
using namespace std;
int main()
{
    EnableScreen();
    // string ip = "0";//一般设为0,bind了任意IP,只要端口是这个就都能接收到
    uint16_t port = glocalport;
    Route troute;
    func_t message_route = std::bind(&Route::Forward, &troute, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    unique_ptr usvr = make_unique(message_route, port);
    usvr->InitServer();
    usvr->Start();
    return 0;
}
//UdpClientMain.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include"Thread.hpp"
#include"InetAddr.hpp"
using namespace std;
int InitClient()
{
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "create socket error"<< endl;
        exit(1);
    }
    return sockfd;
}
void Send(int sockfd,string serverip,uint16_t serverport,const string& name)
{
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    server.sin_port = serverport;
     while (1)
    {
        string msg;
        std::getline(std::cin, msg);
        int n = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&server, sizeof(server));
        if (n <= 0)
        {
            break;
        }
    }
}
void Receive(int sockfd,const string& name)
{
    while(1)
    {
        char buf[512];
        struct sockaddr_in usr;
        socklen_t len;
        int m = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&usr, &len);
        if (m > 0)
        {
            buf[m] = 0;
            cout << endl;
            cout << buf << endl;
        }
        else
        {
            break;
        }
    }
}
int main(int argc, char *argv[])
{
     if (argc != 3)
    {
        cerr << "usage:" << argv[0] << "error" << endl;
        exit(0);
    }
    string serverip = argv[1];
    uint16_t serverport = htons(stoi(argv[2]));
    int sockfd = InitClient();
    tfunc_t rvf = bind(&Receive, sockfd, placeholders::_1);
    tfunc_t sdf = bind(&Send, sockfd, serverip, serverport, placeholders::_1);
    Thread rv("receiver-thread", rvf);
    Thread sd("send-thread",sdf);
    rv.Start();
    sd.Start();
    rv.Join();
    sd.Join();
    // 客户端一定要知道服务器的ip和端口
    // 客户端的端口号一般不由用户自己设定,而是OS随机选择,服务器一定要自己设定
    // 客户端首次向服务器发送数据的时候,OS会自动给client绑定
    ::close(sockfd);
}
//makefile
.phony:all
all:udpserver udpclient
udpclient:UdpClientMain.cpp
	g++ -o $@ $^ -std=c++14 -l pthread
udpserver:UdpServerMain.cpp
	g++ -o $@ $^ -std=c++14 -l pthread
.phony:clean
clean:
	rm -rf udpserver udpclient

posted @ 2025-10-28 11:20  clnchanpin  阅读(1)  评论(0)    收藏  举报