1.Linux下的网络编程
(1)目的
不同主机,进程间通信。
(2)解决的问题
1. 主机与主机之间物理层面必须互联互通。
2. 进程与进程在软件层面必须互联互通。
(3) 网络协议
网络通信标准。
OSI七层模型:开放系统互联模型(open system interconnect)
理论模型:
不同体系结构设备间,网络通信的通信标准。应用层:要传输的数据信息,如文件传输,电子邮件等
表示层:数据加密,解密操作,压缩,解压缩
会话层:建立数据传输通道, ---》会话
传输层:传输的方式 UDP TCP 端口号
网络层:实现数据路由,路径规划 路由器 ip
数据链路层:封装成帧,点对点通信(局域网内通信),差错检测 交换机 ARP
物理层:定义物理设备标准、电气特性,比如网线,光纤等传输介质 比特流 bit 0 1
TCP/IP模型:应用模型五层:
1. 应用层
概念:
应用层是网络体系中最高的一层,直接为用户的应用程序(如浏览器、电子邮件客户端、文件传输程序)提供网络服务。它定义了数据格式和通信规则,以便不同主机上的应用进程能够交换信息。
主要协议及用法:
HTTP:
概念:超文本传输协议,是万维网的数据通信基础。
用法:客户端(浏览器)向服务器发送请求,服务器响应后返回网页、图片、视频等资源。是一种明文传输的协议。
HTTPS:
概念:安全的超文本传输协议,在HTTP的基础上加入了SSL/TLS加密层。
用法:用于需要安全传输的场景,如网上支付、登录认证等。浏览器地址栏会显示锁形标志。
FTP:
概念:文件传输协议,用于在网络上进行双向文件传输。
用法:用户通过FTP客户端连接到FTP服务器,可以上传、下载、删除、重命名文件。需要建立控制连接(TCP 21端口)和数据连接(TCP 20端口)。
TFTP:
概念:简单文件传输协议,是一个轻量级的FTP。
用法:常用于局域网内或设备(如路由器、交换机)的固件升级和配置备份/恢复。它使用UDP 69端口,没有复杂的认证和交互命令,速度快但不可靠。
MQTT:
概念:消息队列遥测传输协议,是一种轻量级、基于发布/订阅模式的物联网通信协议。
用法:适用于网络带宽低、设备资源有限的场景。例如,智能家居中的传感器将数据(如温度)发布到一个主题(Topic),服务器或其他设备订阅该主题即可接收信息。
DNS:
概念:域名系统,是互联网的“电话簿”。
用法:将人类可读的域名(如 www.baidu.com)解析为机器可读的IP地址(如 180.101.49.12)。当你在浏览器输入网址时,第一步就是发起DNS查询。
2. 传输层
概念:
传输层负责为两台主机上的应用程序提供端到端的通信服务。它通过端口号来标识不同的应用程序,从而实现数据流的定向(哪个数据包是给哪个程序的)。
主要协议及用法:
TCP:
概念:传输控制协议,是一种面向连接的、可靠的、基于字节流的协议。
用法:可靠性:通过三次握手建立连接、确认机制、重传机制、流量控制和拥塞控制来保证数据不丢失、不重复、按序到达。
适用场景:要求数据完整性的应用,如网页浏览(HTTP)、文件传输(FTP)、电子邮件(SMTP)。
UDP:
概念:用户数据报协议,是一种无连接的、不可靠的协议。
用法:高效性:发送数据前不需要建立连接,没有TCP的那些复杂机制,因此延迟更低、速度更快。
适用场景:对实时性要求高、可容忍少量数据丢失的应用,如视频会议、语音通话、在线直播(QUIC/HTTP3)、DNS查询、TFTP。
3. 网络层
概念:
网络层负责将数据包从源主机经过多个网络(路由选择)传输到目标主机。它通过IP地址来标识网络中的设备,实现主机到主机的通信。
主要协议及用法:
IP协议:
概念:网际协议,是网络层最核心的协议,定义了数据包(称为IP数据报)的结构和寻址方法。
IPv4:
用法:使用32位地址(如 192.168.1.1),地址空间即将耗尽。是当前互联网的主流协议。
IPv6:
用法:使用128位地址(如 2001:0db8:85a3::8a2e:0370:7334),提供了几乎无限的地址空间,同时提供了更好的安全性和效率,是未来的发展趋势。
ICMP:
概念:互联网控制消息协议,用于网络设备之间发送控制消息(如错误报告、查询信息)。
用法:常用的 ping 命令和 tracert(Windows)/ traceroute(Linux)命令就是基于ICMP协议,用来测试网络连通性和诊断网络故障。
4. 数据链路层
概念:
数据链路层负责在同一个局域网内,通过MAC地址(物理地址)在设备之间传输和校验数据帧。它负责将网络层交下来的IP数据包组装成帧,并在相邻网络节点(如交换机、网卡)之间透明地传输。
主要协议及用法:
ARP:
概念:地址解析协议。
用法:在局域网内,通过目标设备的IP地址来查询其MAC地址。例如,计算机A想发给计算机B,它只知道B的IP,就会在局域网内广播一个ARP请求:“谁的IP是B的IP?请告诉A你的MAC地址”。B收到后会单播回复自己的MAC地址。A随后将B的IP和MAC对应关系缓存到本地的ARP表中。
PPP:点对点协议,常用于宽带拨号上网(ADSL)。
Ethernet:以太网协议,是当前最主流的局域网技术。
5. 物理层
概念:
物理层是整个网络体系的基础,它规定了为传输数据所需要的物理和电气特性。它负责在物理介质上透明地传输比特流(即0和1的信号),不关心数据的含义和结构。
eg:
如果把整个网络通信比作寄送快递:
应用层:就是你写的信的内容。
传输层:将信装入信封,写上收件人和寄件人的姓名(端口号),并选择快递服务(如顺丰-可靠/TCP,或普通信件-不可靠/UDP)。
网络层:在信封上写上收件人和寄件人的家庭地址(IP地址)。
数据链路层:负责在同一个小区或街道内,根据门牌号(MAC地址) 将信件投递到下一个中转站(如交换机、路由器)。
物理层:就是运送信件的卡车、公路、铁路和飞机,以及规定信件包装箱的材质、尺寸。它只负责把“箱子”从一个地方运到另一个地方,不关心箱子里装的是什么。
四层:应用层、传输层、网络层、网络接口层
(4)IP协议
网络层:
IP协议
192.168.1.128
IPv4 32位
IPv6 128位
eg:
192.168.1.140 (用户表示形式) 点分十进制
11000000 10101000 00000000 01000011 (计算机存储形式) 32bits
IP地址 = 网络位 + 主机位
eg:
192.168.0.121/24
24:网络位的位数
网络位:该IP地址位于哪个网段(局域网)内
主机位:这个网段(局域网)第几台主机子网掩码:
如:255.255.255.0
11111111.11111111.11111111.00000000
用来区分IP地址的网络位和主机位,搭配IP地址使用。
子网掩码是1的部分对应IP地址的网络位
子网掩码是0的部分对应IP地址的主机位192.168.1.0
网段号:
IP地址网络位不变,主机位全为0,则为该IP地址的网段号
192.168.1.3
255.255.0.0
192.168.0.0
位于
192.168.1.0 网段内(网段内的IP能直接通信)
192.168.1.3
255.0.0.0广播号:
192.168.1.255
IP地址网络位不变,主机位全为1,则为该IP地址的广播号
192.168.1.3
255.255.255.0
广播号:
192.168.1.255(向广播号发送信息,所有局域网内IP都能收到此信息)
eg:
feiQ VNC
192.168.1.255
网关地址:
192.168.1.1
IP地址的划分:
(1)A类地址:
范围:1.0.0.0 - 126.255.255.255
子网掩码:255.0.0.0 126*2^24
用于管理大规模网络
私有IP地址:10.0.0.0 - 10.255.255.255
127.0.0.0 回环地址
(2)B类地址:
范围:128.0.0.0 - 191.255.255.255
子网掩码:255.255.0.0 2^16
管理大中规模网络
私有IP地址:172.16.0.0 - 172.31.255.255
(3)C类地址:
范围:192.0.0.0 - 223.255.255.255
子网掩码:255.255.255.0 2^8
管理中小规模网络
私有IP地址:192.168.0.0 - 192.168.255.255
(4)D类地址:
224.0.0.0 - 239.255.255.255
组播和广播使用
(5)E类地址:
240.0.0.0 - 255.255.255.254
用来进行实验公有IP:由电信公司直接分配,并需要付费的IP地址, 可以直接访问internet
私有IP:不能直接访问internet的ip地址
节省ip地址
(5)网络端口号
端口号:16位的整形数据(unsigned short)0-65535
端口号功能:标记同一主机上的不同网络进程分类:
1)任何TCP/IP实现所提供的服务都用1-1023之间的端口号。
http : 80
FTP: 20/21
TFPT: 69
HTTPS: 443
2)端口号从1024-49151是被注册的端口号,被IANA指定为特殊服务使用。
MQTT:1883/8883
3)从49152-65535是动态或私有端口号。
(6)网络配置
1. ping ip地址/域名
查看当前主机和IP/域名所对应的这台主机网络是否联通
ping www.baidu.com2. ifconfig
在Linux查看当前主机的IP地址
ipconfig
在Windows上查看当前主机的IP地址3. 网络配置
1)虚拟机--》设置--》网络适配器---》桥接模式
2)编辑--》虚拟网络编辑器--》更改设置--》VMnet0---》桥接至--》当前PC正在上网的网卡上--》应用--》确定
3)修改网络配置文件
sudo vim /etc/network/interfacesauto lo
iface lo inet loopbackauto ens33
iface ens33 inet dhcp4)重启网络服务
sudo /etc/init.d/networking restart
5) 测试
ping www.baidu.com
2.网络协议--UDP
(1)基本概念
UDP:传输层
用户数据报协议(User Datagram Protocol)1)网络编程模型
B/S模型:browser/server(浏览器/服务器)
1. 客户端是通用的客户端(浏览器)
2. 一般只做服务器开发
3. 客户端要加载的数据均来自服务器
C/S模型:client/server(客户端/服务端)
1. 客户端是一个专用的客户端
2. 服务器和客户端都需开发
3. 客户端可保存资源,本地加载,无需所有数据都请求服务器2)UDP编程流程
套接字:文件描述符
网络通信时,应用层可操作的端口。
(2)函数接口
int socket(int domain, int type, int protocol);
功能:创建通信的套接字
参数:
domain:网络层使用什么协议族
AF_INET:IPv4
AF_INET6:IPv6
type:规定传输层的协议
SOCK_DGRAM : UDP协议
SOCK_STREAM:TCP协议
SOCK_RAW :原始套接字
protocol :0 按照默认协议方式创建
返回值:
成功:套接字
失败:-1ssize_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:接收方的地址信息(IP+端口号)
addrlen:接收方地址的大小
返回值:
成功:实际发送的字节数
失败:-1man 7 ip ------》查询此形参 const struct sockaddr *dest_addr,
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 */
};int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:绑定自己的IP地址和端口号
参数:
sockfd:套接字
addr:需要绑定的地址
addrlen:地址大小
返回值:
成功:0
失败:-1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:从套接字上接收数据
参数:
sockfd:套接字
buf:存放接收数据的内存首地址
len:希望接收的字节数
flags:0 :按照默认方式接收(阻塞)
src_addr:发送方的地址信息
addrlen:发送发地址的指针
返回值:
成功:实际接收到的字节数
失败:-1
网络字节序:大端
主机字节序:小端uint32_t htonl(uint32_t hostlong); 主机转网络
uint16_t htons(uint16_t hostshort); 主机转网络
uint32_t ntohl(uint32_t netlong); 网络转主机
uint16_t ntohs(uint16_t netshort); 网络转主机in_addr_t inet_addr(const char *cp);
功能:
将字符串IP地址转换成二进制IP地址形式char *inet_ntoa(struct in_addr in);
功能:
将二进制ip转换成字符串
eg:客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.0.181");
while (1)
{
char buff[1024] = {0};
fgets(buff, sizeof(buff), stdin);
buff[strlen(buff) - 1] = '\0';
ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(cnt < 0)
{
perror("send error");
return -1;
}
printf("cnt = %ld\n", cnt);
}
close(sockfd);
return 0;
}
eg:服务端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_addr.s_addr = inet_addr("192.168.0.181");
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("bind error");
return -1;
}
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
while (1)
{
char buff[1024] = {0};
int cnt = recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
if(cnt < 0)
{
perror("recvform error");
return -1;
}
printf("[%s:%d]cnt = %d, buff = %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),cnt, buff);
}
close(sockfd);
return 0;
}
(3)UDP的特点
① 面向数据包
② 无需建立连接
③ 尽最大努力交付,不安全、不可靠(数据丢包、乱序)
④ 可现实一对一、一对多的传输
⑤ 机制简单,资源开销小,数据实时性高
如何避免UDP丢包:
①发送方以较慢的速度发送数据,让接收方有足够的时间处理数据
②模仿TCP的机制:应答机制
(4)例题
①使用UDP实现全双工聊天
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
struct sockaddr_in seraddr;
int init_udp_cli()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
return sockfd;
}
void *send_msg(void *arg)
{
char buff[1024] = {0};
int sockfd = *((int *)arg);
while (1)
{
fgets(buff, sizeof(buff), stdin);
ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (cnt < 0)
{
perror("sendto error");
break;
}
}
return NULL;
}
void *recv_msg(void *arg)
{
char buff[1024] = {0};
int sockfd = *((int *)arg);
while (1)
{
memset(buff, 0, sizeof(buff));
ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
if (cnt < 0)
{
perror("recvfrom error");
break;
}
printf("B-->A : %s\n", buff);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid[2];
int sockfd = init_udp_cli();
if (sockfd < 0)
{
return -1;
}
sendto(sockfd, "hello", 5, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
pthread_create(&tid[0], NULL, send_msg, &sockfd);
pthread_create(&tid[1], NULL, recv_msg, &sockfd);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
close(sockfd);
return 0;
}
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
struct sockaddr_in cliaddr;
socklen_t clilen;
int init_udp_ser()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
return sockfd;
}
void *send_msg(void *arg)
{
char buff[1024] = {0};
int sockfd = *((int *)arg);
while (1)
{
fgets(buff, sizeof(buff), stdin);
size_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&cliaddr, clilen);
if (cnt < 0)
{
perror("sendto error");
break;
}
}
return NULL;
}
void *recv_msg(void *arg)
{
char buff[1024] = {0};
int sockfd = *((int *)arg);
while (1)
{
ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
if (cnt < 0)
{
perror("recvfrom error");
break;
}
printf("A--->B: %s\n", buff);
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t tid[2];
char buff[1024] = {0};
clilen = sizeof(cliaddr);
int sockfd = init_udp_ser();
if (sockfd < 0)
{
return -1;
}
recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
pthread_create(&tid[0], NULL, send_msg, &sockfd);
pthread_create(&tid[1], NULL, recv_msg, &sockfd);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
return 0;
}
②使用UDP实现图片的传输
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
struct sockaddr_in seraddr;
int init_udp_cli()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
return sockfd;
}
int send_file(int sockfd, const char *filename)
{
char buff[1024] = {9};
int fd = open(filename, O_RDONLY);
if (fd < 0)
{
perror("open file error");
return -1;
}
off_t len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
printf("len = %ld\n", len);
sendto(sockfd, &len, sizeof(len), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
while (1)
{
size_t cnt = read(fd, buff, sizeof(buff));
if (cnt <= 0)
{
break;
}
sendto(sockfd, buff, cnt, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
usleep(1);
}
return 0;
}
int main(int argc, const char *argv[])
{
if (argc < 2)
{
printf("Usage : ./a.out <sendfile>\n");
return -1;
}
pthread_t tid[2];
int sockfd = init_udp_cli();
if (sockfd < 0)
{
return -1;
}
send_file(sockfd, argv[1]);
close(sockfd);
return 0;
}
#include "head.h"
#define SER_PORT 50000
#define SER_IP "192.168.0.179"
int init_udp_ser()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SER_PORT);
seraddr.sin_addr.s_addr = inet_addr(SER_IP);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind error");
return -1;
}
return sockfd;
}
int recv_file(int sockfd, const char *filename)
{
char buff[1024] = {0};
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0)
{
perror("open file error");
return -1;
}
off_t len = 0;
recvfrom(sockfd, &len, sizeof(len), 0, NULL, NULL);
printf("len = %ld\n", len);
while (1)
{
ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
write(fd, buff, cnt);
len -= cnt;
if (0 == len)
{
break;
}
}
return 0;
}
int main(int argc, const char *argv[])
{
if (argc < 2)
{
printf("Usage : ./a.out <recvfile>\n");
return -1;
}
int sockfd = init_udp_ser();
if (sockfd < 0)
{
return -1;
}
recv_file(sockfd, argv[1]);
close(sockfd);
return 0;
}