Linux学习笔记:网络通信

学到Linux网络通信这部分,整理一下笔记,从基础概念到UDP编程都过一遍。

一、网络协议基础

网络通信说白了就是不同主机上的进程之间通信。

OSI七层模型

这是个理论模型,1977年国际标准化组织搞的,目的是让不同体系结构的计算机能互联成网。从上到下七层:

  • 应用层:要传输的数据信息,比如文件传输、电子邮件

  • 表示层:数据加密解密、压缩解压

  • 会话层:建立数据传输通道

  • 传输层:传输方式(UDP/TCP)、端口号

  • 网络层:数据路由、路径规划,对应路由器和IP

  • 数据链路层:封装成帧、点对点通信、差错检测,对应交换机和ARP

  • 物理层:定义物理设备标准,传输比特流(0和1)

TCP/IP模型

实际生产用的是这个。四层模型把应用层、表示层、会话层合并为应用层,数据链路层和物理层合并为网络接口层。也有五层的分法,各层常见协议:

  • 应用层:HTTP(超文本传输)、HTTPS(SSL加密)、FTP(文件传输,TCP)、TFTP(简单文件传输,UDP)、DNS(域名解析)、MQTT(物联网消息队列遥测传输)

  • 传输层:TCP(传输控制协议)、UDP(用户数据报协议)

  • 网络层:IPv4、IPv6

  • 数据链路层:MAC与IP转换,ARP(地址解析)、RARP(逆地址解析)

  • 物理层

二、IP协议

IPv4是32位,IPv6是128位。我们平时看到的192.168.1.3这种叫点分十进制,计算机里存的是32位二进制。

IP地址 = 网络位 + 主机位。比如192.168.0.121/24,这个24就是网络位的位数。

子网掩码用来区分网络位和主机位,比如255.255.255.0,是1的部分对应网络位,是0的部分对应主机位。

几个相关概念:

  • 网段号:网络位不变,主机位全为0。比如192.168.1.3配255.255.0.0,网段号就是192.168.0.0

  • 广播号:网络位不变,主机位全为1。向广播号发信息,局域网内所有IP都能收到

  • 网关地址:比如192.168.1.1

IP地址分五类:

  • A类:1.0.0.0 - 126.255.255.255,子网掩码255.0.0.0,管理大规模网络。私有地址10.0.0.0-10.255.255.255。127.0.0.0是回环地址

  • B类:128.0.0.0 - 191.255.255.255,子网掩码255.255.0.0,管理大中规模网络。私有地址172.16.0.0-172.31.255.255

  • C类:192.0.0.0 - 223.255.255.255,子网掩码255.255.255.0,管理中小规模网络。私有地址192.168.0.0-192.168.255.255

  • D类:224.0.0.0 - 239.255.255.255,组播和广播用

  • E类:240.0.0.0 - 255.255.255.254,实验用

公有IP由电信公司分配、需要付费,可以直接访问互联网。私有IP不能直接访问互联网,主要是为了节省IP地址。

三、端口号

IP+PORT才能找到目标主机上的目标进程。端口号是16位的数值,范围0-65535,作用是唯一标识一个网络进程,通讯时用来区分数据包属于哪个进程。

分三类:

  • 1-1023:知名端口,http是80,FTP是20/21,TFTP是69,HTTPS是443,MQTT是1883

  • 1024-49151:被IANA注册的端口,指定给特殊服务

  • 49152-65535:动态或私有端口

四、网络配置

虚拟机用桥接模式联网的步骤:

  1. 虚拟机设置 → 网络适配器 → 桥接模式 → 确定

  2. 编辑 → 虚拟网络编辑器 → 更改设置 → VMnet0 → 桥接模式 → 桥接到Windows正在上网的网卡 → 应用确定

  3. 修改配置文件:sudo vim /etc/network/interfaces

配置内容大概这样:

plaintext

auto lo

iface lo inet loopback

auto ens33

iface ens33 inet dhcp

  1. 重启网络服务:sudo /etc/init.d/networking restart,不行就sudo reboot

  2. ifconfig查看Linux网卡信息,Windows下是ipconfig

  3. ping www.baidu.com检查网络通不通

五、UDP协议

UDP是用户数据报协议(User Datagram Protocol),在传输层。

网络通信模型

两种:

  • B/S(Browser/Server):浏览器/服务器。通用客户端,数据资源基本存在服务端,只需要开发服务端

  • C/S(Client/Server):客户端/服务端。专用客户端,两端都可以存资源,两端都需要开发

UDP编程相关函数

socket

int socket(int domain, int type, int protocol);

