——首先来看一下 TCP/IP 网络分层模型
数据链路层(网络接口层)
网卡接口的网络驱动程序,处理数据在物理媒介上的传输;不同的物理网络具有不同的电气特性,网络驱动程序隐藏实现细节,为上层协议提供一致的接口。
- 以太网帧格式
IP数据包交付过程中,在数据链路层会对数据包进行添加报头信息。其实更准确的来说,以太网协议是一个规定数据链路层以及物理层的协议,不能仅仅说是一个数据链路层的协议。
以太网帧就是将网络层交付的数据添加报头信息之后,此时的数据以帧的形式传递 ,下面是以太网帧格式:
目的地址&源地址都指的是MAC地址,第三个有一个2字节的类型标识,有三种值: ①IP、②ARP、③RARP。
- 如果类型码为0800,那么在数据链路层解包完毕后,将数据交付给网络层的IP协议来处理报文。
- 如果类型码为0806,那么在向上层交付的时候就交付给ARP协议,将IP地址转换为MAC地址。
- 如果类型码为0835,就交付给RARP协议(Reverse ARP),将MAC地址转换成IP地址。
在以太网帧格式的最后还有一个CRC校验码,来校验数据是否异常。
- APR数据报格式(地址解析协议)
获取下一跳 路由节点的mac地址 - RARP 反向地址解析协议
通过ARP&RARP来实现IP地址与机器物理地址(MAC地址)之间的互相转换。
!!!ARP & RARP 是处于网络层与数据链路层之间的一种协议
网络层(Internet层)
- IP协议
逐跳转发模式,根据数据包的目的IP地址决定数据如何发送,如果数据包不能直接发送到目的地,IP协议负责寻找下一个合适的下一跳路由器,并将数据包交付给该路由器转发。 IP协议是网络层最重要的协议。无论传输层使用哪种协议都要通过IP协议来确定到达目的计算机的路由。IP协议规定了在生存期TTL(8位,最大为255)内传送数据,超过生存期就会丢弃数据,再由丢弃数据的路由器向发送者返回一个ICMP超时报文。 - ICMP协议
因特网控制报文协议,用于检测网络连接,如果数据在传输过程中出现问题,ICMP协议将产生错误报文。
传输层
为两台主机的应用程序提供端到端的通信。
- TCP 传输控制协议
TCP提供一种面向链接的、可靠的基于流的数据传输服务;使用超时重发、数据确认等方式确保数据被正确地发送到目的地。
TCP 通信过程:- 建立连接 三次握手
① 链接的发起端(客户端)向目标计算机(服务器)发送一个请求建立连接的数据包。
② 服务器接收到请求后,对同步信号作出响应,并发送自己的同步信号给客户端。
③ 客户端对服务器发来的同步信号进行响应,完成之后即->建立连接完成 - 关闭
① 请求主机发送一个关闭连接给另一方。
② 另一方收到关闭连接请求后,发送一个接受请求的确认数据包,并且关闭它的socket链接。
③ 请求主机接收到确认数据包后,发送一个确认数据包,告知另一方其发送的确认已经收到,请求主机关闭它的socket链接。
- 建立连接 三次握手
- UDP 用户数据报协议
UDP向应用程序提供一种无连接的,基于数据报的服务,该协议通常被用于不需要可靠数据传输的网络环境中,UDP不需要建立连接,也不需要维持链接,所以也就不保证数据报按照顺序正确的到达目的地,由应用程序来完成。
应用层
应用程序逻辑的实现。
主要有ping、telnet、DNS、HTTP、FTP、DHCP等协议
-
IP地址
IP地址为4字节,32位; 例如这样一个IP地址:‘123.43.11.09‘,它是以字符串形式显示的但是在传输过程中需要转换为(unsigned int)类型,所以它是有传输数据上限的 。 -
IP地址的分类:
- 首先,IP地址以 点分十进制 表示,地址格式为:
IP地址=网络地址+主机地址 或者 IP地址=主机地址+子网地址+主机地址
- 首先,IP地址以 点分十进制 表示,地址格式为:
-
端口
网络地址是用来识别唯一的每台计算机,但每台计算机上通常都会同时运行更多个应用程序,它们可能要同时访问网络。所以对于同一台计算机上的多个应用程序,TCP和UDP协议采用16位的端口号来识别它们。一台主机上的不同进程可以绑定到不同的端口上,这些进程就可以同时访问网络而互不干扰。
简单来说,端口号port用来表示一台主机上一个唯一的进程。
端口号是一个16位无符号整数(0-2^16),(0~65535), 又分为保留端口(知名端口):有权威机构规定用途,其余的为自由端口,用户可以自由申请和使用。 -
C/S 模型
客户机/服务器 模型,既可以使用TCP协议也可以使用UDP协议,在C/S模型中,通常情况下,服务器的端口号和IP地址是固定的,客户端程序连接到服务器IP和端口,所以一般情况下客户端的程序设计相对要简单一些,服务器要考虑多个客户端同时请求服务的问题。 -
B/S 模型
浏览器/服务器 模型,用户通过www浏览器实现,三层架构:
① 客户端表示层:Web浏览器组成,不存放任何应用程序。
② 应用服务器层(事物逻辑层):由一台或者多台服务器组成,具有良好的可扩展性。
③ 数据中心(数据处理层):由数据库系统组成,用于存放业务数据。
总的来说,B/S 是一种特殊的具体化的 C/S模型,B/S模型的客户端软件特指浏览器,服务器一般是Web服务器,使用HTTP协议通信。其工作过程是C/S模型的具体化、实例化。
$ socket 套接字
套接字函数:
- >>>>>>>① socket()函数: 创建套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
名空间: 制定套接字的地址格式
- PF_LOCAL 本地名空间:套接字地址为普通文件名
- PF_INET Internet名空间:套接字地址由Internet地址和端口号(用于区分一台主机上的多个套接字)确定
通信类型: 控制套接字如何传输和处理数据包
- SOCK_STREAM 连接(connection)类型:确保所有包依序传输,如果丢包,则请求重传
- SPCK_DGRAM 数据报(datagram)类型:不保证包的到达顺序,包可能丢失
协议: 确定数据如何传输
- 机器会自动选择最佳的协议来进行传输,因此不需要我们自行修改定义,通常传递0 ,让系统自动选择
返回值:
- 一个新的套接字文件描述符
- >>>>>>>② close()函数: 释放套接字
int close (int fd);
- >>>>>>>③ connect()函数: 创建两个套接字之间的连接
=> 客户端发起该系统调用,试图与服务器建立套接字连接
int connect( int sockfd,const struct sockaddr* addr,socklen_t addrlen);
参数:
- 套接字文件描述符
- 指向套接字地址结构体的指针(服务器地址)
- 服务器地址字符串的长度
返回值:
- 0 表示连接成功、-1 表示连接失败
- >>>>>>>④ bind() 函数
=> 用来将一个套接字与某个端口以及IP地址绑定
int bind(int sockfd, struct sockaddr *my_addr,socklen_t addrlen);
参数:
- 可以将my_addr 的 sin_addr 设置为 INADDR_ANY 而不是某个确定的IP地址就可以绑定到任何网络接口。
- 对于只有一个IP地址的计算机,INADDR_ANY 对应的就是它的IP地址;
- 对于多宿主主机(拥有多块网卡),INADDR_ANY 表示本服务器程序将处理来自所有网络接口上相应端口的连接请求
返回值:
- 函数执行成功返回0,有错误发生时返回-1,错误代码存入errno
- >>>>>>>⑤ listen() 函数
-> 指定同时可以对服务器发起连接请求的最大客户端数量 ,通常默认最大为128(即同时处于三次握手状态的客户端最大数量)
int listen(int s ,int backlog);
= 如果超过最大数量,之后的连接请求将被服务器拒绝
返回值:
- 执行成功返回0,出错返回-1,错误代码去errno
- >>>>>>>⑥ accept() 函数
=> 接受一个连接请求
int accept(int s, struct sockaddr *addr,socklen_t *addrlen);
参数:
- s :由socket创建,经函数bind绑定到某一端口上,再由listen转化来的监听套接字
- addr :传出 参数,从来保存客户端的主机地址&端口
- addrlen :传入传出参数 ,第二个参数所指向的结构体的大小
返回值:
- 返回一个新的套接字文件标识符,进程可以利用这个新的套接字描述符与客户端交换数据
- 参数 s 所指定的套接字继续等待客户端的链接请求
- 客户端&服务端 基本流程
- server.c
- socket(); 建立套接字
- bind(); 绑定IP地址 端口号 (初始化结构体 struct sockaddr_in addr)
- listen(); 指定最大同时发起连接请求的客户端数量 默认值128
- accept(); 阻塞等待客户端发起连接
- read(); 从客户端读取数据
- 处理数据
- write(); 将处理好的数据写回客户端
(… 循环读取->处理->写回…) - close(); 关闭套接字
- client.c
- socket(); 建立套接字
- bind(); 可以依赖“隐式绑定”
- connect(); 发起连接
- write();
- read();
(…loops…) - close();