示意图大致如下:
系统环境:
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 */
}
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;
}
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;
}
}
纯属业余之作,不喜勿喷。
代码下载链接:点击打开链接