网络:
可以用来:数据传输 、数据共享
1. 网络协议模型:
1. OSI协议模型:
|-------|-------------------------|
| 应用层 | 实际收发的数据 |
| 表示层 | 发送的数据是否加密 |
| 会话层 | 是否建立会话连接 |
| 传输层 | 数据传输的方式(数据包,流式) |
| 网络层 | 数据的路由(如何从一个局域网到达另一个局域网) |
| 数据链路层 | 局域网下如何通信 |
| 物理层 | 物理介质的连接 |
2. TCP/IP协议模型:
|-------|-------------------|
| 应用层 | 传输的数据 |
| 传输层 | 传输的方式 |
| 网络层 | 数据如何从一个台主机到达另一台主机 |
| 网络接口层 | 物理介质的连接 |
1. 应用层:
例如有:HTTP 超文本传输协议
HTTPS
FTP 文件传输协议
TFTP 简单文本传输协议
SMTP 邮件传输协议
MQTT
TELNET
...
2. 传输层:
**UDP:**用户数据报协议
**特点:**1. 实现机制简单
-
资源开销小
-
不安全不可靠
**TCP:**传输控制协议
**特点:**1. 实现机制复杂
-
资源开销大
-
安全可靠
3. 网络层:
IPv4
**IP地址:**唯一网络中一台主机的标号
**IP地址:**网络位 + 主机位
子网掩码:用来标识IP地址的网络位和主机位
子网掩码是1的部分表示IP地址的网络位
子网掩码是0的部分表示IP地址的主机位
**网段号:**网络位不变,主机位全位0,表示网段号
**广播地址:**网络位不变,主机位全为1,表示广播地址
IP地址类型:
A类:
1.0.0.0 - 126.255.255.255
子网掩码:255.0.0.0
管理超大规模网络
私有IP地址:10.0.0.0 - 10.255.255.255
B类:
128.0.0.0 - 191.255.255.255
子网掩码:255.255.0.0
管理大中规模型网络
私有IP地址:172.16.0.0 - 172.31.255.255
C类:
192.0.0.0 - 223.255.255.255
子网掩码:255.255.255.0
管理中小规模型网络
私有IP地址:192.168.0.0 - 192.168.255.255
D类:
224.0.0.0 - 239.0.0.0
用于组播
E类:
240.0.0.0 - 255.255.255.255
用于实验
4. UDP编程:
socket套接字(全双工)编程:
**发端:**socket -> sendto -> close
**收端:**socket -> bind -> recvfrom -> close
1. 发端
1. socket:
cpp
int socket(int domain, int type, int protocol);
**功能:**创建一个用来通信的文件描述符
参数:
**domain:**使用的协议族 AF_INET(IPv4协议族)
**type:**套接字类型
**SOCK_STREAM:**流式套接字
**SOCK_DGRAM:**数据报套接字
**SOCK_RAW:**原始套接字
**protocol:**协议
默认为0;
返回值:
成功返回文件描述符
失败返回-1
2. sendto:
cpp
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
**功能:**利用套接字向指定地址发送数据信息
参数:
**sockfd:**套接字文件描述符
**buf:**发送数据空间首地址
**len:**发送数据的长度
**flags:**属性默认为0
**dest_addr:**目的地址信息存放的空间首地址
**addrlen:**目的地址的长度
cpp
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
返回值:
成功返回实际发送字节数
失败返回-1
3. inet_addr:
cpp
in_addr_t inet_addr(const char *cp);
**功能:**将字符串IP地址转换为内存中的IP地址
4. htons:
cpp
uint16_t htons(uint16_t hostshort);
**功能:**将本地字节序转换为网络的大端字节序
练习:
- 编写程序实现从终端接收字符串发送给windows软件调试助手,并接收软件助手的回复,显示在终端屏幕上
cpp
#include "head.h"
int main(void)
{
int sockfd = 0;
ssize_t nsize = 0;
char tmpbuff[1024] = {0};
struct sockaddr_in recvaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("fail to socket");
return -1;
}
gets(tmpbuff);
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(50000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.1.162");
bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(&recvaddr));
nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
if(nsize == -1)
{
perror("fail to sendto");
return -1;
}
printf("成功发送 %ld 字节!\n", nsize);
memset(tmpbuff, 0, sizeof(tmpbuff));
nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)&recvaddr, (socklen_t *)sizeof(&recvaddr));
printf("%s\n",tmpbuff);
close(sockfd);
return 0;
}
2. 收端
1. recvfrom:
cpp
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
**功能:**从套接字中接收数据
参数:
**sockfd:**套接字文件描述符
**buf:**存放数据空间首地址
**flags:**属性,默认为0
**src_addr:**存放IP地址信息的空间首地址
**addlen:**存放接收到IP地址大小空间的首地址
返回值:
成功返回实际接收字节数
失败返回-1
2. 修改虚拟机到桥接模式:
点击"虚拟机"

