Linux是一个可靠性非常高的操作系统,但是所有用过Linux的朋友都会感觉到, Linux和Windows这样的"傻瓜"操作系统(这里丝毫没有贬低Windows的意思,相反这应该是Windows的优点)相比,后者无疑在易操作 性上更胜一筹。但是为什么又有那么多的爱好者钟情于Linux呢,当然自由是最吸引人的一点,另外Linux强大的功能也是一个非常重要的原因,尤其是 Linux强大的网络功能更是引人注目。放眼今天的WAP业务、银行网络业务和曾经红透半边天的电子商务,都越来越倚重基于Linux的解决方案。因此 Linux网络编程是非常重要的,而且当我们一接触到Linux网络编程,我们就会发现这是一件非常有意思的事情,因为以前一些关于网络通信概念似是而非的地方,在这一段段代码面前马上就豁然开朗了。在刚开始学习编程的时候总是让人感觉有点理不清头绪,不过只要多读几段代码,很快我们就能体会到其中的乐趣 了。下面我就从一段Proxy源代码开始,谈谈如何进行Linux网络编程。
首先声明,这段源代码不是我编写的,让我们感谢这位名叫Carl Harris的大虾,是他编写了这段代码并将其散播到网上供大家学习讨论。这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地 描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。
这段Proxy程序的用法是这样的,我们可以使用这个proxy登录其它主机的服务端口。假如编译后生成了名为Proxy的可执行文件,那么命令及其参数的描述为:
./Proxy
其中参数proxy_port是指由我们指定的代理服务器端口。参数remote_host是指我们希望连接的远程主机的主机名,IP地址也同样有 效。这个主机名在网络上应该是唯一的,如果您不确定的话,可以在远程主机上使用uname -n命令查看一下。参数service_port是远程主机可提供的服务名,也可直接键入服务对应的端口号。这个命令的相应操作是将代理服务器的 proxy_port端口绑定到remote_host的service_port端口。然后我们就可以通过代理服务器的proxy_port端口访问 remote_host了。例如一台计算机,网络主机名是legends,IP地址为10.10.8.221,如果在我的计算机上执行:
[root@lee /root]#./proxy 8000 legends telnet
那么我们就可以通过下面这条命令访问legends的telnet端口。
以下代码有多次修改。运行环境:Ubuntu 9.04 Server Gcc 4.3
#include #include #include #include #include #include #include #include #include #include #include #include #define TCP_PROTO "tcp" int proxy_port; struct sockaddr_in hostaddr; extern int errno; //extern char *sys_errlist[]; void parse_args(int argc, char **argv); void daemonize(int servfd); void do_proxy(int usersocket); void reap_status(void); void errorout(char *msg); typedef void Sigfunc(int); int main(int argc, char **argv) { int clilen; pid_t childpid; int sockfd, newsockfd; struct sockaddr_in servaddr, cliaddr; parse_args(argc, argv); bzero((char *) &servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = proxy_port; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fputs("failed to create server socket /r/n", stderr); exit(1); } if (bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { fputs("failed to bind server socket to specified port /r/n", stderr); exit(1); } listen(sockfd, 5); daemonize(sockfd); for(;;) { clilen = sizeof(cliaddr); newsockfd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen); if (newsockfd <0 && errno == EINTR) continue; else if (newsockfd < 0) errorout("failed to accept connection"); if ((childpid = fork()) == 0) { close(sockfd); do_proxy(newsockfd); exit(0); } close(newsockfd); } } /*********************************************************************** function:parse_args description: Parse the command line args. arguments:argc, argv you know what these are. return value: none. calls:none. globals:writes proxy_port, writes hostaddr. **********************************************************************/ void parse_args(int argc, char ** argv) { int i; struct hostent *hostp; struct servent *servp; unsigned long inaddr; struct { char proxy_port[16]; char isolated_host[64]; char service_name[32]; } pargs; if (argc < 4) { printf("usage: %s /r/n", argv[0]); exit(1); } strcpy(pargs.proxy_port, argv[1]); strcpy(pargs.isolated_host, argv[2]); strcpy(pargs.service_name, argv[3]); for (i = 0; i < strlen(pargs.proxy_port); i++) if (!isdigit(*(pargs.proxy_port + i))) break; if (i == strlen(pargs.proxy_port)) proxy_port = htons(atoi(pargs.proxy_port)); else { printf("%s: invalid proxy port /r/n", pargs.proxy_port); exit(0); } bzero(&hostaddr, sizeof(hostaddr)); hostaddr.sin_family = AF_INET; if ((inaddr = inet_addr(pargs.isolated_host)) != INADDR_NONE) bcopy(&inaddr, &hostaddr.sin_addr, sizeof(inaddr)); else if ((hostp = gethostbyname(pargs.isolated_host)) != NULL) bcopy(hostp->h_addr, &hostaddr.sin_addr, hostp->h_length); else { printf("%s: unknown host /r/n", pargs.isolated_host); exit(1); } if ((servp = getservbyname(pargs.service_name, TCP_PROTO)) != NULL) hostaddr.sin_port = servp->s_port; else if (atoi(pargs.service_name) > 0) hostaddr.sin_port = htons(atoi(pargs.service_name)); else { printf("%s: invalid/unknown service name or port number /r/n", pargs.service_name); exit(1); } } /***************************************************************** function: daemonize description: detach the server process from the current context, creating a pristine, predictable environment in which it will execute. arguments: servfd file descriptor in use by server. return value: none calls: none globals: none *****************************************************************/ void daemonize(int servfd) { pid_t childpid; int fd, fdtablesize; /* ignore terminal I/O, stop signals */ signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTSTP, SIG_IGN); /* fork to put us in the background (whether or not the user specified '&' on the command line */ if ((childpid = fork()) < 0) { fputs("failed to fork first child /r/n", stderr); exit(1); } else if (childpid > 0) exit(0); if (setpgrp(0, getpid()) < 0 ) { fputs("failed to become process group leader/r/n", stderr); exit(1); } if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, NULL); close(fd); } for(fd= 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++) if (fd != servfd) close(fd); chdir("/"); umask(0); signal(SIGCLD, (Sigfunc *)reap_status); } /***************************************************************** function:reap_status desc: Handle a SIGCLD signal by reaping the exit status of the perished child, and discarding it. arguments:none return value : none. calls: none globals : none *******************************************************************/ void reap_status(void) { pid_t pid; union wait status; while ((pid = wait3(&status, WNOHANG, NULL)) > 0); } /****************************************************************** function : do_proxy desc : does the actual work of virtually connecting a client to the telnet service on the isolated host. arguments: usersockfd socket to which the client is connected. return value : none calls : none globals : reads hostaddr. *******************************************************************/ void do_proxy(int usersockfd) { int isosockfd; fd_set rdfdset; int connstat; int iolen; char buf[2048]; /*open a socket to connect to the isolated host */ if ((isosockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) errorout("failed to create socket to host"); connstat = connect(isosockfd, (struct sockaddr *)&hostaddr, sizeof(hostaddr)); switch (connstat){ case 0: break; case ETIMEDOUT: case ECONNREFUSED: case ENETUNREACH: strcpy(buf, strerror(errno)); strcat(buf, "/r/n"); write(usersockfd, buf, strlen(buf)); close(usersockfd); exit(1); break; default: errorout("failed to connect to host"); } for(;;) { /* Select for readability on either of our two sockedts */ FD_ZERO(&rdfdset); FD_SET(usersockfd, &rdfdset); FD_SET(isosockfd, &rdfdset); if (select(FD_SETSIZE, &rdfdset, NULL, NULL, NULL) < 0) errorout("Select failed"); /* is the client sending data? */ if (FD_ISSET(usersockfd, &rdfdset)) { /* zero length means the client disconnected */ if ((iolen = read(usersockfd, buf, sizeof(buf))) <= 0) break; write(isosockfd, buf, iolen); } /*is the host sending data? */ if (FD_ISSET(isosockfd, &rdfdset)) { if ((iolen = read(isosockfd, buf, sizeof(buf))) <= 0) break; write(usersockfd, buf, iolen); } } close(isosockfd); close(usersockfd); } /************************************************************** function : errorout desc : displays an error message on the console and kill the current process. arguments : msg-- message to be displayed. return value : none; calls : none globals: none **************************************************************/ void errorout(char *msg) { FILE *console; console = fopen("/dev/console", "a"); fprintf(console, "proxyd: %s /r/n", msg); fclose(console); exit(1); }