用gcctcpforwardport.c-oMyProxy编译此程序后运行效果如下:

当有用户访问本机的8000端口时,MyProxy程序将把此请求转发到172.16.100.218主机的80端口,即实现了一个http代理。

关于select函数:
其函数原型为:
intselect(intn,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);
此函数的功能是由内核检测在timeout时间内,是否有readfds,writefds,exceptfds三个句柄集(filedescriptors)里的某个句柄(filedescriptor)的状态符合寻求,即readfds句柄集里有句柄可读或writefds句柄集里有可写或exceptfds句柄集里有例外发生,任何一个有变化函数就立即返回,返回值为timeout发生状态变化的句柄个数。
n是所有readfds,writefds,exceptfds三个句柄集(filedescriptors)里编号最大值加1。比如:要检测两个socket句柄fd1和fd2在timeout时间内是否分别可读和可写就可以这样:
先把两个句柄集(filedescriptors)清零:
FD_ZERO(&readfds);
FD_ZERO(&writefds);
然后把fd1加入读检测集:
FD_SET(fd1,&readfds);
然后把fd2加入写检测集:
FD_SET(fd2,&writefds);
再给timeout设置值,timeout是这样的一个结构:
structtimeval{
longtv_sec;/*seconds*/
longtv_usec;/*microseconds*/
};
你可以这样赋值:
timeout.tv_sec=1;
timeout.tv_uec=0;
表示检测在1秒钟内是否有句柄状态发生变化。
如果有句柄发生变化,就可以用FD_ISSET检测各个句柄,比如:
FD_ISSET(fd1,&readfds);//检测是否fd1变成可读的了
FD_ISSET(fd2,&writefds);//检测是否fd2变成可写的了
示意程序代码如下:
/*----------------------示意代码开始--------------------------------------------*/
fd1=socket();//创建一个socket
fd2=socket();//创建一个socket
while(1){
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(fd1,&readfds);
FD_SET(fd2,&writefds);
timeout.tv_sec=1;
timeout.tv_uec=0;
ret=select(fd1>fd2?(fd1+1):(fd2+1),&readfds,&writefds,NULL,&timeout);
if(ret<0){printf("系统错误,select出错,错误代码:%d,错误信息:%s",errno,strerror(errno));}
elseif(ret==0){printf("select超时返回,没有任何句柄状态发生变化!");}
//有句柄状态发生了变化
if(FD_ISSET(fd1,&readfds)){
fd1有数据可读;
fd1里的数据被读出来;
}
if(FD_ISSET(fd2,&writefds)){
fd2可写;
fd2里发送数据给对方;
}
}
/*----------------------示意代码结束--------------------------------------------*/

经常用到的几个自定义函数:
1、开启监听的函数

