1.网络体系结构
OSI 七层模型
TCP/IP 四层模型
网络体系结构:网络的分层结构及每层使用的协议集合
(1)OSI(Open System Interconnection)七层模型:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
7.应用层 //FTP、E-mail
6.表示层 //数据格式定义,数据转换加密
5.会话层 //建立通信进程的逻辑名字与物理名字之间的联系
4.传输层 //差错处理、恢复,流量控制,提供可靠的数据传输
3.网络层 //数据分组,路由选择
2.数据链路层 //数据组成可发送和接收的帧
1.物理层 //传输物理信号、接口、信号形式、速率
http //超文本传输协议 浏览网页
ftp //文件传输协议
dns //域名解析协议 www.baidu.com //域名 110.242.68.3 //服务器IP地址 域名和地址 对应
www.net.cn //注册域名的网址
dhcp //动态获取IP地址
smtp //简单邮件传输协议
tcp
udp
icmp //ping命令 使用的就是icmp协议
ip
arp //将ip地址解析电脑的物理地址 MAC地址
(2)TCP/IP四层模型(应用层、传输层、网络层、网络接口层)
4.应用层
ftp (File Transfer Protocol) //文件传输协议
http(Hyper Text Transfer Protocol) //超文本传输协议
DNS (Domain Name Server) //域名解析协议
DHCP(Dynamic Host Configuration Protocol) //动态获取IP地址
SMTP(Simple Mail Transfer Protocol) //简单邮件传输协议
3.传输层
实现了应用程序间端对端的通信
TCP
UDP
2.网络层
在传输数据的过程为数据包选择路由
IP
ICMP //ping命令使用的icmp协议
///
1.网络接口
ARP //将IP解析为对应物理地址MAC
将二进制流转换成数据帧,进行发送,数据帧是网络传输的最资本单元
网络管理相关命令(重点)***********
//如何查看自己windows电脑的IP地址
ipconfig //查看windows IP地址
那么,我们Linux系统里面,如何查看自己的IP地址,命令 ifconfig
1) ifconfig : 查看或者设置ip地址
ifconfig 查看ip地址
sudo ifconfig eth0 192.168.31.3 //设置ip地址
sudo service network-manager restart //重启网络服务
2) ping 查看对方主机是否在线
//TTL 初始化值 64 128
TTL ? 计算通信过程经过的路由的个数, 每经过一个路由ttl减1, 直到减到0, 数据发送失败
TTL 值是 50 ----> 中间经过的路由个数 64 - 50 = 14 个路由
3) nc (nc是netcat的简写,有着网络界的瑞士军刀美誉) 模拟 tcp服务器 tcp客户端 udp 服务器、客户端
如果没有安装的话(sudo apt-get install netcat)
主要功能是网络测试(测试端口使用情况,测试网络)
启动tcp服务器端, 使用端口号 6666
nc -l 6666 (6666 端口号) -l 启动tcp服务器
启动tcp客户端
nc 127.0.0.1 6666 (要写服务器的ip地址和端口号)
127.0.0.1 (环回地址 专门用来做网络程序测试用的 自发自收)
网络通信(两方 收 发)
启动udp接收端
nc -ul 9999 (1 - 65535 之间)
启动udp发送端
nc -u 127.0.0.1 4444
///
2.linux下网络通信又称为socket编程,socket通信
2.1 IP地址(网络中的地址)
//dos端查看自己的IP地址
Windows查看自己的IP地址:windows + r ---> cmd ----> ipconfig
Ubuntu中查看IP地址:ifconfig
ping 192.168.0.5//查看对方主机是否在线
IP地址是主机要与其他机器通信必须具有的一个IP地址
1、 IP地址的两种表示方式
1) 点分制 ("192.168.1.21" 字符串)
2) 整型 0xC0A80115 //4个字节
一个十六进制位 ----> 4个二进制位 4bit
8*4 ---> 32位
1个字节 8bit
32 / 8 = 4 ---> 4个字节就可以存储整型的IP地址
网络通信中使用整型IP地址
//两个函数,实现点分形式 和 整型形式 IP地址相互转换
点分形式的IP地址转为整型 inet_addr
整型的IP地址转换为点分 inet_ntoa
2、 inet_addr函数 //将点分形式的ip地址转换成整型的ip地址
typedef unsigned int in_addr_t;
in_addr_t inet_addr(const char *cp);
功能:将点分制的ip地址转成整型ip地址
const char *cp //保存的是 "192.168.1 21" 字符串的首地址,被转换的点分IP地址
返回值:返回值就是整型的IP地址
inet_addr例子
#include "my.h"
//in_addr_t == unsigned int
int main(int argc, const char *argv[])
{
//将点分形式的IP地址转换为整型的IP地址 "192.168.1.21" ---> 0x1501A8C0
in_addr_t addr = inet_addr("192.168.1.21");
printf("整型IP地址是%X\n",addr);//%X以十六进制的格式输出
return 0;
}
//打印输出
整型IP地址是1501A8C0
3、 inet_ntoa函数
char *inet_ntoa(struct in_addr in);
struct in_addr
{
in_addr_t s_addr; //结构体成员变量就是 整型的IP地址
};
功能:将整型的IP地址转换成点分格式的IP地址)
返回值:char* 点分形式IP地址字符串的首地址"192.168.1.21"
inet_ntoa例子
//打印输出:
#include "my.h"
//in_addr_t == unsigned int
int main(int argc, const char *argv[])
{
//将点分形式的IP地址转换为整型的IP地址 "192.168.1.21" ---> 0x1501A8C0
in_addr_t addr = inet_addr("192.168.1.21");
printf("整型IP地址是%X\n",addr);//%X以十六进制的格式输出
//将整型的IP地址转换为点分形式的IP地址
struct in_addr in; //定义一个结构体保存整型的IP地址
in.s_addr = addr;//结构体变量的成员变量 来保存整型的IP地址
char* p =(char*)inet_ntoa(in); //函数的参数,需要的是结构体
printf("点分形式IP地址是%s\n",p);
return 0;
}
4、 字节序转序
字节序
大端模式:高位字节存储在低位地址中,低位字节存储在高位地址中(高对低,低对高)
小端模式:高位字节存储在高位地址中,低位字节存储在低位地址中(高对高,低对低)
高位字节
低位字节
高位地址
低位地址
int a = 0x12345678
0x12 高位字节
0x78 低位字节
0xaaaa0000 低位地址
0xaaaa0003 高位地址
---测试当前PC字节序 (通常为小端模式)
#include "my.h"
int main(int argc, const char *argv[])
{
int a = 0x12345678;
char* p = (char*)&a;
printf("*(p+0) is %x\n",*(p+0)); //也可以不用循环,直接判断p+0 低位地址中存的内容是0x12 还是 0x78来判断
int i;
//用字符指针,将整型变量a每个字节存储的内容打印输出
for(i = 0; i < 4; i++)
{
printf("*(p+%d) is %X\n",i,*(p+i));
}
return 0;
}
//打印输出结果:
*(p+0) is 78 // p+0 低位地址 0x78 低位字节
*(p+1) is 56 // p+1
*(p+2) is 34
*(p+3) is 12 // p+3 高位地址 0x12 高位字节
所以当前主机字节序是小端模式
(*****)网络字节序和主机字节序不同点,及相关转换函数。
主机字节序: 通常为小端模式,高位字节存储在高位地址中,低位字节存储在低位地址中(高对高,低对低)
网络字节序:大端模式,高位字节存储在低位地址中,低位字节存储在高位地址中(高对低,低对高)
n network 网络
h host 主机
s short
l long
to //转
//主机字节序转为网络字节序
htons() //将主机字节序的短整型转换为网络字节序
htonl() //将主机字节序的长整型转换为网络字节序
//网络转为主机字节序
ntohs() //将网络字节序短整型转换为主机字节序
ntohl() //将网络字节序的长整型转换为主机字节序ntohl()例子
#include "my.h"
int main(int argc, const char *argv[])
{
int a = 0x12345678;
char* p = (char*)&a;
printf("*(p+0) is %X\n",*(p+0));// 0xaaaa0000 - 0xaaaa0003 0x78 0x56 0x34 0x12
//打印结果是 78
int b = htonl(a);//将主机字节序的变量a转为网络字节序存储在变量b中
char* q = (char*)&b;
printf("*(q+0) is %X\n",*(q+0));// 0xbbbb0000 - 0xbbbb0003 0x12 0x34 0x56 0x78
//打印结果是 12
return 0;
}
///
3. UDP通信
3.1 关于UDP通信
(1)什么是UDP?
udp是一个面向无连接、不可靠的传输层协议
(2)UDP通信协议位于哪一层?
传输层
(3)UDP通信的优点和缺点
优点: 传输速度快
缺点: 传输过程中,不可靠,容易丢失数据
(4)应用场合:
UDP通常用于传输较大的文件
3.2 UDP通信,接收端和发送端
分为接收端 和 发送端
接收端过程(4歩)
(1) 创建一个socket数据报套接字SOCK_DGRAM
(2) 绑定自己的IP地址和端口号
(3) 接收数据
(4) 关闭套接字 close(sockfd);
1 接收端
+++++++++++++++++++++++++++++++++++++++++++++++++
(1)创建socket数据报套接字
socket函数头文件及函数原型
##功能:创建一个socket套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//调用:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
参数说明:
int domain //网络的通信协议 AF_INET IPv4
int type //套接字的类型
SOCK_DGRAM 数据报套接字
int protocol //0 代表自动默认匹配相关协议
返回值:
成功:非负套接字描述符 ,socket套接字是一个特殊的文件描述符
失败: -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(2) 绑定自己的ip地址和端口号(将进程与IP地址和端口号关联)
端口号:是用来识别应用程序的,如果A程序绑定55555端口号,
那么网络里面来了一包55555的端口号数据,就发给A程序
int bind(int sockfd, struct sockaddr *addr, int addrlen);
#功能:绑定自己的IP地址和端口号
struct sockaddr_in myaddr = { 0 };//用来保存自己的IP地址和端口号
myaddr.sin_family = AF_INET;//family协议选择IPv4
myaddr.sin_port = htons(4444);//端口号
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //"127.0.0.1"IP地址是主机环回地址
myaddr 类型是 struct sockaddr_in
&myaddr 类型是 struct sockaddr_in*
函数的参数需要的是 struct sockaddr *
所以函数的参数需要将&myaddr 强制类型转换 (struct sockaddr*)&myaddr
//为什么我们定义的结构体类型和函数参数上的类型不一样
bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr))
参数说明:
int sockfd //上一步,socket函数的返回值
struct sockaddr *addr //用来保存自己的IP地址和端口号的结构体的首地址
int addrlen //IP地址和端口号的长度
返回值:
成功: 0
失败: -1
//struct sockaddr 此结构体在网络通信中主要保存 ip地址 和 端口号
//此结构体对IP和端口号赋值很麻烦
struct sockaddr
{
short sa_family; //AF_INET (tcp/ip通信)
char sa_data[14]; //字符数组有14个字节 1-2字节 端口号
3-6 ip地址 7-14 预留
};
因为 struct sockaddr 存储数据不方便,所以,linux又定义了下面这个结构体
struct sockaddr_in
{
short sin_family; //AF_INET //2
short sin_port; //端口号 //2
struct in_addr sin_addr; //struct in_addr 4 s_addr //IP地址
unsigned char sin_zero[8]; //预留
};
struct in_addr {
in_addr_t s_addr;
};
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
因为struct scokaddr 赋值麻烦,所以又定义了一个 struct sockaddr_in ,专门用来赋值的,网络通信时,强制
将struct sockaddr_in转换成struct sockaddr
端口号:是用来识别应用程序的,如果A程序绑定55555端口号,那么网络里面来了一包55555的端口号数据,就发给A程序
有些应用的端口号是固定的
ftp 21 File Transfer Protocol
http 80 HyperText Transfer Protocol)
DNS 53 Domain Name System
DHCP 67 Dynamic Host Configuration Protocol
我们写的应用程序尽量用 > 1023的端口号,
127.0.0.1 环回地址(自发自收,用于网络程序测试用的)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(3) 接收数据(阻塞的方式接收数据)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
#功能:阻塞 接收数据
//调用
//不想保存对方的IP地址和端口号
char buf[100] = { 0 };//用来保存接收到的数据
recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
//想要保存对方的IP地址和端口号
struct sockaddr_in youaddr = { 0 };//用来保存对方的IP地址和端口号
int len = sizeof(youaddr);
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&youaddr, &len);
参数说明:
int sockfd //第一步socket函数的返回值
void *buf //接收到的数据存放的位置
size_t len //接收数据的最大字节数
int flags //0 阻塞接收
struct sockaddr *src_addr //用来保存对方的IP地址和端口号
socklen_t *addrlen //对方iP地址和端口号的长度
返回值:
成功: 实际接收字节数
失败: -1
(4) 关闭socket套接字
close(sockfd);
///
2 发送端
1. 创建一个数据报套接字socket() SOCK_DGRAM
(发送的前提条件是你已经知道了接收方的电话号码(也就是接收方的IP地址和端口号))
2. 发送数据 sendto()
3. 关闭套接字close()
(1)创建一个socket数据包套接字
同接收端
(2)指定接收方的IP地址和端口号
struct sockaddr_in toaddr; //用来保存接收方的IP地址和端口号
//发送方是当前的他, toaddr里面存的是对方的手机号即 她的手机号
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(6666);
//toaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
toaddr.sin_addr.s_addr = inet_addr("192.168.31.179");
(3) 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
char buf[100] = "hello";
//在调用sendto函数的时候,数据发送给谁,取决于 toaddr里面装的是谁的IP地址和端口号
sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr*)&toaddr, sizeof(toaddr));
功能:发送数据
函数说明:
int sockfd //socket函数的返回值
const void *buf //发送数据存放的位置
size_t len //实际发送的字节数
int flags // 通常为0
const struct sockaddr *dest_addr //接收方的IP地址和端口号
socklen_t addrlen //IP地址和端口号的长度
返回值:
成功:实际发送的字节数
失败: -1
(4)关闭套接字
close(sockfd)
recv.c
#include "my.h"
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };//用来保存接收到的数据
//1.创建一个数据报套接字 AF_INET IPv4 SOCK_DGRAM 数据报套接字 0 默认匹配相关协议
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("sockfd failed!!\n");
exit(-1);
}
printf("sockfd is %d\n",sockfd);
//2.绑定自己的IP地址和端口号
struct sockaddr_in myaddr = { 0 };//用来保存接收端自己的IP地址和端口号
myaddr.sin_family = AF_INET;//IPv4
myaddr.sin_port = htons(6666);//将主机字节序的端口号转为网络字节序
myaddr.sin_addr.s_addr = inet_addr("192.168.31.173");//127.0.0.1主机环回地址
int ret = bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr));
if(ret == -1)
{
perror("bind failed!!\n");
exit(-1);
}
//3.阻塞等待接收数据
recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);//第四个参数0代表,阻塞接收数据
printf("recv is %s\n",buf);
//4.关闭套接字
close(sockfd);
return 0;
}
用nc命令来模拟发送端
nc -u 127.0.01 6666
nc -u 192.168.31.173 6666
//send.c
#include "my.h"
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };
struct sockaddr_in toaddr = { 0 };//用保存接收方的IP地址和端口号
//toaddr里面存的是谁的IP地址和端口号,就是给谁发送数据
//1.创建一个数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("socket failed");
exit(-1);
}
//发送之前,提前知道接收当的IP地址和端口号
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(6666);
toaddr.sin_addr.s_addr = inet_addr("192.168.31.173"); //toaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//2.发送数据,但是发送数据之前,你要提前知道接收方的IP地址和端口号
gets(buf);
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&toaddr,sizeof(toaddr));
//3.关闭套接字
close(sockfd);
return 0;
}
gcc -o send send.c
//修改后的recv.c
recv.c 通过recvfrom的第四个参数,获取到发动方的IP地址和端口号
#include "my.h"
int main(int argc, const char *argv[])
{
char buf[100] = { 0 };//用来保存接收到的数据
//1.创建一个数据报套接字 AF_INET IPv4 SOCK_DGRAM 数据报套接字 0 默认匹配相关协议
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("sockfd failed!!\n");
exit(-1);
}
printf("sockfd is %d\n",sockfd);
//2.绑定自己的IP地址和端口号
struct sockaddr_in myaddr = { 0 };//用来保存接收端自己的IP地址和端口号
myaddr.sin_family = AF_INET;//IPv4
myaddr.sin_port = htons(6666);//将主机字节序的端口号转为网络字节序
myaddr.sin_addr.s_addr = inet_addr("192.168.31.179");//127.0.0.1主机环回地址
//myaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 这个宏,可以自动获取本机的IP地址
//接收端,指定本机自己的IP地址有两种方法
//(1) 直接用inet_addr函数指定具体的IP地址myaddr.sin_addr.s_addr = inet_addr("192.168.31.179");//127.0.0.1主机环回地址
//(2) 用INADDR_ANY这个宏,自动获取本机的IP地址 myaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 这个宏,可以自动获取本机的IP地址
int ret = bind(sockfd, (struct sockaddr*)&myaddr, sizeof(myaddr));
if(ret == -1)
{
perror("bind failed!!\n");
exit(-1);
}
//3.阻塞等待接收数据
struct sockaddr_in youaddr = { 0 };//用来保存发送方的IP地址和端口号
int len = sizeof(youaddr);
while(1)
{
//如果将第四个参数和第五个参数赋值为NULL,那么我的心里,不想知道给发送消息的那个人的IP地址和端口号
//recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);//第四个参数0代表,阻塞接收数据
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&youaddr, &len);//第四个参数0代表,阻塞接收数据
//inet_ntoa 为了将youaddr里面的保存的整型的IP地址转换为点分
//ntohs为了将youaddr里面的网络字节序的端口号转换为主机字节序的端口号进行打印
printf("fromIP:%s-%d %s\n",(char*)inet_ntoa(youaddr.sin_addr),ntohs(youaddr.sin_port),buf);
memset(buf, 0, sizeof(buf));
}
//4.关闭套接字
close(sockfd);
return 0;
}
/send.c//
#include "my.h"
int main(int argc, const char *argv[])
{
char buf[100] = "hello";//即将发送的数据
//1.创建一个数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("scoket failed");
exit(-1);
}
//2.给接收端发送数据,发送数据的前提条件,是已经知道了接收方的IP地址和端口号
struct sockaddr_in toaddr = { 0 };//用来保存接收端的IP地址和端口号
toaddr.sin_family = AF_INET;
toaddr.sin_port = htons(6666);
toaddr.sin_addr.s_addr = inet_addr("192.168.31.179");
while(1)
{
printf("请输入要发送的话:\n");
gets(buf);
sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr*)&toaddr,sizeof(toaddr));
}
//3.关闭套接字
close(sockfd);
return 0;
}
华清远见的学习是由浅入深,让我这样一个小白也渐渐熟练了起来!加油!