11.4.9 echo 客户端和服务器的示例
11.4.9 echo 客户端和服务器的示例
学习套接字接口的最好方法是研究示例代码。图 11-20 展示了一个 echo 客户端的代码。在和服务器建立连接之后,客户端进入一个循环,反复从标准输入读取文本行,发送文本行给服务器,从服务器读取回送的行,并输出结果到标准输出。当 fgets 在标准输入上遇到 EOF 时,或者因为用户在键盘上键入 Ctrl+D,或者因为在一个重定向的输入文件中用尽了所有的文本行时,循环就终止。
code/netp/echoclient.c
#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
if (argc != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = argv[2];
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}图 11-20 echo 客户端的主程序
循环终止之后,客户端关闭描述符。这会导致发送一个 EOF 通知到服务器,当服务器从它的 rio_readlineb 函数收到一个为零的返回码时,就会检测到这个结果。在关闭它的描述符后,客户端就终止了。既然客户端内核在一个进程终止时会自动关闭所有打开的描述符,第 24 行的 close 就没有必要了。不过,显式地关闭已经打开的任何描述符是一个良好的编程习惯。
图 11-21 展示了 echo 服务器的主程序。在打开监听描述符后,它进入一个无限循环。每次循环都等待一个来自客户端的连接请求,输出已连接客户端的域名和 IP 地址,并调用 echo 函数为这些客户端服务。在 echo 程序返回后,主程序关闭已连接描述符。一旦客户端和服务器关闭了它们各自的描述符,连接也就终止了。
第 9 行的 clientaddr 变量是一个套接字地址结构,被传递给 accept。在 accept 返回之前,会在 clientaddr 中填上连接另一端客户端的套接字地址。注意,我们将 clientaddr 声明为 struct sockaddr_storage 类型,而不是 struct sockaddr_in 类型。根据定义,sockaddr_storage 结构足够大能够装下任何类型的套接字地址,以保持代码的协议无关性。
code/netp/echoserveri.c
#include "csapp.h"
void echo(int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr; /* Enough space for any address */
char client_hostname[MAXLINE], client_port[MAXLINE];
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE,
client_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, client_port);
echo(connfd);
Close(connfd);
}
exit(0);
}图 11-21 迭代 echo 服务器的主程序
注意,简单的 echo 服务器一次只能处理一个客户端。这种类型的服务器一次一个地在客户端间迭代,称为迭代服务器(iterative server)。在第 12 章中,我们将学习如何建立更加复杂的并发服务器(concurrent server),它能够同时处理多个客户端。
最后,图 11-22 展示了 echo 程序的代码,该程序反复读写文本行,直到 rio_readlineb 函数在第 10 行遇到 EOF。
code/netp/echo.c
#include "csapp.h"
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
printf("server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}图 11-22 读和回送文本行的 echo 函数
旁注 在连接中 EOF 意味什么?
EOF 的概念常常使人们感到迷惑,尤其是在因特网连接的上下文中。首先,我们需要理解其实并没有像 EOF 字符这样的一个东西。进一步来说,EOF 是由内核检测到的一种条件。应用程序在它接收到一个由
read函数返回的零返回码时,它就会发现出 EOF 条件。对于磁盘文件,当前文件位置超出文件长度时,会发生 EOF。对于因特网连接,当一个进程关闭连接它的那一端时,会发生 EOF。连接另一端的进程在试图读取流中最后一个字节之后的字节时,会检测到 EOF。