CSAPP Note chap11

编程入门 行业动态 更新时间:2024-10-07 10:23:48

<a href=https://www.elefans.com/category/jswz/34/1768814.html style=CSAPP Note chap11"/>

CSAPP Note chap11

CSAPP Note chap111

CSAPP 读书笔记系列chap11

chap 11 网络编程

这一次说的是网络编程,是一个比较广泛的话题.之前也有看过王达的<<深入理解计算机网络>> 和宋敬彬的 《Linux网络编程》,但也是了解个皮毛.这一次当作简述吧.

客户端-服务器模型

客户端-服务器模型是网络应用最广泛使用的模型,其中客户端和服务器都是进程而不是机器或主机!!!
客户端-服务器模型的基本操作是事务,一个事务由四步组成

  • 客户端发送一个请求给服务器进程,表示需要某个服务
  • 服务器收到并解释请求,并与适当的方式处理所需资源
  • 服务端发送一个响应给客户端并等待下一个请求
  • 客户端收到响应后处理它

    如下图: cs模型

计算机网络

可以把网络抽象成一个比较慢的IO设备

为了在不同机器中传输传输数据,需要的是大家都遵循一个协议。协议负责做的事情有:

  • 提供命名机制 naming scheme
    • 定义 host address 格式
    • 每个主机和路由器都至少有一个独立的 internet 地址
  • 提供传输机制 delivery mechanism
    • 也即提供一个封装分层机制,上一层可以忽略下一层的不同
    • 不同层之间定义了标准的传输单元
      下面是不同层的传输单元
传输层 TCP/IP传输单元
应用层消息 message
传输层段segment
网络IP层包packet
物理层帧frame

每个传输单元的格式不一样,发送的时候下一层会把上一层给封装起来,加上自己的头部Header.
接收的时候就反过来去掉底层header

计算机网络网络结构分为:
- OSI/RM(Open System Interconnection ReferenceModel, 开放系统互连参考模型)
- TCP/IP 协议体系结构 (又称 TCP/IP 协议参考模型)
如下图

现在都是使用TCP/IP层,计算机网络涉及的东西太多这个详细以后再谈.

TCP/IP是一种面向连接(连接导向)的、可靠的、基于字节流的网络传输协议.

网络编程基本概念

IP地址

IPV4中主机有 32 位的 IP 地址 ,IPv6 为 128 位地址

IP 地址是以 network byte order(也就是大端)来进行存储的

// Internet address structure
struct in_addr {uint32_t s_addr;    // network byte order (big-endian)
}

IP 地址:0x8002C2F2 = 128.2.194.242

具体的转换可以使用 getaddrinfo 和 getnameinfo 函数

为了忽略不同机器的字节顺序,Linux提供了网络和主机顺序的转换函数

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

IP 地址到域名的映射为 Domain Naming System(DNS) ,可以用 nslookup 命令来查看

 -> nslookup www.bing
Server:         127.0.1.1
Address:        127.0.1.1#53Non-authoritative answer:
www.bing     canonical name = fw.ename.
Name:   fw.ename
Address: 23.234.4.151
Name:   fw.ename
Address: 23.234.4.153

域名和地址之间的关系可以是:

  • 一一映射
  • 多个域名映射到同一个IP地址
  • 多个域名映射到同一组IP地址

Internet 连接

客户端和服务器通过连接(connection)来发送字节流,特点是:
- 点对点: 连接一对进程
- 全双工: 数据同时可以在两个方向流动
- 可靠: 字节的发送的顺序和收到的一致

Socket套接字 可以认为是 connection 的 端点endpoint,socket 地址是一个 IPaddress:port 对。
Port(端口)是一个 16 位的整数,用来标识不同的进程,利用不同的端口来连接不同的服务( linux 系统上可以在 /etc/services 中查看具体的信息))

而一个连接是两端的套接字地址唯一确定的,由下面元组表示:
(clientAddr:port, serverAddr:port)

网络编程架构

主要为下面的流程

其中分为服务器和客户端两个part

开启服务器(open_listenfd )
  • getaddrinfo: 设置服务器的相关信息

    • 将主机名,地址等转为套接字地址结构,其为可重入的,适用于任何协议
    • 若成功返回0,否则为nerror代码; res 指向addrinfo结构的链表,

      int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res);
    • 更加具体看书657页 chap11.4.7

  • socket: 创建 socket descriptor,也就是之后用来读写的 file descriptor

    • int socket(int domain, int type, int protocol)
    • 例如 int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    • AF_INET 表示在使用 32 位 IPv4 地址
      -SOCK_STREAM 表示这个 socket 将是 connection 的 endpoint
    • 前面这种写法是协议相关的,建议使用 getaddrinfo 生成的参数来进行配置,这样就是协议无关的了
  • bind: 请求 kernel 把 socket address 和 socket descriptor 绑定

    • int bind(int sockfd, SA *addr, socklen_t addrlen);
    • 最好是用 getaddrinfo 生成的参数作为 addr 和 addrlen,,从result链表中
  • listen: 默认来说,从 socket 函数中得到的 descriptor 默认是 active socket(也就是客户端的连接),调用 listen 函数告诉 kernel 这个 socket 是被服务器使用的

    • int listen(int sockfd, int backlog);
    • 把 sockfd 从 active socket 转换成 listening socket,用来接收客户端的请求
    • backlog 的数值表示 kernel 在接收多少个请求之后(队列缓存起来)开始拒绝请求
  • accept: 调用 accept 函数,开始等待客户端请求

    • int accept(int listenfd, SA *addr, int *addrlen);
    • 等待绑定到 listenfd 的连接接收到请求,然后把客户端的 socket address 写入到 addr,大小写入到 addrlen
    • 返回一个 connected fd 用来进行信息传输(类似 Unix I/O)