点击"设置"

点击"网络适配器"
选择"桥接模式"
点击"确定"

3. 将网卡桥接到无线网卡:
点击"编辑"

点击"虚拟网络编辑器"

点击"更改设置"

4. 在Ubuntu中重启网络服务:
bash
sudo /etc/init.d/networking restart
5. 通过ifconfig查看虚拟机IP地址
6. bind:
cpp
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
**功能:**在套接字上绑定一个IP地址和端口号
参数:
**sockfd:**套接字文件描述符
**addr:**绑定IP地址空间首地址
**addrlen:**绑定IP地址的长度
返回值:
成功返回0
失败返回-1
3. UDP需要注意的细节点:
-
UDP是无连接,发端退出,收端没有任何影响
-
UDP发送数据上限,最好不要超过1500个字节
-
UDP是不安全不可靠的,连续且快速的传输数据容易产生数据丢失
4. wireshark
可以通过wireshark抓包工具来查看收发的数据
操作流程:
- 打开wireshark:
bash
sudo wireshark
-
选择抓取数据包的网卡:any
-
执行通信的代码
-
停止通信
-
设定过滤条件
ip.addr == IP地址 :通过IP地址查找
udp :通过传输方式udp查找
tcp :通过传输方式tcp查找
udp.port == 端口号:通过端口号查找
5. UDP包头长度:8个字节
源端口号(2个字节)
目的端口号(2个字节)
长度(2个字节)
检验和(2个字节)
练习:
要求在不同主机中编写两个程序,实现全双工聊天功能
-
进入软件后接收当前用户的昵称
-
显示的格式为对方用户昵称 (对方IP:对方端口) > 接收到的内容
-
用户输入".quit"退出聊天
-
网络通信时收发结构体
cpp
struct person
{
char name[32];
char text[512];
};
cpp
#include "head.h"
int sockfd = 0;
ssize_t nsize = 0;
struct sockaddr_in tmpaddr;
struct sockaddr_in sendaddr;
socklen_t addrlen = sizeof(tmpaddr);
struct person
{
char name[32];
char text[512];
};
pthread_t tid_recv;
pthread_t tid_send;
void *RecvInfo(void *arg)
{
struct person user;
while(1)
{
memset(&user, 0, sizeof(user));
nsize = recvfrom(sockfd, &user, sizeof(user), 0, (struct sockaddr *)&tmpaddr, &addrlen);
if(nsize == -1)
{
perror("fail to recvfrom");
return NULL;
}
printf("%s %s : %d > %s\n", user.name, inet_ntoa(tmpaddr.sin_addr), ntohs(tmpaddr.sin_port), user.text);
if(!strcmp(user.text, ".quit"))
{
break;
}
}
pthread_cancel(tid_send);
return NULL;
}
void *SendInfo(void *arg)
{
struct person user;
while(1)
{
memset(&user, 0, sizeof(user));
scanf("%s", user.name);
scanf("%s", user.text);
nsize = sendto(sockfd, &user, sizeof(user), 0, (struct sockaddr *)&sendaddr, sizeof(sendaddr));
if(nsize == -1)
{
perror("fail to sendto");
return NULL;
}
printf("success send %ld byte\n", nsize);
if(!strcmp(user.text, ".quit"))
{
break;
}
}
pthread_cancel(tid_recv);
return NULL;
}
int main(void)
{
struct sockaddr_in recvaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)
{
perror("fail to socket");
return -1;
}
recvaddr.sin_family = AF_INET;
recvaddr.sin_port = htons(30000);
recvaddr.sin_addr.s_addr = inet_addr("192.168.1.153");
bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
sendaddr.sin_family = AF_INET;
sendaddr.sin_port = htons(30000);
sendaddr.sin_addr.s_addr = inet_addr("192.168.1.152");
pthread_create(&tid_recv, NULL, RecvInfo, NULL);
pthread_create(&tid_send, NULL, SendInfo, NULL);
pthread_join(tid_recv, NULL);
pthread_join(tid_send, NULL);
close(sockfd);
return 0;
}