Wednesday, 7. February 2007, 09:39:23
拖啊拖,拖到现在才写……
前面的一堆include就不说了,大概就是针对win和*nix平台进行一些统一的定义。注意在*nix下:
#define recv(x,y,z,a) read(x,y,z)
#define send(x,y,z,a) write(x,y,z)
#define closesocket(s) close(s)
然后是结构体:
struct client_t
{
int inuse; //是否已经使用
SOCKET csock, osock;
//csock为远程客户端与本机连接的socket
//osocks为远程服务器端与本机的连接的socket
time_t activity;
//上次活动时间,用于idle超时断开
};
在main()中声明了
struct client_t clients[MAXCLIENTS];
表示可以接受的客户端连接数
其它的声明:
SOCKET lsock; //本地监听socket
char buf[4096]; //收发buffer
struct sockaddr_in laddr, oaddr; //本机ip地址和远端服务器ip地址与端口
接下来程序会从命令行解析本机ip和远程服务器ip,并创建监听socket:
/* create the listener socket */
if ((lsock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return 20;
}
if (bind(lsock, (struct sockaddr *)&laddr, sizeof(laddr))) {
perror("bind");
return 20;
}
if (listen(lsock, 5)) {
perror("listen");
return 20;
}
这里socket()创建了一个ipv4的socket,这个socket是没有地址/名字的。所以接下来将其bind到本机ip上,并用listen()来允许socket接受连接请求(在后面用accept()来接受并产生一个新的socket来表示建立起来的连接)。
至此初始化就完成了。接下来,如果是*nix,则用fork来产生子进程
if ((i = fork()) == -1) {
perror("fork");
return 20;
}
if (i > 0)
return 0;
setsid();
对于原进程,得到的i > 0,因此会终止,留下子进程在后台继续运行。setsid()使子进程不会随用户logout而终止。
然后进入主循环:
fd_set fdsr;
int maxsock;
struct timeval tv = {1,0}; //select超时为1秒
这几个是配合select使用的。
/* build the list of sockets to check. */
FD_ZERO(&fdsr);
FD_SET(lsock, &fdsr);
maxsock = (int) lsock;
for (i = 0; i < MAXCLIENTS; i++)
if (clients[i].inuse) {
FD_SET(clients[i].csock, &fdsr);
if ((int) clients[i].csock > maxsock)
maxsock = (int) clients[i].csock;
FD_SET(clients[i].osock, &fdsr);
if ((int) clients[i].osock > maxsock)
maxsock = (int) clients[i].osock;
}
if (select(maxsock + 1, &fdsr, NULL, NULL, &tv) < 0) {
return 30;
}
首先FD_ZERO把列表清空,然后用FD_SET将监听lsock加入列表,接着将各个客户端连接的csock和osock也加入列表。这样所有在用的socket都在列表中了。这些列表中的fd(file descriptor)都是int型变量,maxsocks是其中数值最大者。
然后就是用select()来监视列表中哪个fd可读。前面用tv指定了时间为1秒,即如果有任一fd可读或超过1秒无任何fd可读,则程序继续执行。
select的原型如下:
int select(
int nfds,
fd_set* readfds,
fd_set* writefds,
fd_set* exceptfds,
const struct timeval* timeout
);
接下来程序用FD_ISSET()检查是哪个fd可读。首先检查lsock,如果它可读,说明有客户端请求连接。
SOCKET csock = accept(lsock, NULL, 0);
用于接受请求,并得到这个连接的fd。然后在clients数组中寻找一个未使用的子元素,用它来保存这个新连接的信息。如果没有任何空闲的子元素,表示客户端连接已满,用closesocket(csock)来关闭连接。如果有空闲的子元素,就建立一个本机到远端服务器的连接,这样就建立起了这样的连接关系:
客户端<--csock-->本机<--osock-->服务器
接下来的程序是检查clients数组中已使用的元素的csock和osock是否可读,如果csock可读,就把数据读入buf并发送到osock,并设置activity为当前时间,从而实现将客户端发送的数据转发到服务器端。同样的,如果osocks可读,就是服务器发送数据到客户端。recv()的返回值是接收到的数据长度,如果从csock或osock接收到的数据长度<0,表示连接关闭,设置closeneeded = 1
然后检查各个元素的activity(即上次活动时间),如果超过设定的时间没有数据,也同样设置closeneeded = 1
然后就是当closeneeded == 1时关闭csock和osock
如此循环,就完成了portmap的功能。