贴一份Linux单网间的透明代理代码

示意图大致如下:

贴一份Linux单网间的透明代理代码_第1张图片

系统环境:

    Windows 或者 Linux,稍作修改也可用于FreeBSD系统。

主要用到的技术点:

1. select 模型

2. 多线程

3. libxml库

4. 记录日志

5. 跨平台

6. 命令行参数解析


代码大致如下:

一、参数解析:

	//get prameters
	while(-1 != (opt = getopt(argc,argv,opt_str))) 
	{
		switch(opt) 
		{
		case 'f': 
			{
				strncpy(cfg_path,optarg,strlen(optarg));
			}break;
		case 'd':
			{
				daemon = 1;
			}break;
		case 'l':
			{
				int level = atoi(optarg);
				if (level > L_DEBUG || level < L_CRIT)
					logger::set_log_level(L_DEBUG);
				else
					logger::set_log_level(level);

				printf("log level: %d\n",level);
			}break;
		case '?':
		default: 
			{
				print_usage();
				return -1;
			}
		}
	}

二、解析配置文件,配置日志

	if (cfg.parse_xml(cfg_path) == 0)
	{
		char logpath[MAX_PATH] = {0};
		cfg.get_logpath(logpath,sizeof(logpath) - 1);
		logger::logger_init(cfg.get_loglevel(),logpath,(ELogType)cfg.get_logtype());
		int logsize = cfg.get_logsize();
		if (logsize)
			logger::set_log_size(logsize);

	}else
	{
		logger::logger_init(L_DEBUG,NULL,ELogConsole);
	}

三、创建线程进行监听,等待客户端连接

	listen_info *pinfo = NULL;
	char *listen_ip = cfg.get_local_ip();
	vector *ports = cfg.get_local_ports();
	for (size_t i = 0;i < ports->size();i++)
	{
		debug("[%s:%d] 进入监听...",listen_ip,(*ports)[i]);

		try
		{
			pinfo = new listen_info;
		}catch(...)
		{
			error("new failed...");
			return 0;
		}

		strncpy(pinfo->ip,listen_ip,sizeof(pinfo->ip) - 1);
		pinfo->port = (*ports)[i];
#ifdef WIN32					
		hThread = CreateThread(NULL,0,listen_routine,pinfo,0,&dwTID);
		//CloseHandle(hThread);
#else
		pthread_create(&thread,NULL,listen_routine,pinfo);
#endif /* WIN32 */
	}

listen_routine 线程函数如下

TCALLBACK listen_routine(void *param)
{
	listen_info *pinfo = (listen_info *)param;
	if (!pinfo)
	{
		error("invalid parameter!");
		TRETURN;
	}

	struct timeval seltime;
	seltime.tv_sec = 0;
	seltime.tv_usec = 500 * 1000; // time out

	struct sockaddr_in addr;
#ifdef WIN32
	HANDLE hThread;
	DWORD dwTID;
	int len = sizeof(addr);
#else
	socklen_t len = sizeof(addr);
	pthread_t thread;
#endif

	SOCKET sock = net_util::server_socket(pinfo->ip,pinfo->port);
	if (sock == INVALID_SOCKET)
	{
		error("创建套接字失败");
		TRETURN;
	}

	sock_info *info = NULL;
	int client = 0;
	int ret = 0;
	fd_set fds;
	while (!exit_flag)
	{
		FD_ZERO(&fds);
		FD_SET(sock,&fds);

		if (select(sock + 1,&fds,NULL,NULL,&seltime) == -1)
		{
#ifndef WIN32
			if (errno == EINTR)
				continue;
#endif
			_dprintf(L_ERROR,"select failed,error: %d",_error_sock); //EINTR 4
		}

		if (FD_ISSET(sock,&fds))
		{
			//_dprintf(L_ERROR,"FD_ISSET is true.");
			client = accept(sock,(struct sockaddr *)&addr,&len);
			if (client == -1)
			{
				_dprintf(L_ERROR,"accept failed,error: %d",_error_sock); 
			}else
			{
				_dprintf(L_DEBUG,"Client IP: %s,Port: %d,sock: %d",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port),client);
				try
				{
					info = new sock_info;
					memset(info,0,sizeof(sock_info));
				}catch(...)
				{
					_dprintf(L_CRIT,"new sock_info failed");
					continue;
				}

				strncpy(info->ip,inet_ntoa(addr.sin_addr),sizeof(info->ip) - 1);
				info->cfd = client;
				info->port = addr.sin_port;
				info->dst_port = pinfo->port;

#ifdef WIN32					
				hThread = CreateThread(NULL,0,client_routine,info,0,&dwTID);
				//CloseHandle(hThread);
#else
				pthread_create(&thread,NULL,client_routine,info);
#endif /* WIN32 */
			}
		}
	}

	closesocket(sock);
	debug("[%s:%d] 退出listen线程",pinfo->ip,pinfo->port);

	delete[] pinfo;
#ifndef WIN32
	pthread_detach(pthread_self());
