Linux网络编程01-网络基础介绍与UDP协议

发布时间:2026/9/2 17:00:35
Linux网络编程01-网络基础介绍与UDP协议 一、网络基础介绍①网络可以进行不同主机进程间的通信。②协议网络通信双方约定的一套标准。1.1OSI通信协议模型应用层要传输的数据信息如文件传输电子邮件等表示层数据加密。解密压缩解压缩会话层是否建立会话链接建立数据传输通道传输层传输方式TCP、 UDP、端口号网络层实现数据路由路由器(路由表) ip数据链路层数据在局域网内的传输封装成帧点对点通信(局域网内通信)差错校验交换机ARP物理层物理介质的连接定义物理设备标准网线光纤传输比特流1.2 TCP/IP模型四层模型应用层应用层、表示层、会话层传输的数据HTTP超文本传输协议HTTPS超文本传输协议加密FTP 文件传输协议TFTP简单文件传输协议MQTT消息队列遥测传输协议物联网DNS 域名解析服务www.baidu.com---IP:将域名转换成IP地址TELNET远程登录SMTP邮件传输协议自定义协议传输层传输的方式UDP用户数据报协议UDP传输简单UDP占用资源比较小UDP不安全不可靠UDP是无连接的TCP传输控制协议TCP传输机制复杂通信之前三次握手建立连接通信过程中有传输确认机制通信结束时通过四次挥手结束连接掉线检测、滑动窗口、拥塞控制来完成对通信过程的管理TCP占用资源较大TCP安全可靠TCP是有连接的网络层数据如何由一台主机到达另一台主机IP协议IPv4/IPv6网络接口层数据链路层、物理层接口电器特性及接口标准ARP地址解析协议IP地址转MAC地址RARP逆地址解析协议MAC地址转IP地址五层模型应用层应用层、表示层、会话层传输层网络层数据链路层物理层1.3IPV4协议①IP地址由网络位和主机位两部分构成。网络位标识主机所在的局域网编号、表示当前主机所在的网段。主机位标识在该局域网内是第几台主机。②表示形式管理员点分十进制表示形式192.168.0.175内存中二进制表示形式11000000.10101000.00000000.10101111ipv4用一个4字节(32位)的整型来保存地址ipv6128位保存ip地址解决ip地址不够用的问题③特殊地址网段号网络地址网络位不变主机位全为0表示该局域网编号—192.168.0.0广播地址网络位不变主机位全为1用于向该局域网内所有主机发送数据—192.168.0.255④主机容量一个局域网内可容纳的主机数量为2^n - 2其中n为主机位数。192.168.0.0网段最多可容纳 256 - 2 254 台主机除去网段号和广播地址。⑤ 局域网内部通信通过 MAC 地址完成。MAC 地址是每个网卡出厂时固化的唯一硬件地址全球唯一不会冲突。⑥ 子网掩码子网掩码与 IP 地址配合使用用于区分网络位和主机位子网掩码中为1的部分对应 IP 地址的网络位。子网掩码中为0的部分对应 IP 地址的主机位。IP地址 192.168.0.175 11000000.10101000.00000000.10101111 子网掩码 255.255.255.0 11111111.11111111.11111111.00000000 192.168.0.175/24⑦ IP地址分类A类 范围1.0.0.0 - 126.255.255.255 子网掩码255.0.0.0 管理超大规模型网络 私有IP地址 10.0.0.0 - 10.255.255.255 0.0.0.0/8本网络占位符仅作源地址不能分配给设备如0.0.0.235就在此范围 127.0.0.0/8环回地址用于本机测试最常见的是127.0.0.1。 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.255.255.255 用于组播和广播 E类 范围240.0.0.0 - 255.255.255.255 用于实验用途 公有IP由电信公司直接分配并需要付费的IP地址 可以直接访问internet 私有IP不能直接访问internet的ip地址1.4 端口号①端口号区分同一主机上的不同网络进程16位的数值 0-65535。② 分类1任何TCP/IP实现所提供的服务都用1-1023之间的端口号。HTTP80、8080FTP20/21TFPT69HTTPS443MQTT18832端口号从1024-49151是被注册的端口号被IANA指定为特殊服务使用。1.5 网络协议栈整个网络传输过程都在不停的封装与解封1.6 网络相关命令ifconfig 查看Linux下主机的IP地址相关信息 sudo ifconfig 网卡名 IP地址/24 up //配置IP地址并启动网卡 ping IP地址/域名 检查当前主机和目标主机是否网络联通 配置IP地址Ubuntu18.04配置桥接模式 sudo vim /etc/network/interfaces auto lo iface lo inet loopback auto ens33 iface ens33 inet dhcp 重启网络服务 sudo /etc/init.d/networking restart sudo route delete default //删除默认路由 sudo route add default gw 192.168.78.2 ens33 //配置默认路由NAT 模式只要 Windows 能上网虚拟机也就能上网。Windows 拥有独立的物理 IP 地址Ubuntu 则使用由虚拟网络分配的地址。因为 Ubuntu 的 IP 是虚拟的外部设备无法直接访问这台虚拟机但它可以主动访问外部网络。适用场景需要让 Ubuntu 正常上网但不要求其作为服务器对外提供服务。桥接模式Windows 和虚拟机共用同一张物理网卡这张网卡会获得两个 IP 地址分别供 Windows 和 Ubuntu 使用。Ubuntu 既能主动访问外部网络也能作为服务器被局域网内的其他设备访问。同一张网卡会占用两个 IP 地址在需要账号登录或 IP 受限的网络环境中可能无法同时保证 Windows 和 Ubuntu 都能正常上网。二、UDP协议2.1 UDP协议介绍①UDPuser datagram protocol通信UDP是传输层协议用户数据报协议。② C/S和B/S模型网络编程模型1C/SClient/Server 客户端/服务器客户端是一个专用客户端客户端和服务端程序都需实现客户端和服务端都可以保存资源2B/S: Browser/Server 浏览器/服务器客户端是一个通用的客户端只需要维护服务端一般都是用HTTP协议主要资源都在服务端③UDP的特点面向数据包,一包不要超过1500不需要建立连接尽最大可能交付不安全不可靠UDP机制简单实时性高效率高资源开销小④UDP 头部8 字节源端口号2字节目标端口号2字节长度2字节校验和2字节数据2.2 UDP函数接口2.3.1 socket创建套接字套接字系统为应用层提供的网络通信端口,本质是文件描述符int socket(int domain, int type, int protocol); 功能创建一个套接字 参数 domain使用的协议族 AF_INET: IPv4 AF_INET6IPv6 type传输方式 SOCK_STREAM 流式套接字TCP SOCK_DGRAM:数据报套接字UDP SOCK_RAW 原始套接字 protocol: 0 默认套接字类型对应的协议 返回值 成功返回新的文件描述符 失败返回-1 tcp_socket socket(AF_INET, SOCK_STREAM, 0); udp_socket socket(AF_INET, SOCK_DGRAM, 0); raw_socket socket(AF_INET, SOCK_RAW, protocol);2.3.2 sendto发送数据包ssize_t sendto(int sockfd, const void *buf, size_t len, intflags, const struct sockaddr *dest_addr,socklen_t addrlen); 功能发送消息发送UDP的数据包 参数 sockfd:套接字的文件描述符 buf:发送数据空间的首地址 len:发送数据的长度 flags:属性 默认为0 dest_addr:目的地址空间的首地址接收方服务端的地址信息的指针 addrlen:目的地址的长度接收方地址的大小 返回值 成功返回发送的字节数 失败返回-1 ipv4的地址结构体 struct sockaddr_in 结构体的定义可通过man 7 ip来查看定义如下 IPV4地址类型 struct sockaddr_in { sa_family_t sin_family; /* 协议族 */ in_port_t sin_port; /* 端口号 */ struct in_addr sin_addr; /* IP地址 */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /*网络字节序的IP地址*/ }; struct sockaddr_in recvaddr; nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 nret) { perror(fail to sendto); return -1; }2.3.3 htons主机字节序转网络字节序short类型主机字节序小端 host网络字节序大端 networkuint16_t htons(uint16_t hostshort); 功能将本地字节序转换为网络的大端字节序 参数 hostshort:本地字节序 返回值 成功返回网络大端字节序 uint32_t htonl(uint32_t hostlong); 主机转网络 uint16_t htons(uint16_t hostshort); 主机转网络 uint32_t ntohl(uint32_t netlong); 网络转主机 uint16_t ntohs(uint16_t netshort); 网络转主机 h:host n:net l:long s:short2.3.4 inet_addr将字符串IP地址转成整形ip地址in_addr_t inet_addr(const char *cp); 功能 将字符串IP地址转换为网络的大端字节序二进制地址 参数 cp:IP地址字符串首地址 char *inet_ntoa(struct in_addr in); 功能将二进制大端字节序IP地址转换为字符串IP地址2.3.5 bind(绑定地址信息)① 只能绑定本机的IP地址不能绑定别人的IP地址② 如果一个端口号已经被占用不能重复绑定int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 功能将IP地址端口号与套接字绑定 参数 sockfd:套接字文件描述符 addr:绑定的IP地址空间首地址 addrlen 地址变量的长度 返回值 成功返回0 失败返回-1 //bind recvaddr.sin_family AF_INET; recvaddr.sin_port htons(30000); recvaddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 ret) { perror(fail to bind); return -1; }2.3.6 recvfrom接收UDP数据ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 功能从UDP套接字接收数据 参数 sockfd套接字文件描述符 buf存储数据的空间首地址 len最多接收字符的个数 flags0 默认方式 src_addr存放发送端IP地址端口号空间的首地址,不关心可传入NULL addrlen保存地址的变量大小的指针,不关心可传入NULL 返回值 成功返回实际收到的字节数 失败-1 struct sockaddr_in sendaddr; char tmpbuff[4096] {0}; socklen_t addrlen sizeof(sendaddr); //必须做初始化 recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)sendaddr, addrlen);练习1//udpsend.c #include head.h int main(void) { int sockfd 0; int ret 0; char tmpbuff[4096] {hello world}; struct sockaddr_in recvaddr; struct sockaddr_in sendaddr; ssize_t nret 0; //1.创建用来通信的套接字套接字使用UDP协议 sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } //2.绑定IP地址和端口 sendaddr.sin_family AF_INET; sendaddr.sin_port htons(40000); sendaddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)sendaddr, sizeof(sendaddr)); if (-1 ret) { perror(fail to bind); return -1; } //3.封装接收方的IP地址 recvaddr.sin_family AF_INET; recvaddr.sin_port htons(50000); recvaddr.sin_addr.s_addr inet_addr(192.168.0.175); //4.发送数据 nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 nret) { perror(fail to sendto); return -1; } printf(发送 %ld 个字节成功\n, nret); //5.关闭套接字 close(sockfd); return 0; }//udprecv.c #include head.h int main(void) { int sockfd 0; int ret 0; struct sockaddr_in recvaddr; struct sockaddr_in sendaddr; char tmpbuff[4096] {0}; socklen_t addrlen sizeof(sendaddr); ssize_t nret 0; //socket sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } //bind recvaddr.sin_family AF_INET; recvaddr.sin_port htons(30000); recvaddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 ret) { perror(fail to bind); return -1; } //recvfrom nret recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)sendaddr, addrlen); if (-1 nret) { perror(fail to recvfrom); return -1; } printf(%s:%d-%s\n, inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), tmpbuff); //close close(sockfd); return 0; }练习2编写clientA.c和clientB.c实现双方聊天功能clientA.c绑定IP地址端口clientB.c绑定IP地址端口//clientA.c #include head.h int sockfd 0; pthread_t tid_send; pthread_t tid_recv; void *sendfun(void *arg) { char tmpbuff[4096] {0}; struct sockaddr_in clientBAddr; ssize_t nret 0; clientBAddr.sin_family AF_INET; clientBAddr.sin_port htons(40000); clientBAddr.sin_addr.s_addr inet_addr(192.168.0.138); while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); gets(tmpbuff); nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)clientBAddr, sizeof(clientBAddr)); if (-1 nret) { perror(fail to sendto); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } } pthread_cancel(tid_recv); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL); if (-1 nret) { perror(fail to recvfrom); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } printf(RECV:%s\n, tmpbuff); } pthread_cancel(tid_send); return NULL; } int main(void) { int ret 0; struct sockaddr_in clientAAddr; sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } clientAAddr.sin_family AF_INET; clientAAddr.sin_port htons(30000); clientAAddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)clientAAddr, sizeof(clientAAddr)); if (-1 ret) { perror(fail to bind); return -1; } pthread_create(tid_send, NULL, sendfun, NULL); pthread_create(tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); return 0; }//clientB.c #include head.h int sockfd 0; pthread_t tid_send; pthread_t tid_recv; void *sendfun(void *arg) { char tmpbuff[4096] {0}; struct sockaddr_in clientAAddr; ssize_t nret 0; clientAAddr.sin_family AF_INET; clientAAddr.sin_port htons(30000); clientAAddr.sin_addr.s_addr inet_addr(192.168.0.138); while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); gets(tmpbuff); nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)clientAAddr, sizeof(clientAAddr)); if (-1 nret) { perror(fail to sendto); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } } pthread_cancel(tid_recv); return NULL; } void *recvfun(void *arg) { char tmpbuff[4096] {0}; ssize_t nret 0; while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL); if (-1 nret) { perror(fail to recvfrom); return NULL; } if (0 strcmp(tmpbuff, .quit)) { break; } printf(RECV:%s\n, tmpbuff); } pthread_cancel(tid_send); return NULL; } int main(void) { int ret 0; struct sockaddr_in clientBAddr; sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } clientBAddr.sin_family AF_INET; clientBAddr.sin_port htons(40000); clientBAddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)clientBAddr, sizeof(clientBAddr)); if (-1 ret) { perror(fail to bind); return -1; } pthread_create(tid_send, NULL, sendfun, NULL); pthread_create(tid_recv, NULL, recvfun, NULL); pthread_join(tid_send, NULL); pthread_join(tid_recv, NULL); return 0; }练习3实现两个主机间文件传输//send.c #include head.h int main(void) { int sockfd 0; char tmpbuff[4096] {0}; ssize_t nret 0; int fd 0; struct sockaddr_in recvaddr; sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } recvaddr.sin_family AF_INET; recvaddr.sin_port htons(50000); recvaddr.sin_addr.s_addr inet_addr(192.168.0.138); strcpy(tmpbuff, src.jpg); nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 nret) { perror(fail to sendto); return -1; } fd open(tmpbuff, O_RDONLY); if (-1 fd) { perror(fail to open); return -1; } while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret read(fd, tmpbuff, sizeof(tmpbuff)); if (nret 0) { break; } nret sendto(sockfd, tmpbuff, nret, 0, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 nret) { perror(fail to sendto); return -1; } } strcpy(tmpbuff, __QUIT__); nret sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 nret) { perror(fail to sendto); return -1; } close(fd); close(sockfd); return 0; }//recv.c #include head.h int main(void) { int sockfd 0; int ret 0; ssize_t nret 0; struct sockaddr_in recvaddr; char tmpbuff[4096] {0}; int fd 0; sockfd socket(AF_INET, SOCK_DGRAM, 0); if (-1 sockfd) { perror(fail to socket); return -1; } recvaddr.sin_family AF_INET; recvaddr.sin_port htons(50000); recvaddr.sin_addr.s_addr inet_addr(192.168.0.138); ret bind(sockfd, (struct sockaddr *)recvaddr, sizeof(recvaddr)); if (-1 ret) { perror(fail to bind); return -1; } memset(tmpbuff, 0, sizeof(tmpbuff)); nret recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL); if (-1 nret) { perror(fail to recvfrom); return -1; } fd open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (-1 fd) { perror(fail to open); return -1; } while (1) { memset(tmpbuff, 0, sizeof(tmpbuff)); nret recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL); if (-1 nret) { perror(fail to recvfrom); return -1; } if (0 strcmp(tmpbuff, __QUIT__)) { break; } write(fd, tmpbuff, nret); } close(fd); close(sockfd); return 0; }三、wireshark抓包工具① 作用抓取当前主机经过网卡的网络数据从而分析和追踪网络问题② 安装sudo apt-get install wireshark③ 抓包流程sudo wireshark打开工具选择要抓的网卡 -- any选择过滤条件并触发比如udp.port 50000进行一次网络通信