udp数据转发代理

一、代码
​#ifndef __WIN32__

#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define S_CLOSE close
#define S_READ read
#define S_WRITE write
#else
#include <winsock2.h>
#define socklen_t int
#define S_CLOSE(s) closesocket(s)
#define S_READ(fd, buf, len) recv(fd, buf, len, 0)
#define S_WRITE(fd, buf, len) send(fd, buf, len, 0)
#endif


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <set>


#define UDPF_KEEP 0x00000001


struct udpiocb {
    int flags;
    int udpio_fd;
    time_t last_active;
    struct sockaddr_in udpio_addr;
};


/*
 * 运算符重载
 */
bool operator < (const struct udpiocb & a, const struct udpiocb & b)
{
    struct sockaddr_in addr_a, addr_b;
    addr_a = a.udpio_addr;
    addr_b = b.udpio_addr;


    if (addr_a.sin_port == addr_b.sin_port)
        return (addr_a.sin_addr.s_addr < addr_b.sin_addr.s_addr);


    return (addr_a.sin_port < addr_b.sin_port);
}


/*
 * std::set是标准库的关联容器,实现内部元素进行了排序,使用这特性可以对一组元素进行插入排序。
 * std::set可以认为是数学中“集合”的概念,它提供的接口也是如此。
 */
static std::set<udpiocb> udpio_list;


/*
 * 创建绑定到指定端口的socket,
 * 并将其插入到集合中;
 */
int udpio_add(u_long addr, u_short port)
{
    int error;
    struct udpiocb iocb;
    struct sockaddr_in addr_in1;
    
    /* 创建一个socket*/
    int s_udp = socket(PF_INET, SOCK_DGRAM, 0);
    assert(s_udp != -1);


    /* 初始化struct  sockaddr_in */
    addr_in1.sin_family = AF_INET;
    addr_in1.sin_port = htons(port);
    addr_in1.sin_addr.s_addr = htonl(INADDR_ANY);
    
    /* 将socket 绑定到指定端口 */
    error = bind(s_udp, (struct sockaddr *)&addr_in1, sizeof(addr_in1));
    assert(error == 0);


    /* 将当前的socket插入到udpio_list中 */
    iocb.flags = UDPF_KEEP;
    iocb.udpio_fd = s_udp;
    iocb.udpio_addr = addr_in1;
    iocb.udpio_addr.sin_addr.s_addr = addr;
    udpio_list.insert(iocb);


    return 0;
}


/*
 *
 */
int udpio_final(void)
{
    std::set<udpiocb>::const_iterator iter;
    iter = udpio_list.begin();
    while (iter != udpio_list.end()) {
        S_CLOSE(iter->udpio_fd);
        ++iter;
    }
    return 0;
}


/*
 * 分配
 */
int udpio_realloc(const struct sockaddr_in & addr)
{
    struct udpiocb iocb;
    iocb.flags = 0;
    iocb.udpio_addr = addr;


    std::set<udpiocb>::iterator iter;
    
    /* 查找发送数据方的地址和端口是否在集合中 */
    iter = udpio_list.find(iocb);


    /* 如果发送数据方的地址和端口在集合中 */
    if (iter != udpio_list.end()) {
        iocb = *iter;
        time(&iocb.last_active);
        udpio_list.erase(iter);
        udpio_list.insert(iocb);


        return iocb.udpio_fd;
    }


    /* 创建这个 socket, 并添加到集合中 */
    iocb.udpio_fd = socket(AF_INET, SOCK_DGRAM, 0);
    time(&iocb.last_active);
    udpio_list.insert(iocb);


    return iocb.udpio_fd;
}


/*
 * 数据接收并转发
 */
int udpio_event(fd_set * readfds, fd_set * writefds, fd_set * errorfds)
{
    int fd, len;
    char buf[4096];
    int addr_len1;
    struct sockaddr_in addr_in1;


    std::set<udpiocb>::iterator iter;


    iter = udpio_list.begin();
    while (iter != udpio_list.end()) {
        if (FD_ISSET(iter->udpio_fd, readfds)) {
            /* 从活动的socket中接收数据 */
            addr_len1 = sizeof(addr_in1);


            /* 
             * 接收从远端主机经指定socket传来的数据,
             * 并把数据存到由参数buf指定的内存空间,
             * addr_in1 为发送数据方的网络地址和端口;
             */
            len = recvfrom(iter->udpio_fd, buf, sizeof(buf), 0,
                     (struct sockaddr *)&addr_in1, &addr_len1);


            /*
             * 将接收方的IP和端口记录在集合中,
             * 如果有断后再连则将原来的socket收回,并分配新的;
             */
            fd = udpio_realloc(addr_in1);


            /* 将数据转码到指定的地址 */
            sendto(fd, buf, len, 0, (struct sockaddr *)&(iter->udpio_addr),
                     sizeof(iter->udpio_addr));
        }
        ++iter;
    }
    return 0;
}


