"勇敢飞!拥吻雨水。"
这里的网络,实质上指的是,网络协议栈。它是一个软件,贯穿整个OS。
协议栈: 本质就是一套标准,任何协议栈都必须支持(TCP/IP协议)
所谓协议,就是约定俗成的标准。通过制定协议,从而来降低交流的成本。
协议栈我们分为四层结构,应用层\传输层\网络层\数据链路层...(物理层)。
物理层:早期以太网采用的的同轴电缆,光纤, 现在的wifi无线网.决定最大传输速率、传输距离、抗干扰性。
数据链路层:负责设备之间的数据帧的传送和识别.有以太网、令牌环网, 无线LAN等标准。
网络层: 负责地址管理和路由选择通过路由表的方式规划出两台主机之间的数据传输的线路。
传输层: 负责两台主机之间的数据传输。
应用层: 负责应用程序间沟通。
为什么使用分层结构呢?我们再理解每层所做的工作。
协议栈层状结构的本质:
保证层与层之间,互不干扰,各自都是独立的,执行独立的 任务。实现解耦
封包:
解包:
它怎么知道包头有多长?它怎么知道该拿什么位置的数据交付上层?
因此,封包的过程进行了约定。
协议共性:
1.结构体内一定包含了 包头多大的字段。2.交给上层的有效载荷有明显的标识。(当然这些不用我们去做了)
我们在看解包的过程,也能很直观地感受到,上层协议是不关心下层协议做了什么,仅仅关心的是有效载荷。
不同包头,可能都是下层协议的有效载荷。
上面只讲了两个协议栈传输的基本流程,那么网络传输的过程又是什么呢?
在早期的时候,计算机网络还没有出来,那么主机与主机间的数据交互,效率低。
因此出现了局域网。
随着用户主机的不断增多,形成了更加庞大的网络系统
因此,广域网就是 多个局域网的组成。
特性:
1.只允许一台主机和另外一台进行通信
2.遇到碰撞会 及时收到信息。
3.每个主机都会执行避免碰撞的算法。降低事件概率。
认识Mac
在数据链路层,识别网络来信。
Mac地址,就是网卡(内置全球唯一的 16进制的序列号)
所以,MAC地址通常都是唯一的,在网卡出厂时,就确定好了。
认识IP:
IP协议通常有两个版本:IPV4 IPV6
1. 对于IPV4来说,IP地址是一个4字节,32比特位
2.IP是标识一个公网内,唯一的主机
可能会有疑问,pid也是确定 OS内进程的唯一标识,为什么又要引进port(端口号)?这样做不冲突吗?
答案是不冲突!
其实这个道理和在社会上你拥有身份证,但在学校你又拥有学号。进行这样的处理的仅仅是为了方便管理!
本篇仅仅对TCP和UDP的协议进行初步的认识,不做多的深究。因为本篇的重在实现套接字编程!
TCP的四个特点:
传输层协议
有连接
可靠传输
面向字节流UDP的四个特点:
传输层协议
无连接
不可靠传输
面向数据报
对TCP和UDP的初步认识,可以从可靠和不可靠的传输开始。所以 如果需要稳定的传输,就得使用TCP协议,反之,这样的协议一定更复杂,如果对数据传输的可靠性要求不高,那么可以选择,实现起来更简单的UDP协议。
我们生活中,主机大端机,小端机。他们存储数据的字节序是不一样的。如果没有一套标准对这样的区别进行规范,那么会造成数据传输的不一致性。
1.发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
2.接收主机把从网络上接到的字节,按内存地址从低到高的顺序保存。
3.TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
因此,对于这种情况的解决方法是,不管是大端机还是小端机,统一发送大端字节序给网络,然后进行接收。
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
当然系统库也为我们提供了相应的函数:
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
注: n表示network h表示主机 l表示长整型 s表示短整型
例如:
htonl 表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
IPV4和IPV6的地址格式定义在
中,在这样的结构体中, 包括了: 16位地址类型,16位port 32位IP
协议家族(sin_familty):AF_INET(IPV4)、 AF_INET6(IPV6)
端口号(sin_port):16位
IP(sin_addr.s_addr):IP32位地址
grep -ER 'struct sockaddr{' /usr/include
server:服务端
client:客户端
int socket(int domain, int type, int protocol);
socket套接字创建成功会返回一个 文件描述符?!这是什么?
不免脑海中浮现,linux下一切皆文件!所以socket也是会被分配文件描述符!
server:
client:
字符串转in_addr的函数:
in_addr转字符串的函数:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind的功能就在于让进程挂钩 网络。
client不用进行bind 但需要和远端对接:
#include
#includessize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
#include
#includessize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr*dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
recvfrom(读数据):
sendto(发送数据):
udpserver\udpclient.cc
- netstat -nlup udp检测
- netstat -nltp tcp检测
有上面的基础,我们还可以做做其他业务:
server:
client:
小结:
服务端: socket + local初始化 +bind(local)
客户端: socket~
数据传输: sendto recvfrom(其实这两个函数 就近似于 write 和 read后面tcp的时候会讲解)
Tcp套接字的大部分实现和UDP是一样的,但是也有部分细节上的差异。
#include
#includeint listen(int sockfd, int backlog);
#include
#includeint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#include
#includeint connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
RETURN VALUE
If the connection or binding succeeds, zero is returned. On error, -1
is returned, and errno is set appropriately.
- //监控脚本
- while :; do netstat -nltp ;echo "#############################"; sleep 1;done
很显然,这很不符合情景。一个客户端访问服务器,只能等其他先得到服务器处理的客户端退出才能被accept! 其原因就在于,我们使用的是 单执行流!
监控脚本:
- while :; do ps ajx | head -1 && ps ajx | grep tcp | grep -v 'grep';
- echo "###################################" ; sleep 1 ;done
成为孤儿进程 被系统领养!
因为反复创建线程必然会对性能造成影响!因此为何不在启动服务器之前就 创建好一批线程,等待拿到 客户端!
我们可以更新业务,做一个查字典的 socket
①认识协议栈,协议栈贯穿整个OS
②协议栈分四层,应用层 传输层 网络层 数据链路层 以及它们的功能
③Mac地址唯一内置网卡 和 公网ip主机的唯一标识
④进程pid vs 端口号port
⑤UDP协议和TCP协议 sockaddr三个结构的体
⑥UDP:server: socket + bind TCP:server:socket + bind +listen + accept
client:socket + 初始化peer client: socket + 初始化peer +connect