⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 51.htm

📁 unix高级编程原吗
💻 HTM
📖 第 1 页 / 共 2 页
字号:
----------------------------------------------------------------- <br>

  在我们这段代理服务器例程中,真正连接用户主机和远端主机的一段操作,就是由 <br>

这个 <br>

do_proxy()函数来完成的。回想一下我们一开始对这段proxy程序用法的介绍。先将我们 <br>

的p <br>

roxy与远端主机绑定,然后用户通过proxy的绑定端口与远端主机建立连接。而在main( <br>

)函 <br>

数 <br>

中,我们的proxy由一段服务器程序与用户主机建立了连接,而在这个do_proxy <br>

()函数中,p <br>

roxy将与远端主机的相应服务端口(由用户在命令行参数中指定)建立连接,并负责传 <br>

递用 <br>

户主机和远端主机之间交换的数据。 <br>

  由于要和远端主机建立连接,所以我们看到do_proxy()函数的前半部分实际上相当 <br>

于一 <br>

段标准的客户机程序。首先创建一个新的套接字描述符isosockfd,然后调用函数conne <br>

ct() <br>

与远端主机之间建立连接。函数connect()的定义为: <br>

----------------------------------------------------------------- <br>



#include <sys/types.h> <br>

#include <sys/socket.h> <br>

int connect(int sockfd, struct sockaddr *servaddr, int addrlen); <br>

----------------------------------------------------------------- <br>

  参数sockfd是调用函数socket()返回的套接字描述符,参数servaddr指向远程服务 <br>

器的 <br>

套接字地址结构,参数addrlen指定这个套接字地址结构的长度。函数connect()执行成 <br>

功时 <br>

返回"0",如果执行失败则返回"-1",并将全局变量errno设置为相应的错误类型。在例 <br>

程中 <br>

的switch()函数调用中对以下三种出错类型进行了处理: ETIMEDOUT、ECONNREFUSED和 <br>

ENET <br>

UNREACH。这三个出错类型的意思分别为:ETIMEDOUT代表超时,产生这种情况的原因有 <br>

很多 <br>

,最常见的是服务器忙,无法应答客户机的连接请求;ECONNREFUSED代表连接拒绝,即 <br>

服务 <br>

器端没有准备好的倾听套接字,或是没有对倾听套接字的状态进行监听;ENETUNREACH表 <br>

示 <br>

网 <br>

络不可达。 <br>

  在本例中,connect()函数的第二个参数servaddr是全局变量hostaddr,其中存储着 <br>

函 <br>

函 <br>

数 <br>

parse_args()转换好的命令行参数。如果连接建立失败,在例程中就调用我们自 <br>

定义的函数 <br>

errorout()输出信息"failed to connect to host"。errorout()函数的定义为: <br>

----------------------------------------------------------------- <br>

/**************************************************************** <br>

function:  errorout <br>

description: displays an error message on the console and kills the current <br>

proc <br>

ess. <br>

arguments:  msg -- message to be displayed. <br>

return value: none -- does not return. <br>

calls:    none. <br>

globals:   none. <br>

****************************************************************/ <br>

void errorout (msg) <br>

char *msg; <br>

{ <br>

  FILE *console; <br>

  console = fopen("/dev/console","a"); <br>

  fprintf(console,"proxyd: %s\r\n",msg); <br>

  fclose(console); <br>



  exit(1); <br>

} <br>

----------------------------------------------------------------- <br>

  do_proxy()函数的后半部分是通过proxy建立用户主机与远端主机之间的连接。我们 <br>

既 <br>

有 <br>

proxy与用户主机连接的套接字(do_proxy()函数的参数usersockfd),又有pro <br>

xy与远端主 <br>

机连接的套接字isosockfd,那么最简单直接的通信建立方式就是从一个套接字读,然后 <br>

直 <br>

接 <br>

写到另一个套接字去。如: <br>

----------------------------------------------------------------- <br>

int n; <br>

char buf[2048]; <br>