创建通信的套接字。参数:

  • domain:协议族,AF_INET是IPv4,AF_INET6是IPv6

  • type:传输层协议类型,SOCK_STREAM是TCP,SOCK_DGRAM是UDP

  • protocol:0默认方式

返回值:成功返回文件描述符(套接字),失败返回-1。套接字就是应用层网络通信的端口,本质是个文件描述符。

sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,

const struct sockaddr *dest_addr, socklen_t addrlen);

发送UDP数据报文。参数:

  • sockfd:套接字

  • buf:要发送的数据首地址

  • len:发送的数据字节数

  • flags:0默认方式

  • dest_addr:服务端地址信息(IP+PORT)

  • addrlen:地址信息长度

返回值:成功返回发送的字节数,失败返回-1。

地址结构体

struct sockaddr_in {

sa_family_t sin_family; /* AF_INET */

in_port_t sin_port; /* 网络字节序的端口 */

struct in_addr sin_addr; /* IP地址 */

};

struct in_addr {

uint32_t s_addr; /* 网络字节序的地址 */

};

字节序

网络字节序是大端(高地址存低字节位,低地址存高字节位),主机字节序一般是小端。转换函数:

  • htons:2字节,主机转网络

  • htonl:4字节,主机转网络

  • ntohs:2字节,网络转主机

  • ntohl:4字节,网络转主机

h是host,n是network,s是short(2字节),l是long(4字节)。

inet_addr

in_addr_t inet_addr(const char *cp);

把点分十进制的字符串IP转换成32位二进制IP地址形式。参数是字符串IP,返回整型IP地址。

bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

绑定套接字和IP地址及端口号。成功返回0,失败返回-1。

recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

struct sockaddr *src_addr, socklen_t *addrlen);

接收UDP数据报文。参数:

  • sockfd:套接字

  • buf:存储接收到的数据

  • len:期望接收的字节数

  • flags:0默认方式

  • src_addr:保存发送方地址信息的指针

  • addrlen:地址长度的指针

返回值:成功返回实际接收的字节数,失败返回-1,返回0表示对方发了0字节数据包。

inet_ntoa

char *inet_ntoa(struct in_addr in);

把结构体类型的IP地址转换成字符串形式的IP地址。

UDP客户端示例

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

int main(void)

{

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if (-1 == sockfd) {

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.119");

char buff128 = {0};

fgets(buff, sizeof(buff), stdin);

buffstrcspn(buff, "\\n") = '\0';

ssize_t size = sendto(sockfd, buff, strlen(buff), 0,

(struct sockaddr *)&seraddr, sizeof(seraddr));

if (-1 == size) {

perror("sendto error");

close(sockfd);

return -1;

}

close(sockfd);

return 0;

}

UDP服务端示例

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

int main(void)

{

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if (-1 == sockfd) {

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.119");

if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) {

perror("bind error");

close(sockfd);

return -1;

}

char buff128 = {0};

struct sockaddr_in cliaddr;

socklen_t addrlen = sizeof(cliaddr);

ssize_t size = recvfrom(sockfd, buff, sizeof(buff), 0,

(struct sockaddr *)&cliaddr, &addrlen);

if (-1 == size) {

perror("recvfrom error");

close(sockfd);

return -1;

}

printf("收到来自 %s 的数据: %s\n", inet_ntoa(cliaddr.sin_addr), buff);

close(sockfd);

return 0;

}

UDP不需要建立连接,客户端直接sendto,服务端bind后recvfrom等着收就行,这也是它和TCP最大的区别。

相关推荐
m4Rk_12 分钟前
【论文阅读】Agent 记忆机制(56):R2D2——把历史网页轨迹变成可搜索地图与反思记忆
论文阅读·人工智能·学习·开源·github
漂流瓶jz15 分钟前
UVA-1609 不公平竞赛 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·链表·贪心·aoapc·算法竞赛入门经典·uva
m4Rk_20 分钟前
【论文阅读】Agent 记忆机制(58):Amory——把长期对话从记忆碎片组织成会生长的故事
论文阅读·人工智能·学习·开源·github
AAA代码批发商22 分钟前
Days37 Linux C 网络编程:UDP、TCP 与 HTTP 协议实战详解
linux·c语言·网络
好评12432 分钟前
【Linux】传输层协议UDP
linux·运维·udp
学习星球38 分钟前
贪心算法精讲——贪心策略的“局部最优“如何通向全局最优?
算法·贪心算法
MuMuMu122340 分钟前
文旅户外场景智能回收终端工程难点解析:越华环保集团碳惠小屋耐候与供电系统实践
java·大数据·算法
西峰u44 分钟前
Java IO 完整学习笔记|字节流、字符流、缓冲流、资源关闭全梳理
java·笔记·学习