1. 入门
1.1 网络概念
1.1.1 互联网的分类
按照网络的作用范围:广域网(WAN)、城域网(MAN)、局域网(LAN)
按照网络使用者:公用网络、专用网络
因特网(Internet):世界上最大的互联网(用户数以亿计)
1.1.2 计算机网络的性能指标
速率:单位时间内发送或接收的比特位(二进制位)bit
时延:发送时延、传播时延、接收时延、排队时延
往返时间RTT:数据报文在端对端通信中的来回一次的时间
带宽:网络通信线路所能传输的能力
利用率:信道的占用率
丢包率:丢弃的数据占总数据的比例
1.1.3 计算机网络体系结构
由于在通信的计算机设备之间可能存在差异性,所以需要在设计计算机网络通信时屏蔽掉这些差异性 进行通信的网络设备,网络设备为了能够正常通信做出一种“约定”----协议 网络协议采用
分而治之,将网络通信的功能分成不同的模块,以分层的思想组合在一起,每一层(协议/约定/规定)实现不同的作用 网络系统结构:网络的层次结构和每一层使用的协议集合
2. 体系结构
层次结构的原则:
各层之间相互是独立的
每一层需要有足够的灵活性
2.1 OSI体系结构
编号 | 层次 | 作用 |
7 | 应用层 | 为计算机用户提供接口和服务,为具体的应用提供协议 |
6 | 表示层 | 数据处理(加密 解密 编码 解码) |
5 | 会话层 | 建立通信进程的逻辑名字与物理名字的关联(管理建立、维护、重连、通信会话) |
4 | 传输层 | 管理端对端的通信连接 |
3 | 网络层 | 路由选择(决定数据在网络的路径),数据分组,路由选择 |
2 | 数据链路层 | 管理相邻节点之间的数据通信,解决分组在同一个网络上(在一个链路上)传输的问题 |
1 | 物理层 | 数据通信的光电物理特性,解决使用何种信号来传输比特流 |
2.2 TCP / IP体系结构
编号 | 层次 | 作用 |
7 | 应用层 | 为计算机用户提供接口和服务,为具体的应用提供接口和协议 |
6 | 传输层 | 端对端的通信连接 |
5 | 网络层 | 数据路由(决定数据在网络的路径),数据分组,路由选择 |
1 | 网络接口层 | 数据组成可发送的、可接收的帧 |
3. 层次详解
3.1 物理层
这一层连接不同的物理设备,传输比特流,为数据提供一个传输可靠的物理介质(媒介)
物理硬件的协议规定,该层主要定义了物理设备的标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速度。 它的作用主要就是能够传输比特流(就是由 1 0 转换为电流强弱来进行传输,到达目的地后转化为0 1),这一层传输的数据是比特。
3.2 数据链路层
数据链路层---- 定义了如何让数据进行传输,以及如何控制对物理介质的访问,这一层通常还提供错误检测(有纠错功能) 数据链路层最基本的服务是将来自于上层的数据可靠的传输到相邻的主机 包括:物理地址寻址、数据的封装成帧、流量的控制、数据的检测
链路:从一个结点到相邻结点的一段物理线路,中间没有经过其他的交换结点
在数据链路层有一个地址,物理地址:mac地址, mac地址就是以太网的数据链路层所使用的物理设备的地址,mac地址是在数据链路层mac帧才使用,ppp帧的数据链路层不需要地址 当多个主机进行通信,每个主机在数据链路层必须有一个唯一的标识(即物理硬件的地址) mac地址:6字节,mac地址是固化在计算机的物理网卡中,也叫做硬件地址 mac地址的分配:
MAC地址只能在有链路时才能起作用
3.3 网络层
网络层-----找区域门牌号 网络层实现两个端主机之间的数据透明传输,具体的功能包括寻址和路由选择、连接的建立 由于mac地址不具备区分不同网络的功能(mac地址只有链路连接通信),所以只在一个单独的网络中可以使用mac地址。对于网络互联的主机,只能通过网络层的协议,实现数据报文在各网络之间传输。 主要解决:
1. 网络层寻址的问题
2. 路由选择问题
在网络层中,对所有加入网络的主机添加了唯一的网络地址(ip地址) ip地址:是因特网上的主机所使用的唯一编号 一个主机的ip地址 = 网络号 + 主机号
网络号:标识因特网上数以百万计的网络
主机号:标识在同一个网络中的不同主机 通过ip地址,就可以标识出在因特网上主机的唯一编号,就可以实现跨网络寻址
ipv4:是因特网上的每一台主机(路由)分配的一个全球范围内唯一的32bit的标识符;对于ipv4地址有32位,表示ipv4地址不方便,因此ipv4地址采用点分十进制来表示,查看方便
ipv6:128比特位
分类编制----按照网络号的位数
划分子网
在网络中申请的网络号,相对比较大(主机位数比较多),可以表示的主机比较,可以在原网络中,分成若干个小的网络,把原网络的主机号借用一部分来共同表示网络号----划分子网 如:198.223.192.0 借用3位表示网络号 划分成: 198.223.192.0 ,198.223.192.32 ,198.223.192.64
198.223.192.128 ,198.223.192.96, 198.223.192.160 , 198.223.192.192 ,198.223.192.224 主机号只占用最低5位
子网掩码:作用判断,当前ip地址网络号的位数;子网掩码也是32bit,在ip地址对应的位如果是网络号,则子网掩码中对应的位为1, 在ip地址对应的位如果是主机号,则子网掩码中对应的位为0
无分类编址 在ipv4地址后加上一个 / 在斜线后写上当前网络的网络号是多少位 如: 198.223.192.5 / 20
IPV4数据报头(网络层头):
3.4 传输层
通过网络层,实现主机与主机之间的通信 在实际网络通信中通信的主体一般还是位于主机中的进程 如何为运行在不同的主机的应用进程提供通信是传输的任务,传输层实现应用程序的通信,传输层协议也叫做端对端协议
TCP协议 UDP协议 使用传输层协议,可以在协议内容中添加要通信的双方进程是谁
在传输层中设计了一个叫做端口号来标识一个进程中要进行网络通信 端口号:16位,就是进行网络通信的进程的通信标识
UDP协议
TCP协议
传输控制协议 1. TCP是一种可靠的传输服务(不会出现传输差错、丢失、重复等各种现象) 2. 是面向连接的服务(通信双方要建立连接) 3. 一个tcp连接只能有两个端点,是一对一通信 4. 是可靠传输:拥塞控制、流量控制、超时重传 5. 面向字节流的传输方式
TCP是一种面向连接,在进行通信前连接是必不可少的过程,在通信完成后,断开连接也是必不可少的
TCP通信有三个阶段: 1、建立连接 2、数据传输进行通信 3、释放连接 连接
解决了以下问题: 1、使TCP通信双方都知道对方的存在 2、能够协商一些参数信息内容 3、能够使TCP双方资源数据进行分配
TCP建立连接: 建立TCP连接的步骤共有三步,叫做三次握手:
第一次握手: TCP客户端打算建立连接,向服务器发送连接请求报文,客户端请求连接,客户端进行同步发送状态(SYNSEND) 在连接请求报文中 把SYN = 1,表示这是一个请求连接报文 把序号字段seq = x(x就是一个初始值),作为客户端的初始序号
第二次握手: TCP服务端在收到客户端请求后,如果同意连接,则会向客户端发送确认请求报文,服务器进入同步接收状态 在确认报文中 把同步位SYN和确认位ACK位置为1,表示这是一个请求确认报文 把序号seq设置为一个初始值y,作为服务器的初始序号 把确认号字段ack = x+1,作为对客户端的确认
第三次握手: TCP客户端在收到了服务器的确认信号后,还要向服务器发送一个确认报文,并进入连接已建立(ESTABLISHED) 发送针对服务器确认的确认报文 确认位ACK = 1,表示这是一个确认报文 序号seq = x+1,表示第一次是x,第二次发送x+1 确认号ack = y+1,则是对服务器的确认 服务器收到后,进入连接已建立
TCP连接释放(断开连接): 释放连接(关闭)步骤共有四步,叫做四次挥手
第一次挥手: TCP客户端主动关闭tcp连接,TCP客户端发送连接释放(断开)报文,并进入终止等待态1 状态 在连接断开释放报文中: 终止位FIN和确认位ACK设置为1,表示是一个TCP连接释放报文,同时对之前的报文做确认 序号seq设置为u,等于之前已经传送过去的数据最后一个字节序号+1 确认号ack设置为v,等于之前收到的数据最后一个字节序号+1
第二次挥手: TCP服务器收到TCP连接断开请求报文,会发送一个确认报文给客户端,并进入关闭等待状态,客户端收到确认会进入终止等待态2 状态 在断开确认报文中: 确认号ACK = 1,表
示是一个确认报文 序号seq = v,等于之前服务器发送的最后一个字节+1,与第一次挥手客户端的确认号做匹配 继续把当前没有传输完成的数据传输完毕
第三次挥手: TCP服务端给客户端发送连接释放报文,并进入最后确认状态 在服务端连接释放报文中: 终止位FIN和确认位ACK设置为1,表示这是一个TCP连接释放,同时对之前的数据做确认
序号seq = w,这时服务器属于半关闭状态 确认号ack = u+1,释放的重复确认
第四次挥手: TCP客户端在收到服务端连接释放,发送确认报文,并客户端进入时间等待状态 确认位ACK = 1,表示是一个确认报文 序号seq = u+1,表示是一个释放报文 确认号ack =w+1,表示是一个确认
3.5 应用层
数据在应用程序中,可以有自己的数据解析方式,针对单独的应用设计自己的协议-----应用层都是自定义内容
4.UDP通信编程
套接字文件 socket :作为进程之间通信的接口 相当于就是特殊的一种文件,这种文件不存储任何数据内容,相当于就是一套网络通信协议的选择,使用这个文件就是使用相对应的协议进行通信 socket:特殊的文件描述符,并不限于TCP/IP协议
API函数介绍:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能说明://创建套接字文件,为进程添加一个对应的网络通信协议(文件),返
回值就是创建号的文件描述符(socket描述符就代表一套协议---套接字)
参数:
参数1:
int domain:地址族,选用那种网络层协议地址
AF_INET------IPV4
参数2:
int type:套接字类型
SOCK_STREAM--------TCP
SOCK_DGRAM---------UDP
SOCK_RAW:原始套接字(没有传输层协议)
参数3:
int protocol:套接字协议
0:套接字默认协议
返回值:
int:整数---------文件描述符(套接字文件)
成功:返回套接字描述符 >= 0
失败:返回-1
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr*addr,socklen_t addrlen);
功能://绑定本地网络信息到套接字中
参数:
参数1:
int sockfd:绑定本地网络信息到哪个套接字上(绑定到哪一套协议上)
参数2:
const struct sockaddr *addr:结构体地址,这个结构体中存储的本地网络信息(要绑定的网络信息ip、port)
struct sockaddr {//通用结构体,表示一个网络信息内容
sa_family_t sa_family;
char sa_data[14];
}
//IPV4 网络信息结构体
struct sockaddr_in {
sa_family_t sin_family;//地址族AF_INET
in_port_t sin_port;//端口
struct in_addr sin_addr;//结构体变量--ip地址
};
/* Internet address. */
struct in_addr {
uint32_t s_addr;//ipv4地址
};
参数3:
socklen_t addrlen:整数,结构体大小(确定信息结构体的大小)
返回值:
成功:返回0
失败:返回-1
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len,int flags,const struct sockaddr *dest_addr, socklen_taddrlen);
功能说明://使用UDP协议发送数据
参数:
参数1:int sockfd:套接字对象,使用哪个套接字发送数据
参数2:const void *buf:要发送的数据的内存首地址
参数3:size_t len:发送的数据的大小
参数4:int flags:标志0:阻塞发送
参数5:const struct sockaddr *dest_addr //发送的目标网络信息
参数6:socklen_t addrlen:结构体大小
返回值:
成功:返回发送的字节数
失败:返回-1
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, intflags,struct sockaddr *src_addr, socklen_t *addrlen);
功能说明://使用UDP协议接收数据
参数:
参数1:
int sockfd:套接字,指定使用哪个套接字来接收(ip、port来进行检测)
参数2:
void *buf:接收的数据存放的地址
参数3:
size_t len:指定能够接收存储的最大大小
参数4:
int flags:标志 0:阻塞接收
参数5:
struct sockaddr *src_addr:在接收数据时,存储发送方的ip和port,如果不需要写NULL
参数6:
socklen_t *addrlen:存储信息结构体的大小,不需要写NULL
返回值:
成功:返回收到的大小
失败:返回-1
注意:udp可以在任意的两个程序之间进行,在发送数据时只用指定接收方是谁-----UDP可以在任意的进程之间进行通信
//UDP通信实例代码(client),服务端一样,只是换一下端口
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 20
void udp_init(int sockfd1,struct sockaddr_in *addr1){
if(sockfd1<0){
perror("套接字创建失败");
exit(0);
}
else puts("套接字创建成功");
//2.绑定套接子,添加网络信息 本机网络信息
addr1->sin_family=AF_INET;//初始化结构体变量 地址族
addr1->sin_port=htons(8888);//端口号
addr1->sin_addr.s_addr=inet_addr("192.168.88.148");
//初始化完成后可以开始绑定
int bindret=bind(sockfd1,(struct sockaddr*)addr1,sizeof(struct sockaddr_in));//该函数返回值为int,成功返回0,失败返回-1,所以定义bindret接收返回值
if(bindret<0){
perror("bind error");
exit(0);
}
else puts("bind sucucess");
}
void msg_server(struct sockaddr_in *dest_addr1){//添加接收方网络信息
//初始化接收方网络信息
dest_addr1->sin_family=AF_INET;//初始化结构体变量 地址族
dest_addr1->sin_port=htons(9999);//端口号
dest_addr1->sin_addr.s_addr=inet_addr("192.168.88.148");
//sendto(sockfd1,buf,sizeof(buf),0,dest_addr1,sizeof(dest_addr1));
}
void receive(int sockfd1,struct sockaddr_in src_addr1){//接收消息,存放到数组指针
char buf[N];
socklen_t len=sizeof(struct sockaddr_in);
recvfrom(sockfd1,buf,sizeof(buf),0,(struct sockaddr *)&src_addr1,&len);
printf("buf:%s,src_addr1.sin_port:%u,src_addr1.sin_addr.s_addr:%x\n",buf,htons(src_addr1.sin_port),src_addr1.sin_addr.s_addr);
}
int main(){
char buf[N];
int sockfd1=socket(AF_INET,SOCK_DGRAM,0);//函数返回值为int型,所以定义一个int型sockfd套接字文件描述符用来接收返回值
struct sockaddr_in addr1;//声明一个结构体实例为addr//本机网络信息
struct sockaddr_in dest_addr1;//声明一个结构体实例为dest_addr1//发送目标的网络信息
struct sockaddr_in src_addr1;//声明一个结构体实例为src_addr1//接收到的对方的网络信息
udp_init(sockfd1,&addr1);
msg_server(&dest_addr1);
while(1){
fgets(buf,sizeof(buf),stdin);
sendto(sockfd1,buf,sizeof(buf),0,(struct sockaddr *)&dest_addr1,sizeof(dest_addr1));
memset(buf,0,sizeof(buf));
receive(sockfd1,src_addr1);
puts("receive cucess");
}
return 0;
}
5.TCP编程
API函数介绍
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:请求与服务器建立连接
参数:
参数1:
int sockfd:客户端的套接字,使用套接字与服务端建立连接
参数2:
const struct sockaddr *addr:服务端的ip、prot信息,客户端要和哪个服务器建立连接
参数3:
socklen_t addrlen:结构体的大小
返回值:
成功:返回0
失败:返回-1
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);//当调用后自动监听(查看是否有客户端连接)//用于能够检测到客户端连接
功能:监听等待客户端连接,如果有客户端的连接只会把客户端的连接存储起来(会创建一个监听等待队列,只要有客户端来进行连接,都会放在监听等待队列中)----
能够一直查看服务器程序自己的信息,是否有客户端连接,且之后套接字只能用于监听,不能用于与客户端进行通信
参数:
参数1:
int sockfd:要进行监听的套接字,就是之前绑定了服务器ip、port的套接字,表示要监视哪个套接字是否有客户端连接
参数2:
int backlog:最多同时能够存储多少个客户端
返回值:
成功:返回0
失败:返回-1
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t
*addrlen);
功能:
//服务器同意连接请求(从等待队列中取出一个客户端连接请求,建
立连接,如果监听队列没有连接请求,阻塞等待监听队列有连接请
求)---一定要接收一个连接请求
参数:
参数1:
int sockfd:监听套接字,从哪个监听中取出连接
参数2:
struct sockaddr *addr:用于存储客户端的ip、port,不需要写NULL
参数3:
socklen_t *addrlen:结构体大小
返回值:成功:返回与连接成功的客户端通信的套接字
失败:返回-1
ssize_t send(int sockfd, const void *buf, size_t len,int flags);
参数:
参数4:
int flags:标志,选项
0:阻塞
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
参数4:
int flags:标志,选项
0:阻塞
客户端代码
//作为tcp客户端进行通信(先发再收)
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
//1、创建套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);//选择TCP协议
if(sockfd < 0)
{
printf("socket create error\n");
return -1;
}
//2、绑定套接字
//bind(需要信息绑定套接字,为套接字添加的源信息,信息结构体的大小);
struct sockaddr_in clientaddr;
clientaddr.sin_family = AF_INET;//源信息类型
clientaddr.sin_port = htons(12000);
clientaddr.sin_addr.s_addr = inet_addr("192.168.124.80");
if( bind(sockfd,(struct sockaddr *)&clientaddr,sizeof(clientaddr)) < 0 )
{
printf("bind error\n");
return -1;
}
//3、与服务器建立链接,发送连接请求
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;//源信息类型
serveraddr.sin_port = htons(9999);
serveraddr.sin_addr.s_addr = inet_addr("192.168.124.80");
if( connect(sockfd,&serveraddr,sizeof(serveraddr)) < 0 )
{
printf("connect error\n");
}
else
{
printf("connect ok\n");
}
char buf[20];
while(1)
{
//write()/send();
//send
memset(buf,0,20);
fgets(buf,20,stdin);
write(sockfd,buf,20);
memset(buf,0,20);
read(sockfd,buf,20);
printf("%s\n",buf);
}
close(sockfd);
return 0;
}
服务端代码
//tcp服务端,与客户端进行通信
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
//1、创建套接字
int serverfd = socket(AF_INET,SOCK_STREAM,0);//创建了tcp的套接字
//2、绑定套接字
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9999);
serveraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
bind(serverfd,&serveraddr,sizeof(serveraddr));
//3、监听套接字----查看哪个ip、port有没有客户端连接
//把客户端连接先暂存起来
listen(serverfd,3);
printf("listen ok\n");
//4、同意连接,把客户端已经发送过来的连接请求(暂存的连接),取出同意
int sockfd = accept(serverfd,NULL,NULL);
printf("accept ok\n");
//连接建立好,可以通信
char buf[20];
while(1)
{
memset(buf,0,20);
read(sockfd,buf,20);
if(strcmp(buf,"hehehe\n") == 0)
{
write(sockfd,"sb",3);
}
else
{
write(sockfd,buf,20);
}
}
close(sockfd);//关闭与某个连接的客户端通信
close(serverfd);//关闭监听
}