云计算百科
云计算领域专业知识百科平台

理解计算机系统_网络编程(5)_echo客户端和服务器

前言        

        以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定

引入

        接续上一篇理解计算机系统_网络编程(4)_套接字api-CSDN博客

一个叫做echo的网络程序

        本书P662给出了一个网络应用程序,由客户端程序和服务器端程序所组成.当连接建立起来后,用Unix I/O函数进行数据传输,因为前面没有系统研究过读写函数,看别人是怎么用的从中学习.

需求        

        本书原话:客户端在和服务器建立连接之后,客户端进入一个循环,反复从标准输入读取文本行,发送文本行给服务器,从服务器读取回送的行,并输出结果到标准输出. 当fgets在标准输入上遇到EOF时,或者因为用户在键盘上键入Ctrl+D,或者因为在一个重定向的输入文件中用尽所有的文本行时,循环终止.

        —fgets函数在标准上遇到EOF和因为在一个重定向的输入文件中用尽所有的文本行,好像是一个意思,即表示到达文件末尾,返回NULL.Ctrl+D表示被中断,也返回NULL.

echo客户端代码

        源码在本书P663,名称叫echoclient.c

代码精简的思考

        首先源码是可以精简的,并不是对代码有质疑,他的写法读起来很清晰.如果自己写,在熟练的基础上可以"浓缩".

        1>变量声明部分,去掉clientfd,host,port的声明

char buf[MAXLINE]; //表示缓冲字节数组
rio_t rio; //缓冲区对象

         2>去掉13,14行,第16行的代码这样写:

int clientfd=Open_clientfd(argv[1],argv[2]);

        两下一对比,源码表达的意思更清晰.所以还是不要精简比较好.

代码解读

第9到12行

        客户端main函数有3个参数,如果数量不对则报错.

第13,14行

        其中argv[1]表示传给main的第2个字符串参数,host—服务器主机名,即服务器域名

        argv[2]表示传给main的第3个字符串参数,port—端口号

第16行        

clientfd=Open_clientfd(host,port);

        调用Open_clientfd函数,即open_clientfd的包装函数,建立和服务器的连接,得到可读写的文件描述符clientfd,具体见上一帖.

第17行

Rio_readinitb(&rio,clientfd);

        调用rio_readinitb的包装函数Rio_readinitb.本书P628第1段讲了这个函数的含义,他是固定用法:每打开一个描述符,都会调用一次rio_readinitb函数.他将描述符fd和地址rp处的一个类型为rio_t的读缓冲区联系起来—黑体字是原话

====================================内容分割线↓============================

        关于Unix I/O函数,本书在这一章节上排版比较乱,先大概有个理解,以后整理再做系统性的分析.

====================================内容分割线↑============================

 第19到23行

while(Fgets(buf,MAXLINE,stdin)!=NULL){ //标准输入(键盘)→buf
Rio_writen(clientfd,buf,strlen(buf)); //buf→服务器端(通过clientfd)
Rio_readlineb(&rio,buf,MAXLINE); //服务器端→buf(通过&rio)
Fputs(buf,stdout); //buf→标准输出(屏幕)
}

—-19行 

        Fgets函数表示从文件描述符stdin中,每次读取MAXLINE-1个字节到字符数组buf中.对应变量声明中的char buf[MAXLINE];也就是说buf接收stdin传来的数据.stdin是表示标准输入的文件描述符,整型表达是0.

        Fgets的结束条件是:文件末尾EOF或者被中断.stdin是标准输入流,没有末尾,只有Ctrl+D可以让他结束(笔者个人理解).

—-20行

        Rio_writen是rio_writen的包装函数,含义在本书P627第6段中间:rio_wirten函数从位置usrbuf传送n个字节到描述符fd.—本书原话.因此这里的调用表示将buf数组中的数据传给描述符clientfd,结合向导图理解,数据发送给服务器端.结合第19行的代码,标准输入中的字符串发送到服务器端

—21行

       Rio_readlineb是rio_readlineb的包装函数,含义在本书P628最后一段:rio_readlineb函数从文件rp读出下一个文本行(包括结尾的换行符),将他复制到内存位置usrbuf,并且用NULL(零)字符来结束这个文本行.—本书原话.&rio是读缓冲区,代表从服务器发来的数据,这里表示将数据写入buf中

