1.模型
1.1网络编程(套接字编程)
TCP/IP协议
特点:
- 不局限于某一台主机中的各个进程,只要网络可以到达,就可以实现跨平台的进程通信。
协议:
- 想要实现在两个或多个平台中稳定通信,就需要遵守(达成)同一个协议。
- ARPA协议:
- 1958年美国总统提出。
- 但是该协议不具备纠错功能。
- 不可以用来链接不同类型的计算机及不同的计算机系统,具有很强的局限性。
- 1973 年由Kahn和Vinton Cerf两人合作开发了新的ARPAnet协议
- 1974 年两人便发表了第一份TCP的详细说明,但此协议在有数据包丢失时不能有效的纠正。
- TCP 协议:
- TCP协议包含两个方面
- tcp传输协议:用来检测网络传输中差错的传输控制协议
- ip 网际协议:专门负责对不同网络进行互联的互联网协议IP
- TCP协议包含两个方面
(通俗的来说TCP是专门负责传输过程中的出错问题,一旦出现传输的问题TCP协议则会介入处理,并把出错的数据包重新传输直到数据安全到达为止。而IP协议则是在网络中负责不同的网络之间的通信,给网络中的每一台主机分配一个不同的IP地址)
网络体系结构
- 网络体系结构指的是网络的层次结构和每一层所使用的协议的集合
- 实现网络传输的功能非常繁杂,所以采用了分而治之的设计方法,把网络的功能划分为不同的独立模块,以分层的形式有机地组合在一起。
- 每一层地实现功能和任务是不同的,其内部实现方法对外部其他层来说是透明的。每一层都为上一层提供服务,同时使用下一层所提供的服务。
OSI模型与TCP/IP模型
- OSI 开放系统互连模型(参考模型)
- OSI模型相关的协议已经非常少使用,但是模型本身非常通用
- OSI模式是一个理想化的模型,至今尚未由完整的实现
- OSI模型共分为7层
接收者 | |||
有客户在我网店中购买了一件商品,要从北京发往吉山 | 应用层 | 应用层 | 获得我从北京购买的产品 |
先把商品装到盒子中方便运输 | 表示层 | 表示层 | 把包裹着商品的包裹拆掉 |
选择一个还没倒闭的快递公司 | 会话层 | 会话层 | 获得一个属于自己的包裹 |
快递员来收件并贴上了一个运单 | 传输层 | 传输层 | 根据运单确定这个包裹是给谁的 |
包裹来到了转运中心,转运中心又用一个大箱子把我的包裹装在了发往广州的集装箱中 | 网络层 | 网络层 | 拆开从北京来的集装箱 |
快递公司计划一个合适可行的路线,以及定制了一些列的运送规则 | 数据链路层 | 数据链路层 | 快递公司开始检查货物是否有损伤,如果有怎么处置(请求再来一次) |
司机选择正确的路线开始运送快递 | 物理层 | 物理层 | 运送的司机到达目的地公司(广州) |
注意:
OSI参考模型并没有提供一个可以实现的方法,而是描述了一些概念,用来协调进程间通信标准的制定。即OSI参考模型并不是一个标准,而是一个在制定标准时所使用的概念性框架。
自下而上了解TCP/IP协议:
- 网络接口和物理层
- 物理层的主要设备就是中继器和集线器(交换机/路由)
- 封装成帧(在数据的前后分别加上开始和结束的标记后便构成了一帧)
- 每一个数据链路层都规定了所能传输的一针数据中数据的长度上线称为最大传输单元(MTU)
- 透明传输(指的是不管数据的二进制是如何组合都应该能够正常传输而不会被误以为是某些控制的命令,确保数据安全到达)
- 差错检测(在传输的二进制编码中加入适当的冗余度使得接收者可以发现在传输中是否出现了差错)
- TCP /IP 协议的数据包组成:
- 数据的封装与传递过程
1.2.网络编程储备知识:
1.2.1Socket --- 插座
- 是一个编程接口
- 是一个特殊的文件描述符
- 不仅仅局限于TCP/IP协议
- 面向连接 TCP
- 无连接 UDP
- 很多的应用 都依赖于socket的接口例如FTP\TELNET等
1.2.2为什么需要socket
普通IO操作的过程:
打开文件 >> 读/写操作 >> 关闭文件
Socket文件的操作:
在同一个机器上的两个程序可以简单的通过某一个相同的文件进行数据的交流和传输,但是如果在网络中两个进程完全不存在统一个机器那需要如何打开?
网络协议具有多样性,如何进行统一的操作?
什么是Socket
- 独立于具体协议的网络编程接口
- 在OSI模型中主要位于会话层和传输层之间
- Socket的类型
- 流式套接字 (SOCK_STREAM) TCP 语音聊天
- 提供一个面向连接、可靠的数据传输服务,数据无差错、无重复、地发送并按顺序抵达。内设流量控制,避免数据流淹没前面相对慢地数据。数据被查看时字节流,无长度限制。
- 数据报套接字(SOCK_DGRAM)UDP 发送消息
- 提供无连接服务,数据包以独立数据包形式被发送,不提供无差错保证,数据有可能丢失或重复到达,顺序发送可能会乱序接收
- 原始套接字(SOCK_RAW)
- 可以直接访问较低层次的协议例如 IP\ICMP。
- 流式套接字 (SOCK_STREAM) TCP 语音聊天
Socket的位置
1.2.3IP地址
- IP地址是网络中主机的标识
- 在网络中主机想要与其他机器通信就必须拥有一个自己的IP地址
- IP地址为32位(IPV4)或者128位(IPV6)
- 每一个数据包都必须携带目的地址IP和源IP地址,路由器依靠此信息为数据包选择最优路由
表示形式
点分形式:如 192.168.100.2 在传输过程中都会被转换为一个32位无符号的整数
1.2.4端口号
- 用于区分一台主机中接收到的数据包应该转交给哪一个进程进行处理。
- TCP端口号与UDP端口号是相互独立的
- 端口号一般由IANA(Internet Assigned Numbers Authority) 统一管理
- 众所周知端口: 1 - 1023 (1-25之间为众所周知端口 , 256 - 1023 为UNIX系统占用)
- 何为总所周知端口其实就是早已固定号的端口比如80端口分配给WWW服务,21端口分配给FTP服务等
- 注册端口: 1024 -49151 分配给进程或者应用。这些端口号在还没有被服务器资源占用时,可以由用户的APP动态注册获得。
- 动态端口号:49152 - 65535 被称为动态端口号他一般不固定分配某种服务而是动态分配的。
1.2.5字节序
不同的CPU 主机中,内存存储多个字节的序列分为两种,这个称为主机字节序
- 小端序(Little-Endian)
- 低序(低有效位)存储在低地址(起始地址),Intel\AMD 等采用的方式
- 大端序(Big-Endian)
- 高序(高有效位)存储在高地址 ARM采用的存储方式
为了避免在不同的处理器中收到的数据出现字节序带来的问题,因此网络中传输的数据必须按照网络字节序来处理,也就是大端序。发送者在发送数据前必须先转换为网络字节序,而接收者需要在收到网络中的数据时再转换为自己合适的主机字节序。
例如:0x12345678
在小端序中存放时:
12 34 56 78
高地址 --------- 低地址
在大端序中存放则相反
78 56 34 12
高地址 --------- 低地址
1.3字节序转换API :
头文件:
#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); // 网络字节序转为主机字节序 无符号短整型
1.4IP地址转换
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数原型:
// 把cp指向的字符串转换为32位的网络字节序的二进制值存于inp中
int inet_aton(const char *cp, struct in_addr *inp);
inet_aton("192.168.100.2", &inp);
// 把cp指向的字符串转换为32位的网络字节序的二进制值并返回
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
// 把in中的32位网络字节序的二进制地址转换为点分十进制的字符串
char *inet_ntoa(struct in_addr in);
2.tcp接收流程:
1建立套接字
接口声明:int socket (int domain, int type, int protocol);
参数:
domain:域。
AF_INET/PF_INET: 网际协议
AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL
type:类型。
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议。
一般为 0
返回值:
成功:待连接套接字
失败:-1
备注:在网际协议中,选择流式套接字就代表 TCP 协议,选择数据包套接字就代表 UDP 协议,
第三个参数 protocol 一般都不用
2.配置地址信息,绑定IP
接口声明:int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.设置监听
接口声明:int listen (int sockfd, int backlog);
4.等待对端连接请求
接口声明:int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5.读取数据
从 TCP 套接字接收数据
接口声明: ssize_t recv (int sockfd, void *buf, size_t len, int flags);
3.tcp发送流程:
1.建立套接字
接口声明:int socket (int domain, int type, int protocol);
参数:
domain:域。
AF_INET/PF_INET: 网际协议
AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL
type:类型。
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议。
一般为 0
返回值:
成功:待连接套接字
失败:-1
备注:在网际协议中,选择流式套接字就代表 TCP 协议,选择数据包套接字就代表 UDP 协议,
第三个参数 protocol 一般都不用
2.设置连接
连接对端监听套接字
接口声明:int connect (int sockfd, const struct sockaddr *addr, socklen_taddrlen);
3.发送数据
向 TCP 套接字发送数据
接口声明:ssize_t send (int sockfd, const void *buf, size_t len, int flags);
4.udp接收流程:
1.建立套接字
接口声明:int socket (int domain, int type, int protocol);
2.配置地址信息,绑定IP
// struct sockaddr_in
// {
// u_short sin_family; // 地址族
// u_short sin_port; // 端口
// struct in_addr sin_addr; // IPV4 地址
// char sin_zero[8];
// };
接口声明:int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3.读取数据
struct sockaddr_in from_addr;
char * msg = calloc(1,1024);
recvfrom( sockfd, msg , 1024 , 0 ,(struct sockaddr *)&from_addr, &addrlen);
5.udp发送流程:
1.建立套接字
接口声明:int socket (int domain, int type, int protocol);
2.配置地址信息
// struct sockaddr_in
// {
// u_short sin_family; // 地址族
// u_short sin_port; // 端口
// struct in_addr sin_addr; // IPV4 地址
// char sin_zero[8];
// };
3.发送数据
sendto( sockfd, msg , 1024 , 0 ,(struct sockaddr *)&server_addr, addrlen);