/*
 * 将超过60秒未活动的socket回收
 */
int udpio_collect(time_t current)
{
    int count = udpio_list.size();
    std::set<udpiocb>::iterator iter;


    iter = udpio_list.begin();
    while (iter != udpio_list.end()) {
        if (iter->last_active + 60 < current &&
                (iter->flags & UDPF_KEEP) == 0) {
            closesocket(iter->udpio_fd);
            udpio_list.erase(iter++);
            continue;
        }
         ++iter;
    }


    if (udpio_list.size() != count)
        printf("udpio collect: %d %d\n", udpio_list.size(), count);


    assert (udpio_list.size() <= count);
    return count - udpio_list.size();
}


/*
 * 找出集合中最大的socket
 */
int udpio_fd_set(fd_set * readfds, fd_set * writefds, fd_set * errorfds)
{
    int fd_max = 0;
    std::set<udpiocb>::const_iterator iter;  // 定义一个迭代器


    /* 
     * 将集合中的socket添加到读数组,
     * 并找出集合中号最大的socket 
     */
    iter = udpio_list.begin();
    while (iter != udpio_list.end()) {
        FD_SET(iter->udpio_fd, readfds); // 将socket添加到读集合
        
        fd_max = (fd_max < iter->udpio_fd? iter->udpio_fd: fd_max);
        ++iter;
    }


    return fd_max;
}


/*
 * 数据接收并转发
 */
int udp_switch(void)
{
    int count;
    /*
     * fd_set的数据结构,实际上是一个long类型的数组,
     * 每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,
     * 建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,
     * 由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
     * typedef struct fd_set {
    *     u_int fd_count;
    *     socket fd_array[FD_SETSIZE];
     * } fd_set;
     */
    fd_set readfds, writefds, errorfds;


    time_t t_last, t_current;
    size_t c_active = udpio_list.size();


    time(&t_last);  // 取得当前时间
    for ( ; ; ) {
        /*
         * FD_ZERO宏用来初始化套接字集合(其实就是清空套接字集合)。
         * 一般而言,初始化服务端的所有套接字组成的集合就应该把FD_ZERO放在循环外,
         * 而初始化具有可读或者可写属性的套接字集合就应该把FD_ZERO放在循环内部。
         * 因为服务端一旦启动服务线程,就会一直处于循环状态,
         * 每一次循环完成都应该将具有可读可写属性的套接字集合清空
         */
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);
        FD_ZERO(&errorfds);


        int max_fd = udpio_fd_set(&readfds, &writefds, &errorfds);


        struct timeval timeout = {1, 1}; // 设置超时时间
        
        /*
         * 监视多个文件句柄的状态变化的。
         * 程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。
         * 返回-1,表示出错;
         * 返回 0,表示超时;
         * 正常返回状态变化的句柄个数
         */
        count = select(max_fd + 1, &readfds, &writefds, &errorfds, &timeout);
        if (count == -1) {
            printf("select error: %d \n", count);
            continue;
        }


        if (count == 0) {
            continue;
        }


        /* 回收 socket */
        if (c_active != udpio_list.size() &&
                time(&t_current) != t_last) {
            udpio_collect(t_current);
            t_last = t_current;
            c_active = udpio_list.size();
        }


        printf("udpio event\n");
        udpio_event(&readfds, &writefds, &errorfds);
    }


    return 0;
}


/* udp_switch addr1:port1 */
int main(int argc, char * argv[])
{
    int error;
    char buf[512];


    int count = 0;
    for (int i = 1; i < argc; i++) {
        char * pdot = NULL;


        strncpy(buf, argv[i], sizeof(buf));
        buf[sizeof(buf) - 1] = 0;
        pdot = strchr(buf, ':');  // 查找字符串buf中首次出现字符':'的位置
        if (pdot == NULL)
            continue;


        *pdot++ = 0;              // 将':'改写成'0',并指针后移一个字节
        int port = atoi(pdot);
        if (port == 0 || port == -1)
            continue;
        udpio_add(inet_addr(buf), port);


        count++;
    }


    if (count > 0)
        udp_switch();


    udpio_final();
    return 0;
}

​二、编译
​g++ -g -Wall -o udp_proxy main.c​

​三、命令
​$  ./udp_proxy 192.1.54.72:10010 192.1.54.74:10020​
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值