注意 listenfd 和 connected fd, 其设定不同状态是为了连接和处理分开做,从而建立并发服务器

  • listenfd 是作为客户端请求连接的一个端点,通常被创建一次,并存在整个server的生命周期
  • connectfd 是已经连接后的一个端点,server每次接受连接都会创建一次,存在于server为一个客户端服务的一次过程
client开启客户端(open_clientfd 函数,设定访问地址,尝试连接)
  • getaddrinfo: 设置客户端的相关信息

  • socket: 创建 socket descriptor,也就是之后用来读写的 file descriptor

  • connect: 客户端调用 connect 来建立和服务器的连接

    • int connect(int clientfd, SA *addr, socklen_t addrlen);
    • 尝试与在 socker address addr 的服务器建立连接
    • 如果成功 clientfd 可以进行读写
    • connection 由 socket 对描述 (x:y, addr.sin_addr:addr.sin_port)
    • x 是客户端地址,y 是客户端临时端口,后面的两个是服务器的地址和端口
    • 最好是用 getaddrinfo 生成的参数作为 addr 和 addrlen
交换数据

主要是一个流程循环,客户端向服务器写入,就是发送请求;服务器向客户端写入,就是发送响应)
- [Client]rio_writen: 写入数据,相当于向服务器发送请求
- [Client]rio_readlineb: 读取数据,相当于从服务器接收响应
- [Server]rio_readlineb: 读取数据,相当于从客户端接收请求
- [Server]rio_writen: 写入数据,相当于向客户端发送响应

关闭客户端

客户端读到EOF,发起 close
- [Client]close: 关闭连接

断开客户端

服务接收到客户端发来的 EOF 消息之后,断开已有的和客户端的连接
- [Server]rio_readlineb: 收到客户端发来的关闭连接请求
- [Server]close: 关闭与客户端的连接
- 然后返回accept,继续监听下一个client

一个echo程序

书上给了一个echo程序的代码,包括客户端和服务端两部分的主函数和辅助函数

echo客户端
echo客户端主函数

主函数为打开一个socket,然后建立一个循环,把一段用户输入的文字发送到服务器,然后再把从服务器接收到的内容显示到输出中

// echoclient.c
#include "csapp.h"
int main (int argc, char **argv) {int clientfd;char *host, *port, buf[MAXLINE];rio_t rio;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);
}
echo客户端open_clientfd

用来建立和服务器的连接,协议无关

int open_clientfd(char *hostname, char *port) {int clientfd;struct addrinfo hints, *listp, *p;// Get a list of potential server addressmemset(&hints, 0, sizeof(struct addrinfo));hints.ai_socktype = SOCK_STREAM; // Open a connectionhints.ai_flags = AI_NUMERICSERV; // using numeric port argumentshints.ai_flags |= AI_ADDRCONFIG; // Recommended for connectionsgetaddrinfo(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// 这里使用从 getaddrinfo 中得到的参数,实现协议无关if ((clientfd = socket(p->ai_family, p->ai_socktype,p->ai_protocol)) < 0)continue; // Socket failed, try the next// Connect to the server// 这里使用从 getaddrinfo 中得到的参数,实现协议无关if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)break; // Successclose(clientfd); // Connect failed, try another}// Clean upfreeaddrinfo(listp);if (!p) // All connections failedreturn -1;else // The last connect succeededreturn clientfd;
}
echo 服务端
echo 服务端主函数和echo处理函数

也就是打开和绑定,listen一个socket,然后使用一个循环处理 1.client建立链接,2.调用处理函数echo

// 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 room for any addrchar client_hostname[MAXLINE], client_port[MAXLINE];// 开启监听端口,注意只开这么一次listenfd = Open_listenfd(argv[1]);while (1) {// 需要具体的大小clientlen = sizeof(struct sockaddr_storage); // Important!// 等待连接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(coonfd);Close(connfd);}exit(0);
}
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);// 把从 client 接收到的信息再写回去Rio_writen(connfd, buf, n);}
}
echo 服务端open_listenfd

创建 listening descriptor,用来接收来自客户端的请求,协议无关

int open_listenfd(char *port){struct addrinfo hints, *listp, *p;int listenfd, optval=1;// Get a list of potential server addressesmemset(&hints, 0, sizeof(struct addrinfo));hints.ai_socktype = SOCK_STREAM; // Accept connectionhints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // on any IP addresshints.ai_flags |= AI_NUMERICSERV; // using port number// 因为服务器不需要连接,所以原来填写地址的地方直接是 NULLgetaddrinfo(NULL, 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// 这里使用从 getaddrinfo 中得到的参数,实现协议无关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 bindsetsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR),(const void *)&optval, sizeof(int));// Bind the descriptor to the addressif (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)break; // Successclose(listenfd); // Bind failed, try another}// Clean upfreeaddrinfo(listp);if (!p) // No address workedreturn -1;// Make it a listening socket ready to accept connection requestsif (listen(listenfd, LISTENQ) < 0) {close(listenfd);return -1;}return listenfd;
}

web服务器

这里实现一个HTTP服务器,关于HTTP协议的一些简单介绍可以看一些之前的笔记HTTP协议介绍

这是一个实现CGI等基本功能的Tiny Server,使用telnet测试。但模型是一个循环服务器,没有并发,所以等chap12看完再做proxy lab

更多推荐

CSAPP Note chap11

本文发布于:2024-02-28 08:19:09,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769147.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:CSAPP   Note

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!