目录
网络发展史
ARPnet--Internet--移动互联网--物联网--AI
局域网和广域网
局域网(LAN)
局域网的缩写是LAN,local area network,顾名思义,是个本地的网络,只能实现小范围短距离的网络通信。我们的家庭网络是典型的局域网。电脑、手机、电视、智能音箱、智能插座都连在路由器上,可以互相通信。局域网,就像是小区里的道路,分支多,连接了很多栋楼。
广域网 (Wan)
广域网(Wide Area Network)是相对局域网来讲的,局域网的传输距离比较近,只能是一个小范围的。如果需要长距离的传输,比如某大型企业,总部在北京,分公司在长沙,局域网是无法架设的。广域网,就像是大马路,分支可能少,但类型多,像国道、省道、高速、小道等,连接了很多大的局域网。
这时需要其它的解决方案。
第一,通过因特网,只需要办一根宽带,就实现了通信,非常方便,现在的宽带价格也比较便宜。
第二,通过广域网专线。
所以为了数据安全,不能连接因特网,需要用一条自己的专用线路来传输数据,这条线路上只有自己人,不会有其他人接入,且距离很远,这个网络就叫 "广域网"。
光猫
光猫是一种类似于基带modem(数字调制解调器)的设备,和基带modem不同的是接入的是光纤专线,是光信号。用于广域网中光电信号的转换和接口协议的转换,接入路由器,是广域网接入。
将光线插入左侧的灰色口,右侧网口接网线到路由器即可。
路由器
用于连接局域网和外网
路由器需要区分WAN口和LAN口,WAN口是接外网的(从Modem出来的或者从上一级路由器出来的),LAN口是接内网的,现在路由器都带无线功能,本质上无线接入就是LAN。
网线
背过一种线序,了解网线的制作流程。
设备通信的要素
思考:如何抓捕周树人?
答:必须说明周树人,字鲁迅 才能精准的抓人。
思考:你通过QQ发送一条消息,最终谁处理了这条信息?
答:需要通过IP定位对方的机器,那么周树人就是IP,鲁迅就是端口。
IP地址
基本概念
- IP地址是Internet中主机的标识
- Internet中的主机要与别的机器通信必须具有一个IP地址
- IP地址为32位(IPv4)或者128位(IPv6)
- 表示形式:常用点分十进制,如192.168.1.109,最后都会转换为一个32位的无符号整数。
地址划分
主机号的第一个和最后一个都不能被使用,第一个作为网段号,最后一个作为广播地址。
A类:1.0.0.1~126.255.255.254`
`B类:128.0.0.1~~191.255.255.254`
`C类:192.0.0.1~~223.255.255.254`
`D类(组播地址):224.0.0.1~~239.255.255.254`
`
网段号的定义:主机位全为0,代表当前设备所处的网段号
这个需要结合子网掩码来计算,子网掩码规定了哪些是网络号,哪些是主机号
如果子网掩码位是1,那么当前为就是网络号,如果是0,那么当前位是主机号
网段号=IP&子网掩码
特殊地址(后续编程使用)
0.0.0.0:在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。在程序里,用宏定义表示:INADDR_ANY
127.0.0.1:回环地址/环路地址,所有发往该类地址的数据包都应该被loop back。仅作为测试使用,只能实现本机上通信。
IP地址转换
实现了人看的IP(192.168.1.155)和机器内部使用(32位的无符号的整数)的实际IP进行转换。
struct` `in_addr` `{`
`uint32_t s_addr;`
`};`
`//从人看的ip地址转为机器使用的32位`
`typedef` `uint32_t` `in_addr_t;`
`in_addr_t` `inet_addr(const` `char` `*cp);`
`//从机器到人`
`char` `*inet_ntoa(struct` `in_addr in);`
`
示例:
给定一个IP地址,转换为机器的32位无符号整数,然后打印。
打印完后,再转回给人看的IP地址,再打印。
#include <stdio.h>`
`#include <sys/socket.h>`
`#include <netinet/in.h>`
`#include <arpa/inet.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`//把人看的转成机器要的,192.168.0.109`
`char ip[64]` `=` `"192.168.0.109";`
`in_addr_t ip32 =` `inet_addr(ip);`
`printf("ip = 0x%x\n", ip32);` `//0x6d00a8c0`
`//把机器的转成人看`
`struct` `in_addr in;`
` in.s_addr = ip32;`
`char` `*p =` `inet_ntoa(in);`
`printf("p = %s\n", p);` `//0x6d00a8c0`
`return` `0;`
`}`
`
端口
- 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区
- TCP端口号与UDP端口号独立
- 端口号一般由IANA (Internet Assigned Numbers Authority) 管理
- 端口用两个字节来表示--USHORT
- 端口指定需要统一为网络字节序(大端)
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)`
`注册端口:1024~49151(尽量用5000以上的)`
`动态或私有端口:49152~65535`
`
字节序
网络中传输一字节以上的带类型的数据(比如short、int),必须使用网络字节序,即大端字节序。
小端序(little-endian) - 低序字节存储在低地址
大端序(big-endian)- 高序字节存储在低地址
面试题:写一个函数,判断当前主机的字节序?
int` `checkCPU()`
`{`
`union w{`
`short a;`
`char b;`
`}c;`
` c.a =` `1;`
`return` `(c.b ==` `1);`
`}`
`
主机字节序到网络字节序
uint16_t` `htons(uint16_t hostshort);`
`
网络字节序到主机字节序
uint16_t` `ntohs(uint16_t netshort);`
`
网络模型
网络的体系结构
- 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。
- 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务
- 网络体系结构即指网络的层次结构和每层所使用协议的集合
- 两类非常重要的体系结构:OSI与TCP/IP
OSI模型
- OSI模型是一个理想化的模型,尚未有完整的实现
- OSI模型共有七层
- OSI现阶段只用作教学和理论研究
TCP/IP模型
网络接口和物理层 :屏蔽硬件差异(驱动),向上层提供统一的操作接口。
网络层 :提供端对端的传输,可以理解为通过IP寻址机器。
传输层 :决定数据交给机器的哪个任务(进程)去处理,通过端口寻址
应用层 :应用协议和应用程序的集合
OSI和TCP/IP模型对应关系图
注意:TCP和IP是属于不同协议栈层的,只是这两个协议属于协议族里最重要的协议,所以协议栈或者模型以之命名了。
TCP/UDP
TCP
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。
适用场景
适合于对传输质量要求较高的通信
在需要可靠数据传输的场合,通常使用TCP协议
登录账户、文件传输等
UDP
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,不需要进行连接,可以进行高效率的数据传输。
适用场景
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
适合于广播/组播式通信中。
音频、视频聊天等
编程预备知识
socket定义
socket类型
流式套接字(SOCK_STREAM) TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
服务器:`
` 1.创建流式套接字(socket())------------------------> 有手机`
` 2.指定本地的网络信息(struct sockaddr_in)----------> 有号码`
` 3.绑定套接字(bind())------------------------------>绑定电话`
` 4.监听套接字(listen())---------------------------->待机`
` 5.链接客户端的请求(accept())---------------------->接电话`
` 6.接收/发送数据(recv()/send())-------------------->通话`
` 7.关闭套接字(close())----------------------------->挂机`
`客户端:`
` 1.创建流式套接字(socket())----------------------->有手机`
` 2.指定服务器的网络信息(struct sockaddr_in)------->有对方号码`
` 3.请求链接服务器(connect())---------------------->打电话`
` 4.发送/接收数据(send()/recv())------------------->通话`
` 5.关闭套接字(close())--------------------------- >挂机`
`
函数接口
socket
int` `socket(int domain,` `int type,` `int protocol);`
`//作用:创建一个socket通信描述符`
`domain:指定通信的域(通信协议)`
` AF_UNIX, AF_LOCAL 本地通信`
` AF_INET ipv4`
` AF_INET6 ipv6`
`type:指定socket的类型`
` SOCK_STREAM:流式套接字,接下来我们的通信使用TCP协议`
` SOCK_DGRAM:数据报套接字,接下来我们的通信使用UDP协议`
`protocol:填0`
`返回值:如果成功,返回创建的描述符,如果失败,返回-1`
`
connect
int` `connect(int sockfd,` `const` `struct` `sockaddr` `*addr,`
`socklen_t addrlen);`
`作用:请求连接服务器`
`参数:`
`sockfd:上面socket接口得到的描述符`
`addr:相当于服务器的地址(IP+port)`
`addrlen:地址的长度,因为前面的地址是可变的,所以要通过参数来协定地址的长度`
`返回值:`
`0` `-1`
`
sockaddr结构体
//从bind接口的帮助文档中拿到`
`struct` `sockaddr` `{`
`sa_family_t sa_family;`
`char sa_data[14];`
`}`
`上述地址结构是一个通用结构,我们在用实际协议进行通信的时候,需要转换成相应协议的结构体。`
`用man 7 ip来查看ipv4对应的结构体`
`struct` `sockaddr_in` `{`
`sa_family_t sin_family;` `/* 地址协议族,=socket接口第一个参数 */`
`in_port_t sin_port;` `/* 指定端口,端口要用网络字节序(大端) */`
`struct` `in_addr sin_addr;` `/* IP地址 */`
`};`
`/* Internet address. */`
`struct` `in_addr` `{`
`uint32_t s_addr;` `/* address in network byte order */`
`};`
`
bind
int` `bind(int sockfd,` `const` `struct` `sockaddr` `*addr,`
`socklen_t addrlen);`
`作用:绑定服务器地址:IP和端口,相当于对外公开自己的IP和端口,客户端就可以连接了`
`addr:绑定的IP和端口结构体,注意绑定的是自己的IP和端口`
` IP:取真实的某个网卡的地址,也可以直接写0.0.0.0`
`addrlen:地址的大小 `
`
listen
int listen(int sockfd, int backlog);`
`作用:进入监听状态,以便接收客户端的连接`
`sockfd:上面的服务器描述符`
`backlog:同时能处理的客户端的连接数量,写个5 10都可以`
`返回值:0 -1`
`
accept
int` `accept(int sockfd,` `struct` `sockaddr` `*addr,` `socklen_t` `*addrlen);`
`接收客户端连接,注意,这是一个阻塞接口,如果没有新的连接过来,那么会等待`
`sockfd:上面的服务器描述符`
`addr:客户端的地址(来电显示)`
`addrlen:客户端地址的长度,是入参,传入然后可能会被接口修改`
`返回值:如果成功,会返回一个非负的整数,代表接收的新的连接的描述符`
`
recv/send
//recv和send是网络的专用接口,比起read和write只是多了一个flags参数,flag一般情况下会取0,代表阻塞接受和发送`
`ssize_t` `recv(int sockfd,` `void` `*buf,` `size_t len,` `int flags);`
`返回值:`
`>0:接收的字节数`
`<0:失败`
`=0:代表对端退出了`
`ssize_t` `send(int sockfd,` `const` `void` `*buf,` `size_t len,` `int flags);`
`
close
int` `close(int fd);`
`关闭套接字连接`
`
示例代码
TCP客户端
#include <stdio.h>`
`#include <sys/types.h> /* See NOTES */`
`#include <sys/socket.h>`
`#include <errno.h>`
`#include <string.h>`
`#include <unistd.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h> /* superset of previous */`
`#include <arpa/inet.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`//创建套接字`
`int fd =` `socket(AF_INET, SOCK_STREAM,` `0);`
`if(fd <` `0)`
`{`
`perror("socket err");`
`return` `-1;`
`}`
`//连接到服务器`
`struct` `sockaddr_in server_addr;`
`int len =` `sizeof(server_addr);`
`//指定连接的服务器的地址(IP+port)`
`memset(&server_addr,` `0, len);`
` server_addr.sin_family = AF_INET;`
` server_addr.sin_port =` `htons(8888);`
` server_addr.sin_addr.s_addr =` `inet_addr("192.168.0.109");`
`int ret =` `connect(fd,` `(struct` `sockaddr` `*)&server_addr, len);`
`if(ret <` `0)`
`{`
`perror("connect err");`
`return` `-1;`
`}`
`printf("conect success\n");`
`char buf[64]` `=` `{0};`
`while` `(1)`
`{`
`memset(buf,` `0,` `64);`
`//从终端接收用户输入,发给服务器,等待服务器的数据,打印`
`gets(buf);`
`if(strcmp(buf,` `"quit")` `==` `0)`
`{`
`break;`
`}`
`send(fd, buf,` `64,` `0);`
`memset(buf,` `0,` `64);`
` ret =` `recv(fd, buf,` `64,` `0);`
`if(ret >` `0)`
`{`
`printf("recv data = %s\n", buf);`
`}`
`else` `if(ret ==` `0)`
`{`
`printf("peer exit\n");`
`break;`
`}`
`else`
`{`
`perror("recv err\n");`
`return` `-1;`
`}`
`}`
`close(fd);`
`return` `0;`
`}`
`
TCP服务器
#include <stdio.h>`
`#include <sys/types.h> /* See NOTES */`
`#include <sys/socket.h>`
`#include <errno.h>`
`#include <string.h>`
`#include <unistd.h>`
`#include <netinet/in.h>`
`#include <netinet/ip.h> /* superset of previous */`
`#include <arpa/inet.h>`
`int` `main(int argc,` `char` `const` `*argv[])`
`{`
`//创建服务器的套接字`
`int server_fd =` `socket(AF_INET, SOCK_STREAM,` `0);`
`if(server_fd <` `0)`
`{`
`perror("socket err");`
`return` `-1;`
`}`
`//初始化服务器地址`
`struct` `sockaddr_in server_addr, client_addr;`
`int len =` `sizeof(server_addr);`
`memset(&server_addr,` `0, len);`
` server_addr.sin_family = AF_INET;`
` server_addr.sin_port =` `htons(8888);`
`#if 0 `
` server_addr.sin_addr.s_addr =` `inet_addr("192.168.0.194");`
`#else`
` server_addr.sin_addr.s_addr = INADDR_ANY;`
`#endif`
`//绑定套接字的地址`
`int ret =` `bind(server_fd,` `(struct` `sockaddr` `*)&server_addr, len);`
`if(ret <` `0)`
`{`
`perror("bind err");`
`return` `-1;`
`}`
`//启动监听`
` ret =` `listen(server_fd,` `5);`
`if(ret <` `0)`
`{`
`perror("listen err");`
`return` `-1;`
`}`
`//接收连接`
`int clientfd =` `accept(server_fd,` `(struct` `sockaddr` `*)&client_addr,` `&len);`
`if(clientfd <` `0)`
`{`
`perror("accept err");`
`return` `-1;`
`}`
`//打印客户端的地址,注意端口要转成主机字节序`
`printf("recv a new connect, ip = %s, port=%d\n",\`
`inet_ntoa(client_addr.sin_addr),` `ntohs(client_addr.sin_port));`
`char buf[64]` `=` `{0};`
`while` `(1)`
`{`
`memset(buf,` `0,` `64);`
`//一旦新的连接过来后,后续通信统统使用新的描述符`
` len =` `read(clientfd, buf,` `64);`
`if(len >` `0)`
`{`
`printf("recv data = %s\n", buf);`
`}`
`else` `if(len ==` `0)`
`{`
`printf("client exit\n");`
`break;`
`}`
`else`
`{`
`perror("recv err\n");`
`return` `-1;`
`}`
`}`
`close(clientfd);`
`close(server_fd);`
`return` `0;`
`}`
`