阅读以下说明和Socket程序,根据要求将程序代码中(1)~(10)空缺处的内容填写完整。 【说明】 网络应用的基本模型是客户机/服务器模型,这是一个不对称的编程模型,通信的双方扮演不同的角色,分别是客户机和服务器。 一般发起通信请求的应用

admin2009-02-15  28

问题 阅读以下说明和Socket程序,根据要求将程序代码中(1)~(10)空缺处的内容填写完整。
  【说明】
   网络应用的基本模型是客户机/服务器模型,这是一个不对称的编程模型,通信的双方扮演不同的角色,分别是客户机和服务器。
   一般发起通信请求的应用程序称为客户软件,该应用程序通过与服务器进程建立连接来发送请求,然后等待服务器返回所请求的内容。服务器软件一般是指等待接收并处理客户机请求的应用程序,通常由系统执行,等待客户机请求,并且在接收到请求之后,根据请求的内容,向客户机返回合适内容。
   本题中的程序较为简单,客户机接收用户在键盘上输入的文字内容,服务器将客户机发送来的文字内容直接返回给客户机,在通信过程中服务器方和客户机方都遵守的通信协议如下:
   由客户机首先发送请求,该请求由首部和内容两大部分组成,两个部分各占一行文字,通过行结束符’\n’隔离。
   首部只有一个Length域,用于指定请求的内容部分的长度,首部的结构为
   ’关键词Length’+’ ’+数值+’\n’
   内容部分为一行文字,其长度必须与Length域的数值相符。
   例如,客户机的请求为“Length 14\n hello,welcome to my home!”,服务器接收请求处理后返回文字“Hello,welcome to my home!”。
  【Socket程序】
   //服务器主程序部分
    #include<stdio.h>
   ……                                    //引用头文件部分略
   #define SERVER_PORT 8080               //服务器监听端口号为8080
   #define BACKLOG 5                      //连接请求队列长度
   int main(int argc,char *argv[ ])
   {
   int listenfd,connfd;                //监听套接字、连接套接字描述符
   struct sockaddr_in servaddr;         //服务器监听地址
   listenfd=  (1)  ,                       //创建用于监听的套接字
   if(listenfd<0)
       {
           fprintf(stderr,"创建套接字错误!")    //套接字创建失败时打印错误信息
           exit(1);
       }
   bzero(&servaddr.sizeof(servadd));    //将地址结构置空
   servaddr.sin_family=AF_INET;        //设置地址结构遵循TCP/IP协议
   servaddr.sin_addrs_addr=htonl.  (2)  
                          //设置监听的IP地址为任意合法地址,并将该地址转换为网络字节顺序
   servaddr.sin_port=(3);    //设置监听的端口,并转化为网络字节顺序
   if(bind((4))<0)
       {
           fprintf(stderr,"绑定套接字与地址!"),
       exit(1);
       }              //将监听地址与用于监听的套接字绑定,绑定失败时打印错误信息
   if(listen(listedfd,BACKLOG)<0)
       {
           fprintf(stderr,"转换套接字为监听套接字!");
           exit(1);
       }             //将用于监听的套接字由普通套接字转化为监听套接字
   for(;;)
       {
           connfd=(5);
                    //从监听套接字的连接队列中接收已经完成的连接并创建新的连接套接字
           if(connfd<0)
               {
                   fprintf(stderr,"接收连接失败!");
                   exit(1);
                }//接收失败打印错误信息
            serv_respon(connfd);    //运行服务器的处理函数
             (6);                    //关闭连接套接字
       }
       close(listenfd);            //关闭监听套接字
   }
     //服务器通信部分
   #include<stdio.h>
   ……                            //引用头文件部分略
   void serv_respon(int sockfd)
   {
   int nbytes;
   char buf[1024]
   for(;;)
       {
           nbytes=read_requ(sockfd,buf,1024);
                                 //读出客户机发出的请求,并分析其中的协议结构,获知请求的内
                                 //部分的长度,并将内容复制到缓冲区buf中
           if(nbytes==0)
               return;          //如客户机结束发送就退出
           else if(bytes<0)
               {
                   fprintf(siderr,"读错误情息:%s\n",strerror(errno));
                   return;
               } //读请求错误打印错误信息
           if(write_all(sockfd,buf,nbytes)<0)
                               //将请求中的内容部分反向发送回客户机
           fprintf(siderr,"写错误信息:%s\n",strerror(errno));
       }
   }
   int read_requ(int sockfd,char*buf int size)
   {
   char inbuf[256],
   int n;
   int i;
   i=read_line(sockfd,inbuf,256);
                              //从套接字接收缓冲区中读出一行数据,该数据为客户请求的首部
   if(i<0)
       return(i);
   else if(i==0)
       return(0);
   if(strncmp(inbuf," ",6)==0)
        sscanf((7),"%d",&n),//从缓冲区buf中读出长度信息
   else
       {
           sprintf(buf," ",14);
           return(14);
       }//取出首部Length域中的数值,该数值为内容部分的长度
   return(read_all(sockfd,buf,n)),//从接收缓冲区中读出请求的内容部分
   }
   int get_char(int fd,char*ch)
   /*get_char的处理方式较为特殊,并不是每次调用read函数读一个字符,而是一次从缓冲区中读一块内容,再一次一个字符提交给函数read_line,如果提交完了就再读一块,这样可以提高读缓冲区的效率。另外,由于客户机是分两次调用writ_all函数将请求的首部和内容发送给服务器,因此get_char不会取出请求内容部分的字符*/
   {   //声明静态变量,在get_char多次被调用期间,该变量的内存不释放
       static int offset=0;
       static int size=0;
       static char buffC[1024];
       for(;size<=0‖(8);)
       {
           size=read(M,bur,1024),//一次从套接字缓冲区中读出一个数据块
           if(size<0)
           {    if(errno==EINTR)
                {  size=0;
                contine;//EINTR表示本次读操作没有成功,但可以继续使用该套接字读出数i
                }
           else
                return(-1);
           }
       }
       offset = 0,    //读出数据后,将偏址置为0
       *ch = buf[(9)];    //将当前的字符取出,并将偏址移向下一字符
       return(1);
   }
   int read_line(int fd,char * buf,int maxlen)
                //函数read_line的作用是读出请求的首部,其处理的方法是每次调用get_char函数
               //取出一个字符,检查该字符是否是回车符’\n’,如果是回车符,就返回请求的首部
   {
       int i,n;
       char ch;
       for(i=0;i<maxlen;)
           {
                    n=get_char(fd,&ch);//取出一个字符
                   if(n==1)
                     {  buff[i++]=ch;//将字符加入字符串中
                        if((10))
                          break;
                     }   
               else  if(n<0)
                          return(-1);
                     else
                          break;
           }
       buf=’\0’;
       return(i);
   }
  【部分Socket数据结构与函数说明】
   1.地址结构
   ①sockaddr_in类型的结构定义;
   sockaddr_in是通用套接字结构SOCkaddr在TCP/IP协议下的结构重定义,为TCP/IP套接字地址结构。
     Struct sockaddr_in{
         short int sin_family;          //地址类型AF_XXX,其中AF_INET为TCP/IP专用
         unsigned short int sin_port;  //端口号
         struct in_addr sin_addr;      //Internet地址
         //端口号以及Internet地址使用的是网络字节顺序,需要通过函数htons转换
     }

     struct_inaddr{
         _u32 s_addr;                //类型为unsignel long
     }
   ②hostent类型的结构定义:
     struct hostent{
         char *h_name;              //主机的正式名称
         char* * h_aliases;         //别名列表
         int h_addrtvPe              //主机地址类型:AF_XXX
         int h_length:              //主机地址长度:4字节(32位)
         char* *h_addr_list;        //主机IP地址列表
       }
       #define h_addr h_addr_list[0]
   2.基本函数
   ①int socket(int domain,int type,int protocol);
   函数socket创建一个套接字描述符,如果失败则返回-1。domain为地址类型;type为套接字类型,本题中为SOCK_STREAM;protocol指定协议,本题中为0。
   ②int connect(int sockfd,struct sockaddr *servaddr,int addrlen);
   函数connect与服务器建立一个连接,成功返回0,失败返回-1。servaddr为远程服务器的套接字地址,包括服务器的IP地址和端口号;addrlen为地址的长度。
   ③int read(int fd,char * bur,int len);
   int write(int fd,char *buf,int len);
   函数read和write从套接字读和写数据,成功返回数据量大小,否则返回-1。buf指定数据缓冲区,len指定接收或发送的数据量大小。
   ④int bind(int sockfd,struct sockaddr *myaddr,int addrlen);
   函数bind将本地地址与套接字绑定在一起,成功返回0,否则返回-1;myaddr是本机地址:addrlen为套接字地址结构的长度。
   ⑤int listen(int sockfd,int backlog);
   函数listen将一个套接字转换为倾听套接字,成功返回0,否则返回-1;backlog为请求队列的最大长度。
   ⑥int accept(int sockfd,struct sockaddr *addr,int *addrlen);
   函数accept从监听套接字的完成连接中接收一个连接,如果完成连接队列为空,那么这个进程睡眠,失败则返回—1,成功时返回新的套接字描述符。sockfd为监听套接字, addr为客户机的地址,addlen为地址长度,在调用时用常量NULL代替addr与addlen表示无需取出客户机的地址信息。
   ⑦struct hostent *gethostbyname(const char *hostname);
   函数gethostbyname查询指定的域名地址对应的IP地址,返回一个hostent结构的指针,如果不成功则返回NULL。
   3.用户自定义函数
   ①int read_all(int fd,void *buf,int nbyte);
   函数read_all从参数fd指定的套接字描述符中读取nbytes字节数据至缓冲区bur中,成功则返回实际读的字节数(可能小于nbyte),失败则返回-1。
   ②int write_all(int fd,void * buf,int nbyte);
   函数write_all向参数fd指定的套接字描述符中写入缓冲区buf前nbyte字节的数据,成功则返回实际写的字节数(始终等于nbyte),失败则返回-1。
   ③write_requ函数为客户机发送请求的函数。
   ④read_requ函数为服务器获取请求的函数。

