Fork me on GitHub

CSAPP 11.4 Socket Interface

CSAPP 11.4 socket interface

以下 socket 全用英文,套接字这个翻译太辣鸡了,不知所云

socket interface 是一组函数,和 Unix I/O 函数结合起来,创建网络应用。

实际上就是 IP: port

下图给出了一个典型的 C/S 事务的上下文中的 socket 接口概述

Figure 11-12 基于 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*/
};
code 11-13 socket 地址结构
  • 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 and AF_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 also AF_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 连接,其中 addrlensizeof(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)。对于 socketconnect ,最好的方法使用 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 的角色。

  1. server 调用 accept,等待 连接请求 connect request 到达 listenfd,具体地设定为 描述符3。描述符 0~2 是预留给标准文件的。
  2. 客户端调用 connect 函数,发送一个 connect request 到 listenfd
  3. accept 函数 打开了一个新的 已连接描述符 connfd(假设是描述符 4),在 clientfd 和 connfd 之间建立连接,并且随后返回 connfd 给应用程序。客户端也从 connect 返回

​ 在这一点以后,客户端和服务器就可以分别通过读和写 clientfd 和 connfd 来回传送数据了。

  • 为什么有 监听描述符 listenfd 和 已连接描述符 connfd 的区别?
    • 帮助建立并发服务器,同时处理许多客户端连接
    • 如:每次一个 连接请求 connect request 到达 listenfd 时,可以派生 fork 一个新的进程,它通过已连接描述符 connfd 与客户端连接。在第 12 章中将介绍更多关于并发服务器的内容。

11.4.7 主机和服务的转换

Linux 提供 getaddrinfogetnameinfo 函数来实现二进制 socket 地址结构和主机名、主机地址、服务名和端口号的字符串表示之间的相互转化。

11.4.7.1 getaddrinfo 函数

getaddrinfo 函数将主机名、主机地址、服务名和端口号的字符串表示转化为 socket 地址结构。它是 deprecated/obsolete gethostbynamegetservbyname 函数的替代品。和这两个函数不同,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语言 **

指向指针的指针

Figure 11.15 getaddrinfo 返回的数据结构

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_familyai_socktypeai_protocolai_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 同理。
    • 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 地址的请求。这是所有示例服务器期望的行为。

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_familyai_socktypeai_protocol 可以被直接传给 socket。类似地,ai_addrai_addrlen 可以被直接传递给 connectbind。这一特性让我们编写的客户端和服务器可以独立于某个特殊版本的 IP 协议。

opaque a. 不透明的

11.4.7.2 getnameinfo 函数

getnameinfo 函数和 getaddrinfo 函数是相反的,它将一个 socket 地址结构转换成相应的 host 名和 service 名字符串。getnameinfo 是 obsolete gethostbyaddrgetservbyport 函数的替代,他是可重入和 协议独立的(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 ,使用 getaddrinfogetnameinfo 来展示 一个域名和 它相关联的 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.

posted @ 2022-07-06 17:43  icewalnut  阅读(129)  评论(0编辑  收藏  举报