#endif
	return 0;
}

四、处理客户端通信

TCALLBACK client_routine(void *param)
{
	sock_info *pinfo = (sock_info *)param;
	assert(pinfo);

	SOCKET cfd = pinfo->cfd;

	char buf[1024] = {0};
	int recvlen = 0;
	int sendlen = 0;

	SOCKET sfd = INVALID_SOCKET;
	/*
	 * 1. 先连远端,如果连接不成功,踢掉客户端
	 * 2. 如果连接上,直接建立通信过程
	 */

	_dprintf(L_INFO,"begin,read msg from client: ip: %s",pinfo->ip);
	char *ip = cfg.get_remote_ip(pinfo->dst_port);
	int port = cfg.get_remote_port(pinfo->dst_port);
	
	sfd = connect_remote(ip,port);
	if (sfd == INVALID_SOCKET)
	{
		closesocket(cfd);
		debug("退出client线程");
		return 0;
	}

	fd_set connection,available;
	FD_ZERO(&connection);

	FD_SET(cfd,&connection);
	FD_SET(sfd,&connection);

	struct timeval tov;
	int rc = 0;
	while (!exit_flag)
	{
		memmove(&available,&connection,sizeof(fd_set));
		tov.tv_sec = 1;
		tov.tv_usec = 0;

		rc = select(sfd + 1,&available,NULL,NULL,&tov);
		if (rc < 0)
		{
			error("select error: %s",strerror(errno));
			break;
		}else if (rc == 0)
		{
			debug("timeout,continue...");
			continue;
		}

		if (FD_ISSET(cfd,&available)) //检查客户端套接字
		{
			recvlen = read(cfd,buf,sizeof(buf));
			if (recvlen <= 0)
			{
#ifndef WIN32
				if (errno == EAGAIN) //下一次继续接收
					continue;

				if (errno == EINTR)
					continue;

				if (recvlen < 0)
				{
					if (errno != ECONNRESET) //出错了
						error("从客户端读取失败,error: %d,recvlen: %d",_error_sock,recvlen);
				}else
					debug("客户端主动退出");
#endif
				break;
				//shutdown(sfd, SHUT_WR);

				//FD_ZERO(&connection);
				//FD_SET(sfd, &connection);
			}else if (write(sfd,buf,recvlen) != recvlen) //发送给服务器
			{
				error("发送给真实服务器失败,error: %s",strerror(errno));
				break;
			}
		}

		if (FD_ISSET(sfd,&available)) //检查服务器套接字
		{
			recvlen = read(sfd,buf,sizeof(buf));
			if (recvlen <= 0)
			{
				if (recvlen < 0)
				{
					if (errno != ECONNRESET) //出错了
						error("从服务端读取失败,error: %d,recvlen: %d",_error_sock,recvlen);
				}else
					debug("服务端主动退出");

				break;
			}else
			{
				int count = write(cfd,buf,recvlen);
				if (count != recvlen)
				{
					error("发送给客户端失败,error: %s",strerror(errno));
					break;
				}
			}
		} //End FD_ISSET(sfd,xxx)
	} //End while

end:
	if (cfd != INVALID_SOCKET) //断开客户端
		closesocket(cfd);

	if (sfd != INVALID_SOCKET) //断开服务端
		closesocket(sfd);

	delete pinfo;
	debug("退出client线程");
#ifndef WIN32
	pthread_detach(pthread_self());
#endif
	return 0;
}

连接服务器,采用的是异步,可以防止阻塞太长时间。禁用nagle算法,可以有效的防止小报文时TCP之间的延时。

int connect_remote(char *remoteip,int remoteport)
{
	SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (sock == INVALID_SOCKET)
	{
		_dprintf(L_ERROR,"socket failed,error: %d",_error);
		return INVALID_SOCKET;
	}

	/* Disable the Nagle (TCP No Delay) algorithm */
	int flag = 1;
	setsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(char *)&flag, sizeof(flag));
	flag = 0;
#ifdef WIN32
	int len = sizeof(int);
#else
	socklen_t len = sizeof(int);
#endif
	getsockopt(sock,IPPROTO_TCP,TCP_NODELAY,(char *)&flag,&len);
	debug("禁用Nagle算法%s",flag ? "成功" : "失败");

	flag = 1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&flag,sizeof(flag));


	_dprintf(L_INFO,"begin,create socket [%d] to connect remote ip: %s",sock,remoteip);

	int ret = net_util::async_conn_socket(sock,remoteip,remoteport,3);
	if (ret == -1)
	{
		debug("can't connect remote host.");
		closesocket(sock);
		return INVALID_SOCKET;
	}else
	{
		debug("connect real server successful.ip: %s,sock: %d",remoteip,sock);
		return sock;
	}
}

主体部分大致如上所示,程序只是简单的使用透明代理传递了两者之间的数据,麻雀虽小五脏俱全,呵呵。。。

纯属业余之作,不喜勿喷。


代码下载链接:点击打开链接



你可能感兴趣的:(Linux)