粉丝不过W
TCP/IP 协议概述
OSI 参考模型及 TCP/IP 参考模型
基于国际标准化组织(ISO), 共七层:应用层、表示层、会话层、传输层、网络层、数据链路层,物理层
将 TCP/IP 的 7层协议模型 简化为 4 层,便更有利于实现和使用
OSI 模型和 TCP/IP 参考模型对应关系:
网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收
note: 数据帧是独立的网络信息传输单元
网络层:负责将数据帧封装成 IP 数据报,并运行必要的路由算法
传输层:负责端对端之间的通信会话连接与建立。传输协议的选择根据数据传输方式而定
应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程
TCP/IP 协议族
ARP:用于获得同一物理网络中的硬件主机地址
MPLS:多协议标签协议
IP:负责在主机和网络之间寻址和路由数据包
ICMP:用于发送报告有关数据包的传送错误的协议
IGMP:被 IP 主机用来向本地多路广播路由器报告主机组成员的协议
TCP:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。也适用于要求得到响应的应用程序
UDP:提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输少量数据可靠性则由应用层来负责
TCP 和 UDP
传输层 TCP 和 UDP 协议
TCP
概述
TCP 向相邻的高层提供服务
由于 TCP 的上一层是应用层,所以 数据传输实现了从一个应用程序到另一个应用程序的数据传递
打开 socket 来使用 TCP 服务,TCP 管理到其他 socket 的数据传递
三次握手协议
TCP 对话通过三次握手来初始化。目的:使数据段的发送和接收同步,告诉其他主机要一次可接收的数据量,并建立虚连接
三次握手的简单过程:
初始化主机,用一个同步标志置位的数据段发出会话请求
接收主机通过发回下列数据段表示回复:同步标志置位、即将发送的数据段的起始字节的顺序号、应答并带有将收到的下一个数据段的字节顺序号
请求主机再回送一个数据段,并带有确认顺序号和确认号
TCP 三次握手协议流程:
TCP 实体采用的基本协议:滑动窗口协议
当发送方传送一个数据报时,它将启动计时器,当该数据报到达目的地后,接收方的 TCP 实体向回发送一个数据报,其中包含有一个确认序号,意思:要收到的下一个数据报的顺序号。如 发送方的定时器在确认信息到达之前超时,那发送方会重发该数据报
TCP 数据报头
TCP 数据报头的格式:
源端口、目的端口:16 位长。标识 远端和本地的端口号
序号:32 位长。标识 发送的数据报的顺序
确认号:32 位长。希望收到的下一个数据报的序列号
TCP 头长:4 位长。表明 TCP 头中包含多少个 32 位字
6 位未用
ACK:ACK : 1 表明 确认号为 合法。如 ACK 为 0,那 数据报不包含确认信息,确认字段被省略
PSH:表示 带有 PUSH 标志的数据。接收方请求数据报 一到 便可送往应用程序,不用等到缓冲区装满时才传送
RST:用于复位由于主机崩溃或其他原因而出现的错误的连接。还可以用于 拒绝非法的数据报或 拒绝连接请求
SYN:用于建立连接
FIN:用于释放连接
窗口大小:16 位长。窗口大小字段:在确认了字节之后还可以发送多少个字节
校验和:16 位长。是为了确保高可靠性。校验头部、数据和伪 TCP 头部之和
可选项:0 个或多个 32 位字。包括最大 TCP 载荷,窗口比例、选择重发数据报等选项
UDP
概述
UDP :用户数据报协议,一种无连接协议,因此不需要像 TCP 那样通过三次握手来建立一个连接
UDP 数据包头
源地址、目的地址:16 位长。标识出远端和本地的端口号
数据报的长度:包括报头和数据部分在内的总的字节数,报头的长度是固定,该域:计算可变长度的数据部分(别名:数据负载)
协议的选择
对数据可靠性的要求
对数据要求高可靠性的应用: TCP 协议,如验证、密码字段的传送都是不允许出错
对数据的可靠性要求不那么高的应用: UDP 传送
应用的实时性
由于 TCP 协议在传送过程中要进行三次握手、重传确认等手段来保证数据传输的可靠性,所以TCP 协议会有较大的时延,因此不适合对实时性要求较高的应用,如 VOIP、视频监控。
UDP 协议:在这些应用中能发挥很好的作用
网络的可靠性
TCP 协议的提出:解决网络的可靠性问题,它通过各种机制来减少错误发生的概率
在网络状况不是很好的情况下: TCP 协议,如 广域网
在网络状况很好的情况下: UDP 协议,减少网络负荷,如 局域网
网络基础编程
socket 概述
socket 定义
Linux 网络编程通过 socket 接口来进行
socket 接口:一种特殊的 I/O,一种文件描述符
每一个 socket 都用一个半相关描述{ 协议,本地地址、本地端口 }来表示
该函数返回一个整型的 socket 描述符,随后的连接建立、数据传输等操作都是通过 socket 来实现
一个完整的套接字用一个相关描述{ 协议,本地地址、本地端口、远程地址、远程端口 }
socket 类型
流式 socket(SOCK_STREAM)
流式套接字提供可靠的、面向连接的通信流;使用 TCP 协议,从而保证了数据传输的正确性和顺序性
数据报 socket(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,且不保证是可靠、无差错
原始 socket
原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用不便,主要用于一些协议的开发
地址及顺序处理
数据结构介绍
/* 等效,可相互转化 */
struct sockaddr
{
unsigned short sa_family; /* 地址族 */
char sa_data[14]; /* 14 字节的协议地址,含该 socket 的 IP 地址、端口号 */
};
struct sockaddr_in
{
short int sa_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP 地址 */
unsigned char sin_zero[8]; /*填充 0 以保持与 struct sockaddr 同样大小*/
};
结构字段
#include <netinet/in.h>
/*
*AF_INET: IPv4 协议
*AF_INET6:IPv6 协议
*AF_LOCAL:UNIX 域协议
*AF_LINK: 链路地址协议
*AF_KEY: 密钥套接字(socket)
*/
unsigned short sa_family;
数据存储优先顺序
函数说明
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先
Internet 上数据以高位字节优先顺序在网络上传输
两个字节存储优先顺序进行相互转化时,需用到:htons、ntohs、htonl、ntohl
h 代表 host,n 代表 network,s 代表 short,l 代表 long
通常 16 位的 IP 端口号用 s 代表,而 IP 地址用 l 来代表
#include <netinet/in.h>
/*
*parameter:
* host16bit:主机字节序的 16bit 数据
* host32bit:主机字节序的 32bit 数据
* net16bit: 网络字节序的 16bit 数据
* net32bit: 网络字节序的 32bit 数据
*return:
* 成功:转换的字节序
* 出错:-1
*/
uint16_t htons(unit16_t host16bit)
uint32_t htonl(unit32_t host32bit)
uint16_t ntohs(unit16_t net16bit)
uint32_t ntohs(unit32_t net32bit)
地址格式转化
函数说明
用户在表达地址时采用的是<stron>表示的数值(或 以冒号分开的十进制IPv6 地址),而 使用的 socket 编程中所使用的是二进制值</stron>
#include <arpa/inet.h>
/*
*function:
* 点分十进制地址映射为二进制地址
*parameter:
* family:
* AF_INET: IPv4 协议
* AF_INET6:IPv6 协议
* strptr: 转化的值
* addrptr: 转化后的地址
*return:
* 成功:0
* 失败:-1
*/
int inet_pton(int family, const char *strptr, void *addrptr)
/*
*function:
* 二进制地址映射为点分十进制地址
*parameter:
* family:
* AF_INET: IPv4 协议
* AF_INET6:IPv6 协议
* addrptr:转化后的地址
* strptr: 要转化的值
* Len: 转化后值的大小
*return:
* 成功:0
* 失败:-1
*/
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)
名字地址转化
函数说明
实现 IPv4 和 IPv6 的地址和主机名之间的转化
struct hostent
{
char *h_name; /* 正式主机名 */
char **h_aliases; /* 主机别名 */
int h_addrtype; /* 地址类型 */
int h_length; /* 地址长度 */
char **h_addr_list; /* 指向 IPv4 或 IPv6 的地址指针数组 */
};
#include <netdb.h>
/*
*ai_flags:
* AI_PASSIVE: 该套接口是用作被动地打开
* AI_CANONNAME:通知 getaddrinfo 函数返回主机的名字
*family:
* AF_INET: IPv4 协议
* AF_INET6:IPv6 协议
* AF_UNSPE:IPv4 或 IPv6 均可
*ai_socktype:
* SOCK_STREAM:字节流套接字 socket(TCP)
* SOCK_DGRAM: 数据报套接字 socket(UDP)
*ai_protocol:
* IPPROTO_IP: IP 协议
* IPPROTO_IPV4:IPv4 协议
* IPPROTO_IPV6:IPv6 协议
* IPPROTO_UDP: UDP
* IPPROTO_TCP: TCP
*note:
* 服务器端, ai_flags: AI_PASSIVE,主机名 nodename: NULL
* 客户端,,ai_flags != AI_PASSIVE,且 主机名 nodename 和服务名 servname(端口)!= 空
*/
struct addrinfo
{
int ai_flags; /* AI_PASSIVE,AI_CANONNAME */
int ai_family; /* 地址族 */
int ai_socktype; /* socket 类型 */
int ai_protocol; /* 协议类型 */
size_t ai_addrlen; /* 地址长度 */
char *ai_canoname; /* 主机名 */
struct sockaddr *ai_addr; /* socket 结构体 */
struct addrinfo *ai_next; /* 下一个指针链表 */
};
函数格式
#include <netdb.h>
/*
*function:
* 主机名转化为 IP 地址
*parameter:
* hostname:主机名
*return:
* 成功:hostent 类型指针
* 失败:-1
*/
struct hostent *gethostbyname(const char *hostname)
#include <netdb.h>
/*
*function:
* 自动识别 IPv4 地址和 IPv6 地址
*parameter:
* hostname:主机名
* service: 服务名或十进制的串口号字符串
* hints: 服务线索函数传入值
* result: 返回结果
*return:
* 成功:0
* 出错:-1
*/
int getaddrinfo(const char *hostname,
const char *service,
const struct addrinfo *hints,
struct addrinfo **result)
例:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
struct addrinfo hints, *res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
/*设置 addrinfo 结构体中各参数*/
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
/*调用 getaddinfo 函数*/
rc = getaddrinfo("127.0.0.1", "123", &hints, &res);
if(rc != 0)
{
perror("getaddrinfo");
exit(1);
}
else
{
printf("getaddrinfo success\n");
}
}