while((n=read(usersockfd, buf, sizeof(buf))>0) <br>

if(write(isosockfd, buf, n)!=n) <br>

err_sys("write wrror\n"); <br>

----------------------------------------------------------------- <br>

  这种形式的阻塞I/O在单向数据传递的时候是非常有效的,但是在我们的proxy操作 <br>

中是 <br>

要求用户主机和远端主机双向通信的,这样就要求我们对两个套接字描述符既能够读由 <br>



能够 <br>

写。如果还是采用这种方式的阻塞I/O的话,很有可能长时间阻塞在一个描述符上。因此 <br>

例 <br>

程 <br>

在处理这个问题的时候调用了select()函数,这个函数允许我们执行I/O多路转接 <br>

。其具体 <br>

含 <br>

义就是select()函数可以构造一个表,在这个表中包含了我们所有要用到 <br>

的文件描述符。然 <br>

后我们可以调用一个函数,这个函数可以检测这些文件描述符的状态,当某个(我们指 <br>

定的 <br>

)文件描述符准备好进行I/O操作时,此函数就返回,告知进程哪个文件描述符已经可以 <br>

执 <br>

行 <br>

I/O操作了。这样就避免了长时间的阻塞。 <br>

  还有一个函数poll()可以实现I/O多路转接,由于在例程中调用的是select(),我们 <br>

就 <br>

只 <br>

对select()进行一下比较详细的介绍。select()系列函数的详细描述为: <br>

----------------------------------------------------------------- <br>

#include <sys/time.h> <br>

#include <sys/types.h> <br>



#include <unistd.h> <br>

int select(int n, fd_set *readfds, fd_set *writefds, fd_est *exceptfds, stru <br>

ct t <br>

imeval *timeout); <br>

FD_CLR(int fd, fd_set *set); <br>

FD_ISSET(int fd, fd_set *set); <br>

FD_SET(int fd, fd_set *set); <br>

FD_ZERO(fd_set *set); <br>

----------------------------------------------------------------- <br>

  select()函数将创建一个我们所关心的文件描述符表,它的参数将在内核中为这些 <br>

文件 <br>

描述符设置我们所关心的条件,例如是否是可读、是否可写以及是否异常,而且在参数 <br>

中还 <br>

可以设置我们希望等待的最大时间。在select()成功执行时,它将返回目前已经准备好 <br>

的描 <br>

述符数量,同时内核可以告诉我们各个描述符的状态信息。如果超时,则返回"0",如果 <br>

出 <br>

错 <br>

,则函数返回"-1",并同时设置errno为相应的值。 <br>

  select()的最后一个参数timeout将设置等待时间。其中结构timeval是在文件<bit <br>

s/ti <br>

me.h>中定义的。 <br>



----------------------------------------------------------------- <br>

struct timeval <br>

{ <br>

  __time_t tv_sec; /* Seconds */ <br>

  __time_t tv_usec; /* Microseconds */ <br>

}; <br>

----------------------------------------------------------------- <br>

  参数timeout的设置有三种情况。象例程中这样timeout==NULL时,这表示用户希望 <br>

永远 <br>

等待,直到我们指定的文件描述符中的一个已准备好,或者是捕捉到一个信号。如果是 <br>

由于 <br>

捕捉到信号而中断了这个无限期的等待过程的话,select()将返回"-1",同时设置errn <br>

o的 <br>

值 <br>

为EINTR。 <br>

  如果timeout->tv_sec==0&&timeout->tv_usec==0,那么这表示完全不等待。Selec <br>

t() <br>

测 <br>

试了所有指定文件描述符后立即返回。这是得到多个描述符状态而不阻塞selec <br>

t()函数的轮 <br>

询方法。 <br>

  如果timeout->tv_sec!=0||timeout->tv_usec!=0,那么这两个参数的值即为我们 <br>



希 <br>

望 <br>

函数等待的时间。其中tv_sec设置时间单位为秒,tv_usec设置时间单位为微秒。 <br>

如果在超 <br>

时 <br>

的时候,在我们指定的所有文件描述符里面仍然没有任何一个准备好的话 <br>

,则select()将返 <br>

回"0"。 <br>

  中间三个参数的数据类型是fd_set,它的意思是文件描述符集,而readfds, write <br>

fds <br>

和 <br>

exceptfds则分别是指向文件描述符集的指针,他们分别描述了我们所关心的可 <br>

读、可写以 <br>

及 <br>

状态异常的各个文件描述符。之所以我们称select()可以创建一个文件 <br>

描述符"表",那个所 <br>

谓的表就是由这三个参数指向的数据结构组成的。其具体结构如图1所示。其中在每个s <br>

et_f <br>

d数据类型中都为我们关心的所有文件描述符保留了一位。所以在监测文件描述符状态的 <br>

时 <br>

候 <br>

,就在这些set_fd数据结构中查询相关的位。 <br>



  第一个参数n用来说明到底需要遍历多少个描述符位。n的值一般是这样设置的,从 <br>

我们 <br>

关心的所有文件描述符中选出最大值再加1。例如我们设置的所有文件描述符中最大的为 <br>

6, <br>

那么将n设置为7,则系统在检测描述符状态的时候,就只用遍历前7位(fd0~fd6)的状 <br>

态。 <br>

不过如果不想这样麻烦的话,我们可以象例程中那样将n的值直接设置为FD_SETSIZE。这 <br>

是 <br>

系 <br>

统中设定的最大文件描述符个数,不同的系统这个值也不相同,一般是256或是1 <br>

024。这样 <br>

在 <br>

检测描述符状态的时候,函数将遍历所有的描述符位。 <br>

  在调用select()函数实现多路I/O转接时,首先我们要声明一个新的文件描述符集, <br>

就 <br>

象 <br>

例程中这样: <br>

   fd_set rdfdset; <br>

  然后调用FD_ZERO()清空此文件描述符集的所有位,以免下面检测描述符位的时候返 <br>

回 <br>

错 <br>

误结果: <br>

误结果: <br>

   FD_ZERO(&rdfdset); <br>

  然后调用FD_SET()在文件描述符集中设置我们关心的位。在本例中,我们关心的就 <br>

是分 <br>

别与用户主机和远端主机连接的两个套接字描述符,所以执行这样的语句: <br>

   FD_SET(usersockfd,&rdfdset); <br>

   FD_SET(isosockfd,&rdfdset); <br>

  然后调用select()返回描述符状态,此时描述符状态被存储进描述符集,也就是se <br>

t_fd <br>

数据结构中。在图1中我们看到所有的描述符位状态都是"0",在select()返回后,例如 <br>

fd0 <br>

可 <br>

读,则在readfds描述符集中fd0对应的位上将状态标志设置为"1",如果fd1可写 <br>

,则writef <br>

ds描述符集中fd1对应的位上将状态标志设置为"1",状态异常的情况也也与此相同。在 <br>

本例 <br>

中,我们只关心两个套接字描述符是否可写,因此执行这样的select()函数: <br>

   select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) <br>

  那么在select()返回后怎样检测set_fd数据结构中描述符位的状态呢?这就要调用 <br>