选项

答案(1)socket(AF_INET, SOCK_STREAM, 0) (2) INADDR_ANY (3) htons(SERVER_PORT) (4) listenfd, (struct soekaddr * ) &servaddr, sizeof(servaddr) (5) accept(listenfd, NULL, NULL) (6) close(connfd) (7) buf + 6 (8) offset = = size (9) offset++ (10) ch == ’\n’ 本试题考查考生实际的套接口网络编程能力。 (1)本题对服务器主程序中(1)~(6)空缺处的解答思路如下: ①管套接口(Socket)通过一组管套函数使应用程序在本地系统和远程系统之间建立通信信道来实现连接的管理和数据的传输。图6-26所示为虚电路服务中管套函数的调用顺序流程图,图6-27所示为数据报服务中管套函数的调用顺序流程图。 [*] ②本试题中程序分为服务器主程序和服务器通信程序两部分。服务器主程序为int main(int argc,char *argv[ ])。主程序流程如下: 定义监听套接字,连接套接字描述符→定义服务器监听地址→定义监听套接字并判误→地址结构初始化→设置合法的监听IP地址并转化为网络字节顺序格式→设置监听端口并转化为网络字节顺序格式→地址与套接字绑定并判误→监听套接字转化并判误→接收已完成连接,创建新的连接套接字→连接失败否?是,则结束;否,则接收处理→关闭连接套接字→关闭监听套接字→结束。 ③根据图6-26所示的虚电路服务中管套函数的调用顺序流程图可知,(1)空缺处调用socket(int domain,int type,int protocol)函数创建一个套接字描述符。其中,domain为地址类型,本题中为AF_INET;type为套接字类型,本题中为SOCK_STREAM;protocol指定协议,本题中为0。因此(1)空缺处需填入“socket(AF_INET,SOCK_STREAM,0)”。 ④根据(2)空缺处所在行的注释可知,此空缺处用于设置监听IP地址为任意合法地址,故属性为INADDR_ANY。 ⑤(3)空缺处用于“设置监听的端口,并转化为网络字节顺序”,根据sockaddr_in类型的结构定义中“unsigned short int sin_port”可知,此处需调用的函数及属性是htons (SERVER_PORT)。 ⑥实现“将监听地址与用于监听的套接字绑定”功能需调用函数bind(int sockfd, struct sockaddr * myaddr,int addrlen)。其中,myaddr是本机地址,本题中可采用“(struct sockaddr *)&servaddr”获取;addrlen为套接字地址结构的长度,本题中可通过函数“sizeof(servaddr)”获取。因此(4)空缺处填写的内容是“listenfd,(struct sockaddr *) &servaddr,sizeof(servaddr)”。 ⑦实现“从监听套接字的连接队列中接收已经完成的连接,并创建新的连接套接字”功能需调用函数accept(int sockfd,struct sockaddr *addr,int *addrlen)。其中,sockfd为监听套接字,本题中为“listenfd”;addr为客户机的地址;addlen为地址长度。本题在调用accept函数时,用常量NULL代替addr与addlen表示无需取出客户机的地址信息。因此(5)空缺处填写的内容是“accept(listenfd,NULL,NULL)”。 ⑧由“close(listenfd);”语句用于关闭监听套接字,以及监听套接字、连接套接字描述符的数据类型定义语句“int listenfd,connfd”可类比推理出,关闭连接套接字的语句是“close(connfd);”。 (2)本题对服务器通信程序中(7)~(10)空缺处的解答思路如下: ①服务器通信部分包括四个函数: void serv_respon(int sockfd) int read_requ(int sockfd,char * buf int size) int get_char(int fd,char * ch) int read_line(int fd,char * buf,int maxlen) 其中,int read_requ(int soekfd,char * buf int size)程序的流程是:从套接字接收缓冲区中读出一行数据→从缓冲区buf中读出长度信息→从接收缓冲区中读出请求的内容部分。 ②由判断语句“if(strncmp(inbuf,"",6)==0)”可知,read_requ程序每次从缓冲区buf中读取信息的长度为6个字节,因此(7)空缺处实现“从缓冲区bur中读出长度信息”的空缺内容是“buf+6”。 ③由程序中函数get_char(int fd,char * ch)的注释内容“get_char的处理方式较为特殊,并不是每次调用read函数读一个字符,而是一次从缓冲区中读一块内容,再一次一个字符提交给函数read_line,如果提交完了就再读一块,这样可以提高读缓冲区的效率”可知,(8)空缺处循环条件设置与数据块长度size相关。再由语句“offset=0”的注释“读出数据后,将偏址置为0”可知,(8)空缺处循环条件设置与偏址offset相关,其填写的内容是“offset==size”。 ④对于(9)空缺处的解答源于其注释行“将当前的字符取出,并将偏址移向下一字符”,注意是取出字符后才将偏址移向下一字符,因此需填入的内容是“offset++”。如果填写的内容是“++offset”,则对“* ch=buf[++offset];”语句的解释是,先将偏址移向下一字符并将该字符取出。 ⑤对于(10)空缺处的解答源于执行语句“break;”,其需填入的内容是“ch==’\n’”,用于判断结束条件。

解析
转载请注明原文地址:https://jikaoti.com/ti/dgI7FFFM
0

最新回复(0)