网络的分层模型
应用层:用于自定义的网络数据协议
传输层:TCP/UDP协议
网络层:IPV4/IPV6,4G,5G 协议
物理层:一些硬件,网线、光纤、路由器
协议
协议是通信双方实现通信而制定的通话规则
目的:为了提高通信效率和安全性
协议自己想怎么制定就怎么制定,但是使用别人的协议的时候必须遵守别人的协议规则
IP地址
作用:标识唯一主机
IPV4:4个字节 192.168.64.1
IPV6:6个字节 0-255.0-255.0-255.0-255
- A类地址 - 第1字节为网络地址,其他3个字节为主机地址。 - 第1字节的最高位固定为0 - `1.0.0.1 – 126.255.255.255`
- B类地址 - 第1字节和第2字节是网络地址,其他2个字节是主机地 址。 - 第1字节的前两位固定为10 - `128.0.0.1 – 191.255.255.255`
- C类地址 - 前3个字节是网络地址,最后1个字节是主机地址。 - 第1字节的前3位固定为110 - `192.0.0.1 – 223.255.255.255`
- D类地址(组播地址) - 不分网络地址和主机地址,第1字节的前4位固定为 1110 - `224.0.0.1 – 239.255.255.255`
端口:区分一个主机中的不同的网络进程,每一个网络进程都有一个唯一的端口号
端口号是一个短整型数据,长度为16位。具体分布:
- 系统端口:1~1023 (不推荐使用,系统网络进 程已经占用了)
- 注册端口:1024~49150 - 动态或私有端口:49151~65535(这是平常做实验可用 的端口号范围
网络字节序-----大端序
字节序: 大端字节序,低地址存放,高字节
小端字节序,低地址存放,低字节
通常,将最低有效位(即78)放在低地址的存储方式称 为小端序,反之即大端序。在单机编程中,字节序是系 统内部的存储细节,与程序无关。但在网络编程中,由 于数据是在两台不同的机器中传输和表达,因此如果字 节序不一致,则会出现牛头不对马嘴的现象。 解决这一困境的办法是,将网络中的数据,统一为某种 固定的字节序,比如大端序。这样一来,凡是从主机往 外发的数据,一律转换为大端序,凡是从网络接收的数 据,也一律是大端序,网络字节序屏蔽了通信双方的具 体细节,从而使得双方通信成为可能。
子网与公网和外网
内网:(子网)局域网
内网又称局域网(LAN),是指在某一区域内由多台计算机 以及网络设备构成的网络,比如校园网、政府网等,一般方 圆几公里。
公网: Internet 网由运营商提供。 (实现远程通信)
外网: 需要翻墙工具, 梯子,飞机 ....
什么是TCP
TCP 通信还会被冠以 可靠传输协议 的头衔。但请注意,这里的可靠并非指发出去的数据对方一定能收到(这是不可能的),而仅指TCP能使发送方可靠地知道对方是否收到了数据。
TCP基本特征
有连接:通信双方需要事先连接成功,方可传输数据
有确认:一方收到对端的任何数据,都会给另一方发回执确认
保证数据有序、不重复、丢失会重发
如果网络拥堵,会自动调节发送量
采用帧异步的流式通信方式(即通信双方每次的收发数据量不必相等)
简单来讲,TCP 类似于打电话,说话前需要花一定的时间接通电话,等到对方接听了之后双方才能开始通信,通信的过程中每个数据的传送,接收方都会给发送方回执确认,断开的时候也会互相通知以便于释放各自相关的资源。可以看出来,TCP 相对于 UDP 而言资源开销更大,提供更丰富的功能,TCP适合用在如下情形:
传输质量要求较高,不能丢失数据
大数据量的通信,以至于通信前后的连接和断开的开销可以忽略不计
用户登录、账户管理等相关的功能
TCP流程图
TCP服务器创建流程
1、socket 创建服务器对象
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
2、bind 绑定服务器地址信息
#include <sys/types.h>
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *addr, socklen_t addrlen);
sockfd:服务器对象
addr:服务器地址
addrlen:服务器地址长度
返回值: 成功 0
失败 -1
3.listen 设置为监听模式
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd:服务器对象
backlog:同时支持多少个用户链接。 backlog 最大值可设至128。
返回值: 成功 0
失败 -1
4、accept接受链接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:服务器对象
addr:对方(发起请求者)IP地址信息
addrlen:对方(发起请求者)IP地址的大小
返回值: 返回一个 新的socket
失败 -1
TCP客户端创建流程
1、sockect 创建通信对象
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:网络协议 AF_INET IPv4 Internet protocols ip(7)
type:传输协议 SOCK_STREAM TCP
protocol:属性默认为 0 即可
返回值: 成功 网络文件描述符
失败 -1
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
2、connect 连接服务器(重点)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:网络文件描述符
addr:需要连接的服务器地址信息 (你要打电话给某人必须要知道他的电话号码)
addrlen:地址长度
返回值:成功 0
失败 -1
//服务器地址结构体
struct sockaddr_in {
sa_family_t sin_family; /* 服务器地址类型: AF_INET->IPV4类型*/
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 */
};
一般的IP地址都是以点分制编写的如:192.168.64.1
#include <arpa/inet.h>
把字符串IP转换为 uint32_t IP
in_addr_t inet_addr(const char *cp);
把uint32_t IP转换为字符串 IP
char *inet_ntoa(struct in_addr in);
网络通信都采用大端序
uint16_t htons(uint16_t hostshort); //把本地序转换为网序
uint16_t ntohs(uint16_t netshort); //把网络序转换为本地序
demo连接 ip:192.168.64.1端口:2333的服务器:
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET; //IPV4
ser_addr.sin_port = htons(2333); //把本地序转换为网络序
ser_addr.sin_addr.s_addr = inet_addr("192.168.64.1");
int ret=connect(tcp_socket,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if(ret < 0)
{
perror("");
}else
{
printf("连接成功\n");
}
3、read/write读写 或者 send/recv 发送接收
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd: 需要发送的网络文件描述符
buf: 数据缓存区
len: 数据的大小
flags:一般设0
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd: 需要发送的网络文件描述符
buf: 数据缓存区
len: 数据的大小
flags:一般设0
参数flags一般设0,其他数值定义如下
MSG_OOB 传送的数据以out-of-band 送出。
MSG_DONTROUTE 取消路由表查询
MSG_DONTWAIT 设置为不阻塞
MSG_NOSIGNAL 此动作不愿被SIGPIPE 信号中断。
UDP
什么是UDP:UDP 通信还会被冠以不可靠的头衔。这里的不可靠指的是:无法可靠地得知对方是否收到数据。
特征:
1、无连接:通信双方不需要事先连接
2、无确认:收到数据不发给对方发回执确认
3、数据丢失 后不会重发
4、采用帧同步数据报信方式(双方每次的收发数据量相等)
UDP类似于寄信,寄信人无法是否确认对方是否收到信。但是UDP无需连接、无需确认、无需缓冲区、无需分包序列号,所以它的效率比较高。
UDP适用于发小数据文件(如:对DNS服务器进行IP地址查询时)
UDP通信流程图
UDP单播设计
特点:指定IP和端口,进行一对一通信。
发送端
1、创建UDP通信对象
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
//创建UDP 通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
2、发送UDP数据报
ssize_t sendto(int sockfd, //UDP 通信对象socket
const void *buf,//数据缓存地址
size_t len, //数据大小
int flags, //属性默认为 0 即可
const struct sockaddr *dest_addr, //目标地址 (接收信息主机的地址)
socklen_t addrlen);//地址长度
返回值: 成功 发送的字节数
失败 -1
const struct sockaddr *dest_addr 的设置如下:
struct sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(端口);
dest_addr.sin_addr.s_addr=inet_addr(IP地址);
demo : 发送一个UDP数据报
sendto(udp_socket,"hello",5,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
接收端
1、创建UDP通信socket
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
//创建UDP 通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
2.绑定接收端的地址信息
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(端口);
server_addr.sin_addr.s_addr=INADDR_ANY; //所有网卡地址
bind(udp_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
3.接收UDP 数据报
ssize_t recvfrom(int sockfd, //UDP 通信对象socket
void *buf,//接收数据缓存地址
size_t len, //数据长度
int flags, //属性默认为0
struct sockaddr *src_addr, //数据发送者的IP地址信息
socklen_t *addrlen);//IP地址信息长度
demo:只接收数据不获取IP
char buf[1024]={0};
recvfrom(udp_socket,buf,1024,0,NULL,NULL);
UDP组播设计
特点:加入小组的都可以接收到数据
1、创建UDP通信对象
int udp_socket = socket(AF_INET,SOCK_DGRAM,0);
2、绑定UDP通信对象
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(端口);
server_addr.sin_addr.s_addr=INADDR_ANY; //所有网卡地址
DR_ANY; //所有网卡地址
bind(udp_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
3、加入组播
struct ip_mreq a;
bzero(&a, sizeof(a));
a.imr_interface.s_addr = INADDR_ANY; //所有网卡地址加入组播
a.imr_multiaddr.s_addr = inet_addr("224.10.10.10"); // 指定多播地址
//开启组播功能
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &a, sizeof(a));
————————————————————————————————————————————————————————————————————
//组播结构体
struct in_addr
{
in_addr_t s_addr;
};
struct ip_mreq
{
struct in_addr imr_interface; /* 需要加入组播的IP地址 */
struct in_addr imr_multiaddr; /* 组播地址(D类地址) */
};
D类地址: 224.0.0.1 ~ 239.255.255.255
4、往组播地址发送数据
struct sockaddr_in arry_addr;
arry_addr.sin_family=AF_INET;
arry_addr.sin_port=htons(8888);
arry_addr.sin_addr.s_addr=inet_addr("224.10.10.10"); //所有网卡地址
sendto(udp_socket,buf,strlen(buf),0,(struct sockaddr *)&arry_addr,sizeof(arry_addr));
UDP广播设计
特点:在同一个(网络地址)网段中的所有主机都可以接收到数据。
1、创建UDP通信对象
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
2、开启广播功能
int on=1; //开启
int ret = setsockopt(udp_socket,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(ret < 0)
{
perror("广播开启失败:");
return -1;
}else
{
printf("广播开启成功\n");
}
3、发送数据
//设置广播地址
struct sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(6666); //所有处于192.168.64网段且,端口为 6666 的进程都可以收到数dest_addr.sin_addr.s_addr=inet_addr("192.168.64.255"); //设置广播地址
//发送一个UDP 广播数据报
sendto(udp_socket,"hello",5,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));