函数 <br>

FD_ISSET(),如果对应文件描述符的状态为"已准备好"(即描述符位为"1"),则FD_IS <br>

SET( <br>

)返回"1",否则返回"0"。 <br>



----------------------------------------------------------------- <br>

if (FD_ISSET(usersockfd,&rdfdset)) { <br>

  if ((iolen = read(usersockfd,buf,sizeof(buf))) <= 0) <br>

  break; /* zero length means the host disconnected */ <br>

  write(isosockfd,buf,iolen); <br>

----------------------------------------------------------------- <br>

  这一段代码就实现从套接字usersockfd(用户主机)到套接字isosockfd(远端主机 <br>

) <br>

的 <br>

无阻塞传输。而下一段代码实现反方向的无阻塞传输: <br>

----------------------------------------------------------------- <br>

if (FD_ISSET(isosockfd,&rdfdset)) { <br>

  if ((iolen = read(isosockfd,buf,sizeof(buf))) <= 0) <br>

   break; /* zero length means the host disconnected */ <br>

   write(usersockfd,buf,iolen); <br>

----------------------------------------------------------------- <br>

  这样就通过proxy实现了用户主机与远端主机之间的通信。 <br>

  对这段proxy代码我只是写了一些自己的理解,大多数是一些函数的用法,这些都是 <br>

lin <br>

ux网络编程中一些最基础的知识,如果有不对的地方,还请各位大 号

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -