一、代码
#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