/*----------------------源代码代码开始--------------------------------------------*/
int
OpenSCPServer(intport,inttotal,intsendbuflen,intrecvbuflen,intblockORnot,intreuseORnot){
/*************************关于本函数************************************
*function_name:OpenSCPServer
*参数说明:port整数型监听端口号,total整数型监听个数,sendbuflen整数型发送缓冲区大小
*recvbuflen整数型接收缓冲区大小,blockORnot整数型是否阻塞,reuseORnot整数型是否端口重用
*purpose:用来建立一个tcp服务端socket
*tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2006-07-0520:00:00
*Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
*但请遵循GPL
*Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含GetCurrentTime.h头文件
*********************************************************************/
intsockfd=0,ret=0,opt=0,flags=1;
structsockaddr_inladdr;

ret=sockfd=socket(PF_INET,SOCK_STREAM,0);
if(ret<0){
sprintf(errorMessage,"OpenTCPServersocket()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
return-1;
}

ret=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuseORnot,sizeof(int));
if(ret<0){
sprintf(errorMessage,"OpenTCPServersetsockopt()reuseerror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
return-2;
}

ret=setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&recvbuflen,sizeof(int));
if(ret<0){
sprintf(errorMessage,"OpenTCPServersetsockopt()recvbuferror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
return-3;
}

ret=setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&sendbuflen,sizeof(int));
if(ret<0){
sprintf(errorMessage,"OpenTCPServersetsockopt()sendbuferror!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
return-4;
}

ioctl(sockfd,FIONBIO,&blockORnot);/*blockornot*/

laddr.sin_family=PF_INET;
laddr.sin_port=htons(port);
laddr.sin_addr.s_addr=INADDR_ANY;
bzero(&(laddr.sin_zero),8);

ret=bind(sockfd,(structsockaddr*)&laddr,sizeof(structsockaddr));
if(ret<0){
sprintf(errorMessage,"OpenTCPServerbind()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
close(sockfd);
return-5;
}
ret=listen(sockfd,total);
if(ret<0){
sprintf(errorMessage,"OpenTCPServerlisten()error!return:%d,errno=%d,errortext:'%s'%s",ret,errno,strerror(errno),GetCurrentTime(0,0));
close(sockfd);
return-6;
}
sprintf(errorMessage,"OpenTCPServeropenedonport.%d(%d)OK,socket(%d),buf(%d:%d)!%s",port,total,sockfd,sendbuflen,recvbuflen,GetCurrentTime(0,0));
returnsockfd;
}
/*----------------------源代码代码结束--------------------------------------------*/

2、连接服务器的函数

/*----------------------源代码代码开始--------------------------------------------*/
int
ConnectSCPServer(char*serverip,intserverport,intblockORnot){
/*************************关于本函数************************************
*function_name:ConnectSCPServer
*参数说明:serverip服务器IP地址或主机名,serverport服务器端口,blockORnot整数型是否阻塞
*purpose:用来建立一个tcp客户端socket
*tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2006-07-0520:40:00
*Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
*但请遵循GPL
*Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
intserversock=0,ret=0;
unsignedlongaddr;
structsockaddr_insin;
structhostent*he;

if((he=gethostbyname(serverip))==0){
sprintf(errorMessage,"ConnectSCPServerIPaddress'%s'error!return:-1%s",serverip,GetCurrentTime(0,0));
return-1;
}

serversock=socket(PF_INET,SOCK_STREAM,0);
if(serversock==-1){
sprintf(errorMessage,"ConnectSCPServersocket()error!return:-2,errno=%d,errortext:'%s'%s",errno,strerror(errno),GetCurrentTime(0,0));
return-2;
}

ioctl(serversock,FIONBIO,&blockORnot);//blockornot

memset((char*)&sin,0,sizeof(structsockaddr_in));
sin.sin_family=PF_INET;
sin.sin_port=htons(serverport);
sin.sin_addr=*((structin_addr*)he->h_addr);

ret=connect(serversock,(structsockaddr*)&sin,sizeof(sin));

if(ret==-1){
sprintf(errorMessage,"ConnectSCPServerconnect()error!return:-3,errno=%d,errortext:'%s'%s",errno,strerror(errno),GetCurrentTime(0,0));
close(serversock);
return-3;
}

returnserversock;
}
/*----------------------源代码代码结束--------------------------------------------*/

3、发送数据函数Send
/*----------------------源代码代码开始--------------------------------------------*/
int
Send(intsock,char*buf,size_tsize,intflag,inttimeout){
/*************************关于本函数************************************
*function_name:Send
*参数说明:sock整数型socket,buf待发送的内容,size要发送的大小,flag发送选项,timeout超时时间值
*purpose:用来通过一个socket在指定时间内发送数据
*tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2006-07-0520:58:00
*Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
*但请遵循GPL
*Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
inti=0,ret=0,intretry=0;

structtimevaltival;
fd_setwritefds;
intmaxfds=0;

tival.tv_sec=timeout;
tival.tv_usec=0;

FD_ZERO(&writefds);

if(sock>0){
FD_SET(sock,&writefds);
maxfds=((sock>maxfds)?sock:maxfds);
}
else{
sprintf(errorMessage,"Sendsocket:%derror!return:-2%s",sock,GetCurrentTime(0,0));
return-2;
}

ret=select(maxfds+1,NULL,&writefds,NULL,&tival);
if(ret<=0){
if(ret<0)sprintf(errorMessage,"Sendsocket:%dselect()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
elsesprintf(errorMessage,"Sendsocket:%dselecttimeout(%d)!%s",sock,timeout,GetCurrentTime(0,0));
close(sock);
return-3;
}
if(!(FD_ISSET(sock,&writefds))){
sprintf(errorMessage,"Sendsocket:%dnotinwritefds!%s",sock,GetCurrentTime(0,0));
close(sock);
return-4;
}

while(i<size){
ret=send(sock,buf+i,size-i,flag);
if(ret<=0){
sprintf(errorMessage,"Sendsocket:%dsend()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));

if(EINTR==errno)
if(intretry<10){intretry++;continue;}
elsesprintf(errorMessage,"Sendsocket:%dsend()error!EINTR10times!%s",sock,GetCurrentTime(0,0));

close(sock);
return-1;
}
elsei+=ret;
}
sprintf(errorMessage,"Sendsocket:%dsend()OK!%d/%dbytessent!%s",sock,i,size,GetCurrentTime(0,0));
returni;
}
/*----------------------源代码代码结束--------------------------------------------*/

4、接收数据函数Recv

/*----------------------源代码代码开始--------------------------------------------*/
int
Recv(intsock,char*buf,size_tsize,intflag,inttimeout){
/*************************关于本函数************************************
*function_name:Recv
*参数说明:sock整数型socket,buf接收数据的缓冲区,size要接收数据的大小,flag接收选项,timeout超时时间值
*purpose:用来从一个socket在指定时间内读取数据
*tidiedby:zhoulifa(zhoulifa@163.com)周立发(http://zhoulifa.bokee.com)
Linux爱好者Linux知识传播者SOHO族开发者最擅长C语言
*datetime:2006-07-0521:10:00
*Note:任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
*但请遵循GPL
*Thanksto:PaulSheer感谢PaulSheer在select_tut的man手册里提供了这份源代码
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*Note:要使用此函数需要自定义一个全局变量charerrorMessage[1024];并包含自己编写的GetCurrentTime.h头文件
*********************************************************************/
inti=0,ret=0,intretry=0;

structtimevaltival;
fd_setreadfds;
intmaxfds=0;

tival.tv_sec=timeout;
tival.tv_usec=0;

FD_ZERO(&readfds);

if(sock>0){
FD_SET(sock,&readfds);
maxfds=((sock>maxfds)?sock:maxfds);
}
else{
sprintf(errorMessage,"Recvsocket:%derror!return:-2%s",sock,GetCurrentTime(0,0));
return-2;
}

ret=select(maxfds+1,&readfds,NULL,NULL,&tival);
if(ret<=0){
if(ret<0)sprintf(errorMessage,"Recvsocket:%dselect()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
elsesprintf(errorMessage,"Recvsocket:%dselecttimeout(%d)!%s",sock,timeout,GetCurrentTime(0,0));
close(sock);
return-3;
}
if(!(FD_ISSET(sock,&readfds))){
sprintf(errorMessage,"Recvsocket:%dnotinreadfds!%s",sock,GetCurrentTime(0,0));
close(sock);
return-4;
}
while(i<size){
ret=recv(sock,buf+i,size-i,flag);
if(ret<=0){
sprintf(errorMessage,"Recvsocket:%drecv()error!return:%d,errno=%d,errortext:'%s'%s",sock,ret,errno,strerror(errno),GetCurrentTime(0,0));
if(errno==EINTR)
if(intretry<10){intretry++;continue;}
elsesprintf(errorMessage,"Recvsocket:%drecv()error!EINTR10times!%s",sock,GetCurrentTime(0,0));
close(sock);
return-1;
}
elsei+=ret;
}
sprintf(errorMessage,"Recvsocket:%drecv()OK!%d/%dbytesreceived!%s",sock,i,size,GetCurrentTime(0,0));
returni;
}

最后需要说明的是:我这里讲到的源程序并不能实际地作为一个产品程序来用,实际情况下可能会有其它许多工作要做,比如可能要建立共享队列来存放socket里读到的消息,也可能把发送消息先进行排队然后再调用Send函数。还有,如果不是全数字,在发送前一定要htonl转换为网络字节序,同理接收到后一定要先ntohl由网络字节序转换为主机字节序,否则对方发送过来的0x00000001在你这里可能是0x00010000,因为高低位顺序不同。