理解计算机系统_网络编程(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是由底层实现(硬件或者内核),不必关心(当然能知道怎么实现更好)

小结

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

相关推荐
Antonio9154 天前
【网络编程】UDP协议 和 Socket编程
网络·udp·网络编程
jllws17 天前
理解计算机系统_网络编程(3)
网络编程·计算机系统
jllws110 天前
理解计算机系统_网络编程(1)
网络编程·计算机系统
Thanks_ks20 天前
探秘 Python 网络编程:构建简单聊天服务器
python·网络编程·socket·tcp·客户端·套接字·聊天服务器
矮油0_o1 个月前
5.好事多磨 -- TCP网络连接Ⅱ
服务器·网络·tcp/ip·网络编程·socket
joker D8881 个月前
深入理解:阻塞IO、非阻塞IO、水平触发与边缘触发
linux·网络编程·epoll
UestcXiye1 个月前
《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型
c++·计算机网络·网络编程·ip·tcp
攻城狮7号1 个月前
【第22节】windows网络编程模型(WSAAsyncSelect模型)
c++·windows·网络编程·windows编程·windows sdk
述雾学java1 个月前
Servlet、Servlet的5个接口方法、生命周期、以及模拟实现 HttpServlet 来写接口的基本原理
java·servlet·网络编程·java基础