CSAPP 11.4 Socket Interface
CSAPP 11.4 socket interface
以下 socket 全用英文,套接字这个翻译太辣鸡了,不知所云
socket interface 是一组函数,和 Unix I/O 函数结合起来,创建网络应用。
实际上就是 IP: port
下图给出了一个典型的 C/S 事务的上下文中的 socket 接口概述
11.4.1 socket 地址结构
从 Linux 内核看:一个 socket 是通信的一个 端点
从 Linux 程序看: socket 是一个 有相应描述符的打开文件
Internet 的 socket 地址存放在如图 11-13 所示的文件类型为 socketaddr_in
的 16 字节结构中。
对于因特网应用,
sin_family
成员是AF_INET
sin_port
成员是一个 16 位的端口号sin_addr
成员是一个 32 位的 IP 地址
IP 地址和端口号总是以网络字节顺序(大端法)存放的
- code/netp/netpfragments.c
/* IP socket address structure */
struct sockaddr_in {
unit16_t sin_family; /* Protocol family (always AF_INET)*/
unit16_t sin_port; /* Port number in network byte order */
struct in_addr sin_addr; /* IP address in network byte order */
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};
/* Generic socket address structure (for connect, bind, and accept) */
struct socketaddr {
unit16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data*/
};
AF_INET
- AF ---- Address Family 的缩写
- StackOverFlow 上的解释:
AF_INET
is an address family that is used to designate the type of addresses that your socket can communicate with (in this case, Internet Ptotocol v4 addresses). When you create a socket, you have to specify its address family, and then you can only use addresses of that type with the socket. The Linux kernel, for example, supports 29 other address families such as UNIX (AF_UNIX
) sockets and IPX (AF_IPX
), and also communications with IRDA and Bluetooth (AF_IRDA
andAF_BLUETOOTH
, but it is doubtful you'll user these at such a low level).- For the most part, sticking with
AF_INET
for socket programming over a network is the safest option. There is alsoAF_INET
for Internet Protocol v6 addresses._in 后缀
是 internet 的缩写,而不是 input 的缩写
connect、bind 和 accept 函数要求一个指向与协议相关的 socket 地址结构的指针。解决办法:
定义 socket 函数要求一个指向通用 socketaddr 结构的指针,然后要求应用程序将与协议特定的结构的指针强制转换成这个通用结构
定义下面的类型:
typedef struct sockaddr SA;
无论何时需要将 sockaddr_in
结构强制转换成通用 sockaddr
结构时,都是用这个类型。
11.4.2 socket 函数
client 和 server 使用 socket 函数创建一个 socket 描述符(socket descriptor)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
// 返回: 若成功则为非负描述符,若出错则为 -1
如果想要是 socket 成为连接的一个端点,就是用如下硬编码的参数来调用 socket 函数:
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
其中,
AF_INET
表示正在使用 32 位 IP 地址SOCK_STREAM
表示这个 socket 是连接的一个端点
最好的方法是使用 getaddrinfo
函数来自动生成这些参数,这样代码就与协议无关了
socket 返回的 clientfd
描述符仅是部分打开的,还不能用于读写。如何完成打开 socket 的工作,取决于我们是 client 还是 server。下节描述 client 如何打开 socket
11.4.3 connect 函数
client 调用 connet
函数来 建立和 server 的连接
#include <sys/socket.h>
/**
clientfd 客户端文件描述符
*addr socket地址
addrlen sockaddr_in 的长度
*/
int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回:若成功则为0, 若出错则为 -1
connect
函数试图与 socket 地址为 addr 的server 建立一个 Internet 连接,其中 addrlen
是 sizeof(sockaddr_in)
。connect
函数会阻塞,直到连接成功建立或是发生错误。如果成功, clientfd 描述符就可以准备读写了,并且得到的连接是由 socket 对 (x: y, addr.sin_addr: addr.sin_port)
刻画的
-
x ---- 客户端 IP 地址
-
y ---- 临时端口
它唯一地确定了客户端主机上的客户端进程。
对于 socket ,最好的方法使用 getaddrinfo
来为 connect
提供参数(见11.4.8)
11.4.4 bind 函数
服务器用 bind、listen、accept 来和 client 建立连接
#include <sys/socket.h>
int bind (int socketfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回: 成功 0 ,出错 -1
bind 函数告诉内核将 addr 中的服务器 socket 地址和 socket 描述符 socketfd
联系起来。
参数 addrlen 是 sizeof(sockaddr_in)
。对于 socket
和 connect
,最好的方法使用 getaddrinfo
来为 connect
提供参数(见11.4.8)
11.4.5 listen 函数
客户端是发起连接请求的 主动实体, server 是等待来自 client 的连接请求的 被动实体。
默认情况下,内核会认为 socket 函数创造的描述符对应于 主动 socket(active socket) ,它存在于一个连接的 client。server 调用 listen
函数告诉内核,描述符是被 server 而不是 client 使用的。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
// 返回: 成功 0 , 失败 -1
listen
函数 将 sockfd
从一个主动 socket 转化为一个 监听 socket (listening socket),该 socket 可以接收来自客户端的连接请求。
backlog
参数暗示了内核在开始拒绝连接请求之前,队列中要排队的未完成的连接请求的数量。
backlog 参数的确切含义要求对 TCP/IP 协议的理解,通常会把它设为一个比较大的值 ,如 1024
11.4.6 accept 函数
server 通过调用 accept
函数来等待来自客户端的连接请求
#include <sys/socket.h>
/**
listenfd 监听描述符
addr socket 地址
addrlen socket 地址长度?
*/
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
// 返回:若成功则为非负连接描述符,若出错则为 -1
accept 函数等待来自客户端的连接请求 到达监听描述符 listenfd
,然后在 addr 中填写客户端的 socket 地址,并返回一个 已连接描述符(connected descriptor),这个描述符可被用来利用 Unix I/O 函数与客户端通信。
listenfd 和 connected descriptor 之间的区别:
- listenfd 作为客户端连接请求的一个端点,通常被创建一次,并存在于 服务器的整个生命周期。
- connected descriptor 是客户端和服务器之间已经建立起来的连接的一个端点。服务器每次接受连接请求时都会创建一次,只存在于服务器为一个客户端服务的过程中。
图 11-14 描绘了 listenfd 和 connected descriptor 的角色。
- server 调用
accept
,等待 连接请求 connect request 到达 listenfd,具体地设定为 描述符3。描述符 0~2 是预留给标准文件的。 - 客户端调用
connect
函数,发送一个 connect request 到 listenfd accept
函数 打开了一个新的 已连接描述符 connfd(假设是描述符 4),在 clientfd 和 connfd 之间建立连接,并且随后返回 connfd 给应用程序。客户端也从 connect 返回
在这一点以后,客户端和服务器就可以分别通过读和写 clientfd 和 connfd 来回传送数据了。
- 为什么有 监听描述符 listenfd 和 已连接描述符 connfd 的区别?
- 帮助建立并发服务器,同时处理许多客户端连接
- 如:每次一个 连接请求 connect request 到达 listenfd 时,可以派生 fork 一个新的进程,它通过已连接描述符 connfd 与客户端连接。在第 12 章中将介绍更多关于并发服务器的内容。
11.4.7 主机和服务的转换
Linux 提供 getaddrinfo
和 getnameinfo
函数来实现二进制 socket 地址结构和主机名、主机地址、服务名和端口号的字符串表示之间的相互转化。
11.4.7.1 getaddrinfo 函数
getaddrinfo
函数将主机名、主机地址、服务名和端口号的字符串表示转化为 socket 地址结构。它是 deprecated/obsolete gethostbyname
和 getservbyname
函数的替代品。和这两个函数不同,getaddrinfo
是可重入的,且适用于任何协议。
deprecated
obsolete a. 废弃的,淘汰的
reentrant a. 可重入的
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/**
host 主机
service 服务
hints
result
如果 OK 返回 0,如果错误返回 非零的 error code
*/
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints,
struct addrinfo **result);
// 释放地址?
void freeaddrinfo(struct addrinfo *result);
// 根据错误代码返回错误信息
// 这里的 ai 应该是 addrinfo 的缩写,gai 应该是 getaddrinfo
const char *gai_strerror(int errcode);
c语言 **
指向指针的指针
ai 是 addrinfo 的缩写
给定 host 和 service 这两个 socket 地址的组件,getaddrinfo
函数返回一个结果 result,这个返回值 指向 addrinfo 结构的一个链表,其中每个结构指向一个对应于 host 和 service 的 socket 地址结构(图 11.15)。
在客户端调用 getaddrinfo
函数之后,它会遍历这个链表,尝试每个 socket 地址,直到调用 socket 和 connect 成功,建立起连接。类似地,服务器会尝试链表上的每个 socket 地址,直到调用 socket 和 bind 成功,且描述符被绑定到一个有效的 socket 地址上。为避免内存泄漏,最终应用必须通过调用 freeaddrinfo
释放链表。如果 getaddrinfo
返回一个非零的错误代码,应用可以调用 gai_strerror
可将错误代码转换为信息字符串
getaddrinfo
的 host 参数可以是 域名或者 数值地址,如 点分十进制(dotted-decimal) IP 地址。service 参数可以是 service 名,如 http,或者一个 十进制端口号。如果不想把 主机名转换为地址,可以将 host 设为 NULL。对 service 来说一样,但是 host 和 service 只要有一个需要指定。
decimal a. 十进制的
hints 参数是一个可选参数,是一个 addrinfo 结构(如下代码所示),提供对 getaddrinfo
函数返回的 socket 地址链表更好的控制。在作为 hints 参数传输时,只能设置 ai_family
,ai_socktype
,ai_protocol
和 ai_flags
字段。其他字段必须被设为 0 (或者 NULL)。
struct addrinfo {
int ai_flags; /* Hints argument flags */
int ai_family; /* First arg to socket function */
int ai_socktype; /* Second arg to socket function */
int ai_protocol; /* Third arg to socket function */
char *ai_canonname; /* Canonical hostname */
size_t ai_addrlen; /* Size of ai_addr struct */
struct sockaddr *ai_addr; /* Ptr to socket address structure */
struct addrinfo *ai_next; /* Ptr to next item in linked list */
};
canonical a. 宗教的;规范化的
field n. 字段
实际上,我们使用 memset 来将整个 structure 置零,然后有选择地设置部分字段:
memset() is used to fill a block of memory with a particular value
-
默认地,
getaddrinfo
可以返回 IPv4 和 IPv6 socket 地址。将ai_family
设置为AF_INET
可以将列表限制为 IPv4 地址。AF_INET6
对应 IPv6 地址。 -
对于每个和 host 关联的唯一地址,
getaddrinfo
函数可以返回最多 3 个 addrinfo 结构,每个结构都有不同的ai_socktype
字段:一个是连接,一个是数据报(datagrams,本书未讲述),一个是原始 socket(本书未讲述)。ai_socktype
设置为SOCK_STREAM
将列表限制为 :对每个唯一地址,最多只有一个 addrinfo 结构,该结构的 socket 地址可以被用来作为连接的一个端点。 -
ai_flags
字段是一个 位掩码 ,可以进一步修改默认行为。对不同的值进行或操作运算。以下是一些有用的值:- AI_ADDRCONFIG
- 当你正在使用连接,推荐此值。只要本地主机配置为 IPv4,它就会要求
getaddrinfo
函数返回 IPv4 地址。对 IPv6 同理。
- 当你正在使用连接,推荐此值。只要本地主机配置为 IPv4,它就会要求
- AI_CANONNAME
- 默认情况下,
ai_canonname
字段为 NULL。如果设置为这个 flag,它将使getaddrinfo
在链表中的第一个 addrinfo 结构中的ai_canonname
字段指向 host 的权威(canonical /官方的) 名字,如图 11.15 。
- 默认情况下,
- AI_NUMERICSERV
- 默认地,service 参数可以是一个 service 名或者 端口号。此 flag 强制使 service 参数为一个端口号
- AI_PASSIVE
- 默认地,
getaddrinfo
返回可以被客户端在调用 connect 函数时,用作 主动 active sockets 的 socket 地址。此 flag 使getaddrinfo
返回可以被 服务器用作 监听 listening sockets 的 socket 地址。这种情况下,host 参数应为 NULL。在 结果 socket 地址结构中的 address 字段将会是通配符地址 wildcard address,它告诉内核这个服务器将会接受发送到该主机所有 IP 地址的请求。这是所有示例服务器期望的行为。
- 默认地,
- AI_ADDRCONFIG
ORing 或操作运算
active 主动
passive 被动
wildcard n. 通配符 wildcard address 通配符地址
当 getaddrinfo
在输出列表中创建一个 addrinfo 结构时,它填充除了 ai_flag
字段之外的所有其它字段。ai_addr
字段指向一个 socket 地址结构,ai_addrlen
字段给出 socket 地址结构的大小,ai_next
字段指向链表汇总下一个 addrinfo 结构。其它字段描述了 socket 地址的不同属性。
addrinfo 结构中的字段是 opaque 不透明的,意味着它们可以直接被传递到 sockets 接口的函数中,避免被程序代码更多地操作。例如,ai_family
,ai_socktype
和 ai_protocol
可以被直接传给 socket。类似地,ai_addr
和 ai_addrlen
可以被直接传递给 connect
和 bind
。这一特性让我们编写的客户端和服务器可以独立于某个特殊版本的 IP 协议。
opaque a. 不透明的
11.4.7.2 getnameinfo 函数
getnameinfo
函数和 getaddrinfo
函数是相反的,它将一个 socket 地址结构转换成相应的 host 名和 service 名字符串。getnameinfo
是 obsolete gethostbyaddr
和 getservbyport
函数的替代,他是可重入和 协议独立的(protocol-independent)。
#include <sys/socket.h>
#include <netdb.h>
/**
sa 指向一个 socket 地址结构
salen 此 socket 地址结构的大小
host 指向一个缓冲区(buffer)
hostlen 此缓冲区 buffer 的大小
service 指向一个缓冲区(buffer)
servlen 此缓冲区 buffer 的大小
*/
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen,
char *service, size_t servlen, int flags);
// 返回: 如果 OK 返回 0, 出错返回非零错误代码
7 个参数,3个指针,3个长度,外加一个 flag
参数 sa
指向一个大小为 salen
字节的 socket 地址结构,参数 host
指向一个大小为 hostlen
字节的缓冲区,参数 service
指向一个大小为 servlen
字节的缓冲区。
getnameinfo
函数将 socket 地址结构 sa
转换为相应的主机名 host 和服务名 service name,且将它们复制到 host 和 service 缓冲区。如果 getnameinfo
返回一个非零错误代码,程序可以通过调用 gai_strerror
把代码转换为相应的字符串
如果我们不想要主机名,我们可以将 host 设为 NULL 且将 hostlen 设为 0。对 service 字段同理。但是两者其一必须不为空。
flags
参数是一个位掩码,用来修改默认行为。通过对不同的值进行 或操作运算可得到,下面是一些又用的值:
NI 是 nameinfo 的缩写
- NI_NUMERICHOST
- 默认地,
getnameinfo
返回 host 中的域名。设置为此 flag 将使它返回一个数值地址字符串而不是域名。
- 默认地,
- NI_NUMERICSER
- 默认地,
getnameinfo
将会查询/etc/services
且如果可能的话,返回一个 service 名 而不是一个端口号。设置为此 flag 将会强制跳过 查询 且只返回端口号。
- 默认地,
下列代码,名为 HOSTINFO
,使用 getaddrinfo
和 getnameinfo
来展示 一个域名和 它相关联的 IP 地址的映射。此程序和 11.3.2 中的 NSLOOKUP
程序类似。
Figure 11.17 HOSTINFO
displays the mapping of a domain name to its associated IP addresses.
code/netp/hostinfo.c
#include "caspp.h"
int main(int argc, char **argv) // argc 是参数数量?
{
struct addrinfo *p, *listp, hints; // 指针,列表指针,提示
char buf[MAXLINE]; // 缓冲区
int rc, flags; // rc getaddrinfo() 返回的 数字代码
if (argc != 2) { // 参数数量不是两个?
fprintf(stderr, "usage: %s <domain name>\n", argv[0]);
exit(0);
}
/* Get a list of addrinfo records */
memset(&hints, 0, sizeof(struct addrinfo)); // 置空内存?
hints.ai_family = AF_INET; /* IPv4 only */
hints.ai_socktype = SOCK_STREAM; /* Connections only */
// 注: c 语言中 a = b 返回右值
// 此处 argv[1] 为 host , service 为 null(不想转换 端口号?)
// hints 在上面定义过了
// 如果出现错误
if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc));
exit(1);
}
/* Walk the list and display each IP address */
// Display address string instead of domain name
flags = NI_NUMERICHOST;
for (p = listp; p; p = p->ai_next) {
// socket address 是 p,host 缓冲区 是 buf,service 缓冲区无
Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0, flags);
printf("%s\n", buf);
}
/* Clean up */
Freeaddrinfo(listp);
exit(0);
}
First, we initialize the hints
structure so that getaddrinfo
returns the addresses we want. In this case, we are looking for 32-bit IP addresses (line 16) that can be used as end points of connections (line 17). Since we are only asking getaddrinfo
to convert domain names, we call it with a NULL service
argument.
After the call to getaddrinfo
, we walk the list of addrinfo
structures, using getnameinfo
to convert each socket address to a dotted-decimal address string. After walking the list, we are careful to free it by calling freeaddrinfo
(although for this simple program is not strictly necessary).
When we run HOSTINFO
, we see that twitter.com
maps to four IP addresses, which is what we saw using NSLOOKUP
in Section 11.3.2.
linux> ./hostinfo twitter.com
199.16.156.102
199.16.156.230
199.16.156.6
199.16.156.70
Practice Problem 11.4
The getaddrinfo
and getnameinfo
functions subsume the functionality of inet_pton
and inet_ntop
, respectively, and they provide a higher-level of abstraction that is independent of any particular address format. To convice yourself how handy this is, wirte a version of HOSTINFO
(Figure 11.17 ) that uses inet_ntop
instead of getnameinfo
to convert each socket address to a dotted-decimal address string.
subsume v. 包含;把 。。。 归入
提供了更高级别的、独立于任何特殊地址格式的抽象
11.4.8 Helper Functions for the Sockets Interface
The getaddrinfo
function and the sockets interface can seem somewhere daunting when you first learn about them. We find it convinient to wrap them with higher-level helper functions, called open_clientfd
and open_listenfd
, that clients and servers can use when they want to communicate with each other.
daunt v. 使气馁,使胆怯
### 11.4.8.1 The open_clientfd Function
A client establishes a connection with a server by calling open_clientfd
.
#include "csapp.h"
int open_clientfd(char *hostname, char *port);
// Returns: descriptor if OK, -1 on error
The open_clientfd
function establishes a connection with a server running on hostname and listening for connection on port number port. It returns an open socket descriptor that is ready for input an output using the Unix I/O functions. Figure 11.18 shows the code for open_clientfd
.
Figure 11.18 open_clientfd
: Helper function that establishes a connection with a server. It is reentrant and protocol-independent.
int open_clientfd(char *hostname, char *port) {
int clientfd;
struct addrinfo hints, *listp, *p;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Open a connection */
hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */
// |= 按位或赋值 跟 += 类似 a |= b; 就是 a = a | b; 把 a | b 的值赋给 a
hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */
Getaddrinfo(hostname, port, &hints, &listp);
/* Walk the list for one that we can successfully connect to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Connect to the server */
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break; /* Success */
Close(clientfd); /* Connect failed, try another */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* All connects failed */
return -1;
else /* The last connect succeeded */
return clientfd;
}
We call getaddrinfo
, which returns a list of addrinfo structures, each of which points to a socket addresss structure that is suitable for establishing a connection with a server running on hostname
and listening on port
. We then walk the list, trying each list entry in turn, until the calls to socket
and connect
succeed. If the connect
fails, we are careful to close the socket descriptor before trying the next entry. If the connect
succeeds, we free the list memory and return the socket descriptor to the client, which can immediately begin using Unix I/O to communicate with the server.
Notice how there is no dependence on any particular version of IP anywhere in the code. The arguments to socket
and connect
are generated for us automatically by getaddrinfo
, which allows our code to be clean and portable.
A server creates a listening descriptor that is ready to receive connection requests by calling the open_listenfd
function.
#include <csapp.h>
int open_listenfd(char *port);
// Returns: descriptor if OK, -1 on error
The open_listenfd
function returns a listening descriptor that is ready to receive connection requests on port port
. Figure 11.19 shows the code for open_listenfd
.
Figure 11.19 open_listenfd
: Helper function that opens and returns a listening descriptor. It is reentrant and protocol-independent.
code/src/csapp.c
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, optval=1; // optval ? 可选值?
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; /* Accept connections */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */
Getaddrinfo(NULL, port, &hints, &listp);
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
// Configure the server so that it can be terminated, be started, and begin
// accepting connection requests immediately.
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval, sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
Close(listenfd); /* Bind failed, try the next */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0) {
Close(listenfd);
return -1;
}
return listenfd;
}
The style is similar to open_clientfd
. We call getaddrinfo
and then walk the resulting list until the calls to socket
and bind
succeed. Note that in line 20 we user the setsockopt
function (not described here) to configure the server so that it can be terminated, be restarted, and begin accepting connection requests immediately. By default, a restarted server will deny connection requests from clients for approximately 30 seconds, which seriously hinders debugging.
hinder v. 阻碍,妨碍
Since we have called getaddrinfo
with the AI_PASSIVE
flag and a NULL
host
argument, the address field in each socket address strcuture is set to the wildcard address, which tells the kernel that this server will accept requests to any of the IP addresses for this host.
Finally, we call the listen
function to convert listenfd
to a listening descriptor and return it to the caller. If the listen
fails, we are careful to avoid a memory leak by closing the descriptor before returning.
11.4.9 Example Echo Client and Server
echo n. 回声 v. 发出回声;重复;应答;回应
echo client 应答客户端? 中译本就是 echo 客户端
Figure 11.20 shows the code for an echo client.
Figure 11.20 Echo client main routine
routine n.例行程序
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) { // if the count of arguments is not 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);
}
After establishing a connection with the server, the client enters a loop that repeatedly reads a text line from standard input, sends the text line to the server, reads the echo line from the server, and prints the result to standard output. The loop terminates when fgets
encounters EOF on standard input, either because the user typed Ctrl + D at the keyboard or because it has exhausted the text lines in a redirected input file.