—22行

        Fputs是fputs函数的包装函数,表示将buf写入标准输出(屏幕),结合第21行,服务器端传来的数据被显示到屏幕上.

部分小结:代码注释了数据的走向,buf在数据传输的过程中起到了临时存储的作用.同时思考:前面提到了套接字是全双工的传输,但这里是不是没有表现出来?因为数据的进出都依赖于buf[MAXLINE]. 

第24行 

Close(clientfd);

           关闭文件描述符,本次连接结束.下一次连接需要重新运行客户端程序.

====================================内容分割线↓============================

        笔者在这里做个理解:套接字连接是一个资源配对的过程,客户端和服务器端各自分配的资源—端口port,而后抽象出clientfd和listenfd描述符,这是双方连接准备阶段做的事.当服务器端调用connect并且连接成功后,客户端生成connfd描述符,开始传数据.

        白话:两边找出可用的端口,形成描述符.客户端发起连接尝试,服务器端接受后连接产生.但还要考虑到阻塞,例如服务器端最大处理1024个连接,超过这个负荷,连接不会被接受而处于等待(阻塞)状态.若等待超时则连接取消等等状况.      

====================================内容分割线↑============================

echo服务器端代码

本书原话:在打开监听描述符后,他进入一个无限循环.每次循环都等待一个来自客户端的连接请求,输出已连接客户端的域名和IP地址,并调用echo函数为这些客户端服务. 在echo程序返回后,主程序关闭已连接描述符.一旦客户端和服务器关闭了他们各自的描述符,连接也就终止了.—黑体字是原话

        —解读:输出已连接客户端的域名和IP地址不是必须的,是程序这样写的.

代码解读

        大致和客户端也差不了多少.讲注意的几点

1>struct sockaddr_storage

        本书原话:注意,我们将clientaddr声明为 struct sockaddr_storage类型,而不是struct sockaddr_in类型.根据定义, struct sockaddr_storage结构足够大能够装下任何类型的套接字地址,以保持代码的协议相关性.

       解读:根据定义可以这么做,定义是什么?也不知道.只知道按书上所说这样可行,所以还是老办法"抄".

        本书P664代码第21行调用的包装函数Getnameinfo中使用了强制转换,使用(SA *)将 struct sockaddr_storage类型转换成struct sockaddr_in类型,以符合函数对参数类型的要求.

2>迭代服务器

        本书原话:简单的echo服务器一次只能处理一个客户端.这种类型的服务器一次一个地在客户端间迭代,称为迭代服务器(iterative server).在第12章中,我们将学习如何建立更加复杂的并发服务器(concurrent server),他能够同时处理多个客户端.

       解读:按照书中的意思,和代码第20行,一个客户端的端口对应一个服务器端口,一个clientfd描述符对应一个connfd描述符.当客户端发来EOF终止信号并关闭client描述符,服务器端同时关闭connfd描述符,连接结束.他们是一一对应的关系.

3>echo函数

        本书P664有echo函数的定义,和客户端差不多,用了Rio_readinitb函数初始化缓冲区,Rio_readlineb读取客户端发来的信息,Rio_writen向客户端发送信息.

4>EOF

        EOF是一种内核判定的条件,列举了几种情形:

        1)磁盘文件,当前文件位置超出文件长度时,发生EOF

                每个文件的末尾都有EOF

        2)因特网连接,当一个进程关闭连接他的那一端时,发生EOF.

                应用:根据向导图,客户端先用Ctrl+D从标准输入流stdin中跳出来,然后调用Close函数,进程关闭,发生EOF,这个EOF似乎没有什么表现.

        3)连接另一端的进程在试图读取流中最后一个字节之后的字节时,会检测到EOF

               应用:接第2)条

                服务器端的echo函数第10行,读取客户端发来的数据,到最后一个字节后,检测到EOF,跳出while,循环结束,接下来执行服务器端主函数中的Close(connfd),此时两边的描述符关闭,连接结束. 

while(n=Rio_readlineb(&rio,buf,MAXLINE)!=0)

        EOF和标准类似,由程序员知道什么时候出现,怎么用.但EOF是由底层实现(硬件或者内核),不必关心(当然能知道怎么实现更好) 

小结

        一个网络通信的例子,从建立连接到传送文本.

                        

赞(0)
未经允许不得转载:网硕互联帮助中心 » 理解计算机系统_网络编程(5)_echo客户端和服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!