【Linux网络编程】
文章目录
- 一. Linux的网络模型
- 1. 网络模型概述
- 2. Linux网络协议栈包含的网络协议
- 3. Linux网络编程模型
- 4. socket套接字编程模型
- 二. UDP编程
- 1 编程准备-字节序、地址转换
- 1.1 字节序概述
- 1.2 htonl函数
- 1.3 htons函数
- 1.4 ntohl函数
- 1.5 ntohs函数
- 1.6地址转换函数 inet_pton、inet_ntop
- 1.6.1 inet_pton函数
- 1.6.2 inet_ntop函数
- 2 UDP介绍、编程流程
- 2.1 UDP概述
- UDP特点
- 2.2网络编程接口socket
- 2.3 UDP编程C/S架构
- 3 UDP编程-创建套接字
- 3.1创建socket套接字
- 4 UDP编程-发送、绑定、接收数据
- 4.1 IPv4套接字地址结构in_addr、sockaddr_in、sockaddr
- 4.2两种地址结构使用场合
- 4.3 sendto—函数发送数据
- 4.4向“网络调试助手”发送消息
- 4.5绑定 bind函数
- 4.6接收数据—recvfrom 函数
- 4.7 UDP_QQ聊天
- 三. 广播
- 1广播的用途
- 2 UDP广播的特点
- 3 UDP广播地址
- 4 广播与单播的流程
- 5 设置广播(套接口选项)
- 6广播代码流程
- 四. 多播
- 1 多播的特点:
- 2 多播地址
- 3 多播的工作流程
- 4 加入或退出多播组
- 多播套接口选项
- ip_mreq{} 多播地址结构体
- 5 多播流程代码
- 五. TCP编程
- 1 TCP介绍、编程流程
- 2 TCP C/S架构
- 3 TCP客户端编程流程
- 1. 创建TCP套接字
- 2. connect连接服务器
- 3. send发送请求
- 4 recv接收应答 (默认带阻塞)
- 5 close
- 6 客户端编程流程代码
- 4 TCP服务端编程流程
- 1. 创建TCP套接字
- 2. bind 给服务器绑定固定的port、IP地址信息
- 3. listen 监听并创建队列
- 4. accept 提取客户端的连接(阻塞)
- 5. send 发送消息到客户端
- 6. recv 接收客户端的消息
- 7. close关闭所有套接字
- 8. TCP服务端编程流程代码
- 六. TCP三次握手
- 七. 四次挥手
- 八. TCP并发服务器 - - 多进程
- 九. 端口复用
- 十. HTTP协议
- 十一. Webserver通信过程
- 十二. Web服务器实现
- 十三. 网络通信过程
- ping命令 arp工作流程
- 十四. 集线器、交换机、路由器
- 集线器
- 交换机
- 路由器
- 数据从一个局域网到另一个局域网(没有跨外网,在内网)
- 十五. 局域网-->外网-->局域网
- 十六. 原始套接字概述
- 流式套接字只能收发
- 数据报套接字只能收发
- 原始套接字可以收发
- 创建原始套接字
- 十七. 原始套接字编程
- 分析mac报文
- 十八. arp报文(扫描局域网mac)
- 千峰物联网__网络编程
一. Linux的网络模型
1. 网络模型概述
Linux使用的网络模型是TCP/IP四层网络模型,主要由应用程序、传输层、网络层、网络接口层组成。与OSI七层模型不同,但是又相互对应,它们之间关系如下图:
OSI模型的应用层、表示层、会话层对应着TCP/IP模型的应用层,传输层对应传输层,网络层对应网络互连层,数据链路层和物理层对应主机到网络层(网络接口层)。linux中的网卡驱动属于7层模型中的数据链路层,属于四层模型中的最底层的网络接口层。
2. Linux网络协议栈包含的网络协议
Linux用到的网络协议主要有:TCP、IP、UDP、以太网协议等。这些协议之间的关系,体现在各类协议数据包之间的关系,主要是各类数据包之间的相互包含。如下图所示
TCP数据包加上IP首部之后,称为IP数据报,IP数据报加上以太网首部,以太网尾部成为以太网帧。网络上传输的所有数据都是以太网帧的形式。
3. Linux网络编程模型
Linux采用统一的网络编程模型:利用Socket(套接字)抽象层进行网络编程。如果不采用统一的编程模型,那么linux的网络编程会是下图的情况:
4. socket套接字编程模型
用户程序只需要调用socket抽象层提供的统一接口即可,无需考虑具体要使用哪个协议,这些事情内核会帮我们解决,我们只要调用socket抽象层提供的接口就行。这样,进程a、b、c都只要调用send方法就OK。这就使linux网络编程变得很方便了。socket又叫套接字编程模型。
二. UDP编程
1 编程准备-字节序、地址转换
1.1 字节序概述
是指多字节数据的存储顺序
分类
- 小端格式:将低位字节数据存储在低地址
- 大端格式:将高位字节数据存储在低地址
LSB:低地址 MSB:高地址
确定主机字节序程序
#include<stdio.h>
union{short s;char c[sizeof(short)];
}un;int main()
{un.s =0x0102;if((un.c[0]==1)&&(un.c[1]==2)){printf("big-endian\n");}if((un.c[0]==2)&&(un.c[1]==1)){printf("little-endian\n");}return 0;
}
1.2 htonl函数
将32位主机字节序数据转换成网络字节序数据
将主机字节序的IP地址转换成网络字节序
//头文件:
#include <arpa/inet.h>uint32_t htonl(uint32_t hostint32);/*
参数:hostint32:待转换的32位主机字节序数据
返回值:成功:返回网络字节序的值
*/
例
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{int num = 0x01020304;//short a = 0x0102;int sum = htonl(num);printf("%x\n",sum);short b = htons(a);printf("%x\n",b);return 0;
}
1.3 htons函数
将16位主机字节序数据转换成网络字节序数据
将主机字节序的端口转换成网络字节序
//头文件:
#include <arpa/inet.h>uint16_t htons(uint16_t hostint16);/*
参数:uint16_t:unsigned short inthostint16:待转换的16位主机字节序数据
返回值:成功:返回网络字节序的值
*/
1.4 ntohl函数
将32位网络字节序数据转换成主机字节序数据
//头文件:
#include <arpa/inet.h>uint32_t ntohl(uint32_t netint32);/*
参数:uint32_t: unsigned intnetint32:待转换的32位网络字节序数据
返回值:成功:返回主机字节序的值
*/
例
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{int num = 0x01020304;//int sum = htonl(num);printf("%x\n",ntohl(sum));return 0;
}
1.5 ntohs函数
将16位网络字节序数据转换成主机字节序数据
//头文件:
#include <arpa/inet.h>uint16_t ntohs(uint16_t netint16);
/*
参数:uint16_t: unsigned short intnetint16:待转换的16位网络字节序数据
返回值:成功:返回主机字节序的值
*/
例
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{short a = 0x0102;short b = htons(a);printf("%x\n",ntohs(b));return 0;
}
1.6地址转换函数 inet_pton、inet_ntop
1.6.1 inet_pton函数
字符串ip地址转整型数据
将点分十进制数串转换成32位无符号整数
//头文件:
#include <arpa/inet.h>int inet_pton(int af,const char *stc, void *dst);/*
参数:af: 协议族 选IPV4对应的宏AF_INET ,选IPv6对应的宏AF_INET6stc:点分十进制数串的首元素地址dst:转换为32位无符号整数的地址
返回值:成功返回1 、 失败返回其它
*/
例
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{char ip_str[] = "10.0.13.100";unsigned int ip_uint = 0;unsigned char * ip_p =NULL;//可以用char吗?inet_pton(AF_INET,ip_str,&ip_uint);printf("ip_uint = %d\n",ip_uint);ip_p = (unsigned char *) &ip_uint;printf("ip_uint = %d.%d.%d.%d\n",*ip_p,*(ip_p+1),*(ip_p+2),*(ip_p+3));return 0;
}
1.6.2 inet_ntop函数
整型数据转字符串格式ip地址
将32位无符号整型数据(默认大端)转成 点分十进制数组
//头文件:
#include <arpa/inet.h>//len的宏定义
#define INET_ADDRSTRLEN 16 //for ipv4
#define INET6_ADDRSTRLEN 46 //for ipv6const char *inet_ntop(int family, const void *addrptr,char *strptr, size_t len);/*
参数:family 协议族 AF_INET:IPv4 AF_INET6:IPv6addrptr 32位无符号整数数据的地址strptr 点分十进制数串的首元素地址len 点分十进制数串的最大长度
返回值:成功:则返回字符串的首地址失败:返回NULL
*/
例
#include<stdio.h>
#include<arpa/inet.h>
int main()
{unsigned char ip[]={10,0,13,252};char ip_str[16];inet_ntop(AF_INET,(unsigned int *)ip,ip_str,16);printf("ip_str = %s\n",ip_str);return 0;
}
2 UDP介绍、编程流程
2.1 UDP概述
UDP协议:面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目地主机的运输层收到UDP报文后,不需要给出任何确认
UDP特点
1、相比TCP速度稍快些
2、简单的请求/应答应用程序可以使用UDP
3、对于海量数据传输不应该使用UDP
4、广播和多播应用必须使用UDP
UDP应用: DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等
2.2网络编程接口socket
网络通信需要解决3大问题(应用层)
1.协议
2.端口(port)
3.IP地址
20世纪80年代初,加州大学Berkeley分校在BSD(一个UNIX OS版本)系统内实现了TCP/IP协议;其网络程序编程开发接口为socket。
随着UNIX以及类UNIX操作系统的广泛应用, socket成为最流行的网络程序开发接口
socket作用:提供不同主机上的进程之间的通信
特点1、socket也称“套接字”2、是一种文件描述符,代表了一个通信管道的一个端点3、类似对文件的操作一样,可以使用read、write、close等函数对socket套接字进行网络数据的收取和发送等操作4、得到socket套接字(描述符)的方法调用socket()
2.3 UDP编程C/S架构
- 服务端要绑定确定的端口号
- 客户端不能使用read、write,因为需要发送给指定的服务端地址。
3 UDP编程-创建套接字
3.1创建socket套接字
创建一个用于网络通信的socket套接字(描述符)
//头文件:
#include <sys/socket.h>int socket(int family,int type,int protocol);/*
参数:
family:协议族(AF_INET4、AF_INET6、PF_PACKET等) ||流式套接字 用于TCP通信 ||报式套接字 用于UDP通信 ||原始套接字
type:套接字类( SOCK_STREAM、 SOCK_DGRAM、 SOCK_RAW等)||一般放0 自动指定协议
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等)返回值:>0 通信的文件描述符(套接字)<0 创建失败
*/
特点:
1.创建套接字时,系统不会分配端口2.创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器3.时,往往需要修改为被动的
例
#include <stdio.h>
#include <sys/socket.h>int main(int argc, char const *argv[])
{//创建通信的UDP的套接字int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd < 0){perror("socket");exit(-1);}//关闭套接字close(sockfd);return 0;
}注意:
AF_INET:IPv4协议
SOCK_DGRAM:数据报套接字
0:选择所给定的family和type组合的系统默认值
4 UDP编程-发送、绑定、接收数据
4.1 IPv4套接字地址结构in_addr、sockaddr_in、sockaddr
存放IPv4协议通信的所有地址信息
//头文件:
#include <netinet/in.h>struct in_addr
{in_addr_t s_addr;//4字节
};struct sockaddr_in
{sa_family_t sin_family; //2字节 协议AF_INE4 AF_INET6in_port_t sin_port; //2字节 端口struct in_addr sin_addr;//4字节 IP地址(32位无符号整数)char sin_zero[8] //8字节 全写0
};
为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构
//头文件:
#include <netinet/in.h>
struct sockaddr
{sa_family_t sa_family; // 2字节char sa_data[14] //14字节
};
4.2两种地址结构使用场合
struct sockaddr_in //IPv4地址结构(存放客户端、服务器的地址信息(协议,port,IP))
struct sockaddr //通用地址结构,不是存放数据 socket API 类型转换//在定义源地址和目的地址结构的时候,选用struct sockaddr_in;
//例:
struct sockaddr_in my_addr;//当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换
//例:
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
4.3 sendto—函数发送数据
向to结构体指针中指定的ip,发送UDP数据
#include <sys/socket.h>
ssize_t sendto(int sockfd,const void *message,size_t length,int flags,const struct sockaddr *dest_addr, socklen_t dest_len);/*
参数:sockfd:从那个套接字发出message:需要发送的消息的首元素地址length: 消息的实际长度flags:0 网络默认方式通信dest_addr:指向目主机的IPv4地址信息(协议、port、IP地址)dest_len:地址结构体的长度返回值:成功:发送数据的字符数失败: -1注意:通过dest_addr和dest_len确定目的地址可以发送0长度的UDP数据包
*/
4.4向“网络调试助手”发送消息
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> //memser
#include <arpa/inet.h> //htonsint main()
{//创建通信的UDP的套接字(没有port、ip)int sockfd = socket(AF_INET, SOCK_DGRAM,0);printf("UDP套接字sockfd=%d\n",sockfd);//udp客户端 发送消息 给服务器//定义一个IPv4地址结构 存放服务器的地址信息(目的主机)struct sockaddr_in ser_addr; memset(&ser_add, 0, sizeof(ser_add));ser_addr.sin_family = AF_INET; //IPv4ser_addr.sin_port = htons(8000); //服务器的端口inet_pton(AF_INET,"10.9.21.211", &ser_addr.sin_addr.s_addr); //服务器的IP地址//发送数据sento(sockfd, "hello net", strlen("hello net"), 0, \(struct sockaddr *)&ser_addr,sizeof(ser_addr));close(sockfd);return 0;
}
4.5绑定 bind函数
bind给udp套接字绑定固定的port、IP信息
服务器收到客户端的信息,客户端的port是随机的。如果udp套接字不使用bind函数绑定固定端口,那么在第一次调用sendto系统会自动给套接字分配一个随机端口。后续sendto调用继续使用前一次的端口。
将本地协议地址与sockfd绑定
#include <sys/socket.h>int bind(int sockfd,const struct sockaddr *address,socklen_t address_len);/*
参数:sockfd: socket套接字sockaddr: 指向特定协议的地址结构指针address_len:该地址结构的长度返回值:成功:返回0失败:其他注:只能绑定本地主机的
*/
例
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> //memset
#include <arpa/inet.h> //htos
#include <unistd.h> //closeint main()
{//创建通信的UDP的套接字(没有port、ip)int sockfd = socket(AF_INET, SOCK_DGRAM, 0);printf("UDP套接字sockfd=%d\n", sockfd);//定义IPv4地址结构,存放本机信息struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(9000);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);//给udp套接字 bind绑定一个固定的地址信息bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));//udp客户端 发送消息 给服务器//定义一个IPv4地址结构 存放服务器的地址信息(目的主机)struct sockaddr_in ser_addr; memset(&ser_add, 0, sizeof(ser_add));ser_addr.sin_family = AF_INET; //IPv4ser_addr.sin_port = htons(8000); //服务器的端口//服务器的IP地址inet_pton(AF_INET,"10.9.21.211", &ser_addr.sin_addr.s_addr);//发送数据sento(sockfd, "hello net", strlen("hello net"), 0, \(struct sockaddr *)&ser_addr,sizeof(ser_addr));//关闭套接字close(sockfd);return 0;}
4.6接收数据—recvfrom 函数
接收UDP数据,并将源地址信息保存在from指向的结构中(默认没消息,阻塞)
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from,socklen_t *addrlen);/*
参数:sockfd: udp套接字buf: 用来存放接收消息的空间起始地址nbytes: 能接收消息的最大字节数flags: 套接字标志(常为0)from: 存放发送者的IPv4地址信息(不关心发送者信息,可为NULL)addrlen: 地址结构长度返回值:
成功:接收到的实际字节数
失败: -1注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
*/
例
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> //memset
#include <arpa/inet.h> //htos
#include <unistd.h> //closeint main()
{//创建通信的UDP的套接字(没有port、ip)int sockfd = socket(AF_INET, SOCK_DGRAM, 0);printf("UDP套接字sockfd=%d\n", sockfd);//定义IPv4地址结构,存放本机信息struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(9000);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);//给udp套接字 bind绑定一个固定的地址信息bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));//接收udp消息while(1){//定义一个IPv4地址结构,存放发送者的信息struct sockaddr_in from_addr;socklen_t from_len = sizeof(from_addr);unsigned char buf[1500] = "";int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from_addr, &from_len);//from_addr存放的就是发送者的消息char ip[16] = "";//转换为点分十进制ipinet_ntop(AF_INET, &from_addr.sin_addr.s_addr, ip, 16);printf("消息来自%s %hu--->", ip, ntohs(from_addr.sin_port));printf("len:%d msg:%s\n", len, buf);}//关闭套接字close(sockfd);return 0;}
4.7 UDP_QQ聊天
同时收发数据
1、创建udp套接字socket 2、bind固定的地址信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h> //线程头文件
#include <arpa/inet.h>void *send_function(void *arg)
{//获取套接字int sockfd = *(int *)arg;//定义地址目的结构struct sockaddr_in dst_addr;bzero(&dst_addr,sizeof(dst_addr));dst_addr.sin_family = AF_INET;while(1){//获取键盘输入fgets(buf,sizeof(buf),stdin);buf(strlen(buf)-1)=0;//判断是否是IP port//sayto IP portif(strncmp(buf,"sayto",5) == 0){char ip[16] = "";unsigned short port = 0;//sayto 10.9.21.211 8000sscanf(buf,"sayto %s %hu", ip, &port);dst_addr.sin_port = htons(port);inet_pton(AF_INET, ip, &dst_addr.sin_addr.s_addr);continue;}else{sendto(socked, buf, strlen(buf), 0, \(struct sockaddr *)&dst_addr, sizeof(dst_addr));if(strcmp(buf,"bye")==0)break;}}return NULL;
}void *recv_function(void *arg)
{int sockfd = *(int *)arg; while(1){struct sockaddr_in from_addr;socklen_t from_len = sizeof(from_addr);unsigned char buf[1500]="";char ip[16]="";int len = recvfrom(sockfd, buf,sizeof(buf), 0,\(struct sockaddr *)&from_addr, &from_len);printf("%s %hu:%s\n",inet_ntop(AF_INET,&from_addr.sin_addr.s_addr,ip,16),\ntohs(from_addr.sin_port),buf);if(strcmp(buf,"bye")==0)break;}return NULL;
}int main(int argc, char const *argv[])
{// 判断参数 ./a.out 8000\n");if(arfc != 2){printf("./a.out 8000\n");return 0;}//创建udp套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//bind绑定固定的端口, IPstruct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(atoi(argv[1]); ///atoi 将字符串转为数字my_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));//创建发送线程pthread_d send_tid;pthread_create(&send_tid, NULL, send_function, (void *)&sockfd);//创建接收线程pthread_d recv_tid;pthread_create(&recv_tid, NULL, recv_function, (void *)&sockfd); pthread_join(send_tid, NULL);pthread_join(recv_tid, NULL);//关闭套接字close(sockfd);return 0;
}
三. 广播
1广播的用途
单个服务器与多个客户主机通信时减少分组流通
以下几个协议都用到广播
1、地址解析协议(ARP,寻找IP地址对应的MAC地址)
2、动态主机配置协议(DHCP,向路由器申请IP地址)
3、网络时间协议(NTP)
2 UDP广播的特点
1、处于同一子网的所有主机都必须处理数据
2、UDP数据包会沿协议栈向上一直到UDP层
3、运行音视频等较高速率工作的应用,会带来大负载
4、局限于局域网内使用,不可用于广域网
3 UDP广播地址
{网络ID,主机ID}
网络ID表示由子网掩码中1覆盖的连续位
主机ID表示由子网掩码中0覆盖的连续位
定向广播地址:主机ID全1
1、例:对于192.168.220.0/24,其定向广播地址为192.168.220.255(所有主机必须无条件接收)
2、通常路由器不转发该广播
受限广播地址:255.255.255.255
路由器从不转发该广播(否则全世界的人都接收了)
4 广播与单播的流程
单播:
广播:
5 设置广播(套接口选项)
描述:设置套接字有广播功能
#include <sys/socket.h>
int setsockopt(int sockfd, int level,\int optname, const void *optval,\socklen_t optlen);
/*
参数: sockfd:套接字 level:SOL_SOCKET optname:SO_BROADCAST optval:int 类型变量的地址,这个值设置为1 optlen:int类型变量的大小成功返回0,否则返回-1
*/
6广播代码流程
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>int main(int argc, char const *argv[])
{//创建一个udp套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//设置套接字允许广播int yes = 1;setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int));//定义地址结构存放广播地址信息struct sockaddr_in dst_addr;bzero(&dst_addr, sizeof(dst_addr));dst_addr.sin_family = AF_INET;dst_addr.sin_port = htons(8000);dst_addr.sin_addr.s_addr= inet_addr("10.9.21.255"); //广播地址//广播for(int i = 0; i < 5; i++){sendto(sockfd, "this is broadcast", strlen("this is broadcast"), \0, (struct sockaddr *)&dst_addr, sizeof(dst_addr));sleep(1);}close(sockfd);return 0;
}
四. 多播
数据的收发仅仅在同一分组中进行
1 多播的特点:
1、多播地址标示一组接口
2、多播可以用于广域网使用
3、在IPv4中,多播是可选的
2 多播地址
IPv4的D类IP地址是多播地址
十进制:224.0.0.1 ~ 239.255.255.254(里面任意一个都是多播地址)
十六进制:E0.00.00.01 ~ EF.FF.FF.FE
多播地址向以太网MAC地址的映射(MAC地址不能直接全部设为FF,需要进行过滤)
3 多播的工作流程
只能将自己主机的IP加入多播组
基于mac地址不完备硬件过滤
基于IP地址的完备软件过滤
4 加入或退出多播组
多播套接口选项
int setsockopt(int sockfd, int level,int optname, const void *optval, socklen_t optlen);
/*
sockfd: 加入或退出的套接字
*/
//成功执行返回0,否则返回-1
ip_mreq{} 多播地址结构体
在IPv4因特网域(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示:
struct in_addr
{in_addr_t s_addr;
}struct ip_mreq
{struct in_addr imr_multiaddr; //多播组IPstruct in_addr imr_interface; //将要添加到多播组的IP/*将imr_interface添加到imr_multiaddr中*/
};
5 多播流程代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>int main(int argc, char const *argv[])
{//创建一个udp套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//bind绑定固定的IP端口struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(8000);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));//将sockfd加入多播组224.0.0.1struct ip_mreq mreq;mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); //多播组IPmreq.imr_interface.s_addr = hton1(INADDR_ANY); //主机所有IPsetsockopt(sockfd, IPPORT_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));//测试接收多播组的消息while(1){unsigned char buf[1500] = "";recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);printf("buf = %s \n",buf);}close(sockfd);return 0;
}
五. TCP编程
1 TCP介绍、编程流程
1、面向连接的流式协议;可靠、出错重传、且每收到一个数据都要给出相应的确认
2、通信之前需要建立链接
3、服务器被动链接,客户端是主动链接
2 TCP C/S架构
3 TCP客户端编程流程
- socket 创建套接字
- connect 连接服务器
- send 发送请求
- recv 接收应答
- close
1. 创建TCP套接字
socket创建的套接字:没有端口、主动连接别人
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
注:TCP是SOCK_STREAM, UDP为SOCK_DGRAM
2. connect连接服务器
如果sockfd没有被bind绑定,第一次调用connect系统自动分配随机端口,后续使用该端口
int connect(int socket, const struct sockaddr *address, socklen_t address_len)
/*
socket: 套接字
address: 连接的服务器地址结构
address_len: 地址结构长度
*/
注:如果客户端和服务器通信,必须使用connect事先建立连接
3. send发送请求
ssize_t send(int socket, const void *buffer, size_t length, int flags)
/*
socket: 客户端套接字
buffer:发送的消息
length:消息长度
flags: 0返回值:成功:返回发送的字节数失败:返回-1
*/
4 recv接收应答 (默认带阻塞)
ssize_t recv(int socket, void *buffer, size_t length, int flags);
/*
socket: 客户端套接字
buffer: 接收的消息
length:能接收的最大长度
flags:0返回值:成功:成功接收的字节数失败:-1
*/
5 close
#include <unistd.h>int close(int fildes);
6 客户端编程流程代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>int main(int argc, char const *argv[])
{//创建tcp套接字 SOCK_STREANint sockfd = socket(AF_INET, SOCK_STREAM, 0);//connect连接服务器(知道服务器地址信息)struct sockaddr_in ser_addr;bzero(&ser_addr, sizeof(ser_addr));ser_addr.sin_family = AF_INET;ser_addr.sin_port = htons(8000);ser_addr.sin_addr.s_addr = inet_addr("10.9.21.211");connect(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));//客户端发送请求send(sockfd, "hello tcp", strlen("hello tcp"), 0 );//客户端接收服务器的应答unsigned char buf[1500] = "";int len = recv(sockfd, buf, sizeof(buf), 0);printf("服务器的应答:%s\n", buf);//关闭套接字close(sockfd);return 0;
}
4 TCP服务端编程流程
- socket 创建套接字
- bind 绑定固定的port、ip地址信息
- listen 监听套接字 创建连接队列
- accept
- send 发送请求
- recv 接收应答
- close
1. 创建TCP套接字
socket创建的套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
注:TCP是SOCK_STREAM, UDP为SOCK_DGRAM
2. bind 给服务器绑定固定的port、IP地址信息
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)):
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8000);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)):
3. listen 监听并创建队列
listen监听:等待客户端的连接到来,经过三次握手(底层自动),将客户端放入入连接队列
#include <sys/socket.h>int listen(int socket, int backlog);/*
功能:1. 将监听套接字由主动变被动2. 为该套接字创建连接队列参数:socket:变被动的套接字backlog:连接队列的大小返回值:成功:0失败:-1
*/
4. accept 提取客户端的连接(阻塞)
#include <sys/socket.h>int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);/*
功能:从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)参数:sockfd: socket监听套接字cliaddr: 用于存放客户端套接字地址结构addrlen:套接字地址结构体长度的地址返回值:已连接套接字
*/
注:返回的是一个已连接套接字,服务器只能通过已连接套接字和客户端进行数据通信
5. send 发送消息到客户端
ssize_t send(int socket, const void *buffer, size_t length, int flags)
/*
socket: 客户端套接字
buffer:发送的消息
length:消息长度
flags: 0返回值:成功:返回发送的字节数失败:返回-1
*/
注:如果ssize_t>0,表示发送成功(实际发送的字节数)
如果ssize_t为-1,表示发送是失败
tcp不允许send发送0长度报文(服务端接收0长度报文则为客户端断开连接)
udp允许sendto发送0长度报文
6. recv 接收客户端的消息
ssize_t recv(int socket, void *buffer, size_t length, int flags);
/*
socket: 客户端套接字
buffer: 接收的消息
length:能接收的最大长度
flags:0返回值:成功:成功接收的字节数失败:-1
*/
注:如果ssize_t为0,表示客户端已经断开连接
如果ssize_t>0,表示recv收到的实际字节数
如果ssize_t为-1,表示recv读取数据出错
7. close关闭所有套接字
#include <unistd.h>int close(int fildes);
8. TCP服务端编程流程代码
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> //memset
#include <arpa/inet.h> //htos
#include <unistd.h> //closeint main()
{//创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//bind绑定固定的port、ip地址信息struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(9000);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));//监听套接字 创捷连接队列listen(sockfd, 10);//提取客户端的连接while(1){//一次只能提取一个客户端struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);int cfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);if(cfd < 0 ) //提取失败{perrer("accept\n");break;}else{char ip[16] = "";unsigned short port = 0;inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, 16);port = ntohs(cli_addr.sin_port);//打印客户端的信息printf("客户端:%s %d connected\n", ip, port);while(1){//获取客户端的请求unsigned char buf[1500] = "";int len = recv(cfd, buf, sizeof(buf), 0);if(len == 0) //客户端已经关闭{//关闭与客户端连接的套接字close(cfd);break;}//应答客户端send(cfd, buf, len, 0);}} }//关闭监听套接字close(sockfd);return 0;
}
六. TCP三次握手
客户端调用connect连接服务器时,底层会发生“三次握手”,握手成功,连接建立,connect解阻塞继续执行。
SYN 置1为连接报文
FIN 置1为断开报文
ACK 置1为回应报文
RST 复位
URG 紧急指针,比其他报文优先发送
PSH 推送
序号 当前报文的标号 seq
确认序号指希望对方下次发送数据的序号 ack_seq
客户端发送SYN请求,处于SYN_SENT状态完成第一次握手
服务端收到客户端SYN请求,处于SYN_RCVD状态,并发出SYN以及ACK请求,完成第二次握手
客户端收到服务端的SYN以及ACK处于ESTABISHED(连接状态),并发出ACK请求,完成第三次握手
七. 四次挥手
当客户端调用close,激发低层发出FIN请求,完成第一次挥手
服务器收到客户端的FIN,立马回应ACK报文完成第二次挥手
服务器应用层调用close,激发底层发出FIN请求,完成第三次挥手
客户端收到服务器的FIN请求,发出ACK应答完成第四次挥手
为啥是四次挥手
第二次、第三次报文存在时间差异,不能组成一个报文发送
客户端调用close,为啥能收到服务器的FIN以及ACK?
客户端调用close只是处于版关闭状态(应用层不能收发,但底层可以)
八. TCP并发服务器 - - 多进程
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockddr_in
#include <arpa/inet.h> //inet_pton inet_addr
#include <string.h> //bzero
#include <stdlib.h> //exit
#include <unistd.h> //fork
#include <signal.h> //SIGCHLD
#include <errno.h>void free_child(int sig)
{//回收子进程的资源while(1){pid_t pid = waitpid(-1, NULL, WNOHANG);if(pid <= 0) //没有子进程退出{break;}else if(pid > 0){printf("子进程%d退出了\n", pid);}}}int main(int argc, char const *argv[])
{if(argc != 2){printf("./a.out 8000\n");_exit(-1);}//1 创建tcp监听套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd < 0 ){perror("socket");exit(-1);}//设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REuseADDR, &opt, sizeof(opt));//2 bind给服务器的监听套接字绑定固定的IP、portstruct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(atoi(arg[1]));my_addr.sin_addr.s_addr = htonl(INADDR_ANY);int ret = bind(lfd, (struct sockaddr *)&my_addr, sizeof(my_addr));if(ret < 0 ){perror("bind");_exit(-1);}//3 listen将服务器的套接字主动变被动 创建连接队列 进行监听ret = listen(lfd, 128);if(ret < 0 ){perror("listen");_exit(-1);}//将SIGCHLD放入阻塞集sigset_t set;//清空集合sigemptyset(&set);//将SIGCHLD放入集合中sigaddset(&set,SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);//4 while ---> accept提取客户端while(1){struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);int cfd = accept(lfd,(struct sockaddr *)&cli_addr, &cli_len);if(cfd < 0){if(errno == ECONNABORTED || errno == EINTR)continue;break; }char ip[16] = "";unsigned short port = 0;inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, 16);port = ntohs(cli_addr.sin_port);printf("%s %hu connected\n", ip, port);//5 一个客户端创建一个进程(父进程close(cfd),子进程close(lfd)pid_t pid = fork();if(pid == 0) //子进程{//子进程close(lfd)close(lfd);//6 子进程 服务客户端while(1){//获取客户端的请求unsigned char buf[1500] = "";int len = recv(cfd, buf, sizeof(buf), 0);if(len <= 0){printf("%s %hu 退出了\n", ip, port);break;}else{printf("%s\n",buf);//将数据发给客户端send(cfd, buf, len, 0)}//关闭cfdclose(cfd);_exit(-1); //进程退出 发出SIGCHLD信号}else if(pid > 0) //父进程{//父进程close(cfd)close(cfd);//注册SIGCHLD处理函数signal(SIGCHLD, free_child);//将SIGCHLD从阻塞中解除sigprocmask(SIG_UNBLOCK, &set, NULL);}}
九. 端口复用
默认情况下,如果一个网络应用程序的一个套接字绑定了一个端口(占用了8000),这时候,别的套接字就无法使用这个端口(8000)
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REuseADDR, &opt, sizeof(opt));
上面的socked为需要使用同一端口复用的套接字
十. HTTP协议
基于TCP协议得超文本传送协议。是浏览器与服务器之间得通信协议。
一个连接只能处理一个请求。
特点:
- 支持C/S架构
- 简单快速:客户向服务器请求服务器时,只需传送请求方法和路径 ,常用方法:GET、POST
- 无连接:限制每次连接只处理一个请求
- 无状态:即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大
十一. Webserver通信过程
十二. Web服务器实现
#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h> //inet_pton inet_addr
#include <string.h> //bzero
#include <stdlib.h> //_exit
#include <pthread.h> //线程相关函数
#include <unistd.h>//open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>char head[] = "HTTP/1.1 200 OK\r\n" \"Content-Type: text/htm1\r\n" \"\r\n";
char err[] = "HTTP/1.1 404 Not Found\r\n" \"Content-Type: text/htm1\r\n" \"\r\n" \"<HTML><BODY>File not found</BODY></HTML>";typedef struct {int cfd; //存放已连接套接字struct sockaddr_in addr; //存放客户端的信息
}CLIENT_MSG;void *deal_client_fun(void *arg)
{CLIENT_MSG *p = (CLIENT_MSG *)arg;//打印客户端的信息char ip[16] = "";unsigned short port = 0;inet_ntop(AF_INET, &p->addr.sin_addr.s_addr, ip, 16);port = ntohs(p->addr.sin_port);printf("%s %hu connected\n", ip, port);//获取浏览器的请求unsigned char buf[1500] = "";recv(p->cfd, buf, sizeof(buf), 0);//提取请求中的文件名char file_name[512] = "./htm1"; //存放本地的路劲sscanf(buf, "GET %s", file_name+6);printf("##%s##\n", file_name);if(file_name[7] == '\0') //没有提取到文件名{strcpy(file_name,"./html/index.htm1"); //默认提出的文件}//从本地打开file_name文件int fd = open(file_name, O_RDONLY);if(fd < 0) //本地没有该文件{//send 404send(p->cfd, err, strlen(err), 0);//退出线程close(p->cfd);//释放堆区空间if(p != NULL){free(p);p = NULL;}return NULL:}//本地文件打开成功//send 200send(p->cfd, head, strlen(head), 0);//循环读取本地文件 发送给浏览器while(1){unsigned char file_data[1024] = "";//读取文本文件数据int len = read(fd, file_data, sizeof(file_data));//将file_data发送给浏览器send(p->cfd, file_data, len, 0);if(len < 1024)break; }//释放堆区空间close(p->cfd);if(p != NULL){free(p);p = NULL;}//线程结束pthread_exit(NULL);return NULL:
}//1. 创建tcp监听套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd < 0){perror("socket")_exit(-1);}//设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//2. bind给服务器的监听套接字绑定固定的IP、portstruct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr));my_addr.sin_family = AF_INET;my_addr.sin_port = htons(atoi(argv[1]));my_addr.sin_addr.s_addr = htonl(INADDR_ANY);int ret = bind(lfd, (struct sockaddr *)&my_addr, sizeof(my_addr));if(ret < 0){perror("bind");_exit(-1);}//3. listen将服务器的套接字主动变被动 创建连接队列 进行监听ret = listen(lfd, 128)if(ret < 0){perror("listen");_exit(-1);}//4. while-->accept提取客户端while(1){struct sockaddr_in cli_addr;socklen_t cli_len = sizeof(cli_addr);int cfd = accept(lfd, (struct sockaddr *)&cli_addr, &cli_len);CLIENT_MSG *p = (CLIENT_MSG *)calloc(1, sizeof(CLIENT_MSG));p->cfd = cfd;p->addr = cli_addr;//5. 一个客户端创建一个线程pthread_t tid;pthread_create(&tid, NULL, deal_client_fun, (void *)p);//线程分离pthread_detach(tid);}//关闭监听套接字close(lfd);return 0;
}
十三. 网络通信过程
通过对TCP、UDP的编程学习,能够完成对实际项目需求中网络功能的开发,为了提高程序的稳定性以及效率等等,通常会使用多线程、多进程开发;根据功能需求的不同,可以利用C/S、B/S模式进行开发
ping命令 arp工作流程
- ping基于ICMP协议。
- 协议栈有一张ARP表记录通信过的IP对应的mac。
- ping通过ICMP协议数据到达链路层,先去ARP表中查看目的IP对应的mac地址,如果有记录,将目的mac放入mac报文中发出去。如果arp表中没有记录,系统自动调用arp协议进行广播获取目的主机的mac,目的主机收到arp请求,单播应答。发送方就会更新arp表并将mac放入报文发送出去。
十四. 集线器、交换机、路由器
集线器
集线器工作在物理层。
数据到达集线器会被集线器广播到与集线器相连的所有设备上。
所有连接到集线器上的设备都是共享集线器的带宽。
整形放大的功能。
交换机
交换机工作在链路层(核心层),三层交换机(核心层在链路层,只是具备VLAN虚拟局域网的划分)。
交换机上的设备是单播通信。
交换机上所有设备独享带宽。
路由器
路由器是不同网段通信的桥梁。
1、如果目的IP和发送主机的IP不在同一个局域网,发送主机的网络层思考应该将数据发送给网关。
2、假如每台主机已经配置网关信息,数据传递到链路(封装mac地址),去arp表中查找网关的mac地址(如果没有,需要arp广播得到网关mac),src_mac为主机mac、dest_mac为网关的mac,发送出去数据就到达网关。
3、路由器收到数据:查看报文中的目的IP地址和当前路由器的哪个接口的IP是同一个网段。
1. 找到同一网段的接口,那么数据就会从该接口发送至目的主机(src_mac为接口的mac,dst_mas为主机的mac)就会从该接口发送至目的主机
2. 没有找到同一个网段接口,从路由器的“路由表”中查看“下一跳”,并确定当前路由器的那个接口和下一跳相连。将数据(src_mac为接口的mac,dst_mac为下一跳的mac)发送到下一跳,下一跳收到数据,重复上一个路由器的所有动作。
数据从一个局域网到另一个局域网(没有跨外网,在内网)
数据从应用层到传输层,封装源端口、目的端口,数据到达网络层封装原IP、目的IP,并查看目的IP是不是当前局域网,不是当前局域网,数据交给网关,数据传递到链路层,链路层在arp表中查找网关的mac,如果有,修改原mac为主机mac, 目的mac为网关mac,数据发送到网关,如果arp表中没有记录网关的mac地址,将调用arp协议广播得到网关的mac地址,并更新arp表,并修改原mac为主机mac,目的mac为网关的mac,数据从主机的网卡发送到网关,网关的路由器收到数据后,先进行分析数据包中的目的网段是否和路由器的某一个接口的网段是同一个网段,如果是,数据就从该接口发送到目的主机,原mac为接口的mac,目的mac为主机的mac,如果报文中的目的IP,与路由器中的所有接口都不在同一网段,这时候路由器会查看路由表,查看下一跳,并确定路由器的哪一个接口和下一跳相连,修改原mac为接口的mac,目的mac为下一跳的mac,数据发送到下一跳,下一跳重复上一跳的所有动作传递到目的主机,
十五. 局域网–>外网–>局域网
- 常规方式
十六. 原始套接字概述
原始套接字(SOCK_RAW)
1、一种不同于SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)的套接字,它实现于系统核心
2、可以接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用
3、开发人员可发送自己组装的数据包到网络上
4、广泛应用于高级网络编程
5、网络专家、黑客通常会用此来编写奇特的网络程序
流式套接字只能收发
TCP协议的数据
数据报套接字只能收发
UDP协议的数据
原始套接字可以收发
- 内核没有处理的数据包,因此要访问其他协议
- 发送的数据需要使用,原始套接字(SOCK_RAW)
创建原始套接字
在链路层可以发送自定义的帧数据,也可以进行数据的分析、伪装等。
PF_PACKET:链路层编程
SOCK_RAW:原始套接字
ETH_P_ALL:收发 所有帧数据
#include <sys/socket.h>
#include <netinet/ehter.h>
int socket(PF_PACKET, SOCK_RAW, protocol)/*
功能:创建链路层的原始套接字
参数:protocol:指定可以接收或发送的数据包类型ETH_P_IP:IPV4数据包ETH_P_ARP:ARP数据包ETH_P_ALL:任何协议类型的数据包
返回值:成功(>0):链路层套接字失败(<0):出错
*/
在TCP/IP协议栈中的每一层为了能够正确解析出上层的数据包,从而使用一些”协议类型“来标记,如图
十七. 原始套接字编程
原始套接字必须sudo运行
1、创建原始套接字
2、recvfrom接收数据
分析mac报文
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ehter.h>
#include <arpa/inet.h>int mian(int argc, char const *argv[])
{int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));printf("sockfd = %d\n",sockfd);//接收数据while(1){unsigned char buf[1500] = "";int len = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);printf("len = %d\n", len); //分析mac报文char src_mac[18] = ""; //源macchar dst_mac[18] = ""; //目的macsprintf(dst_mac,"%02x:%02x:%02x:%02x:%02x:%02x",\buf[0],buf[1],buf[2],buf[3],buf[4],buf[5]);sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",\buf[0+6],buf[1+6],buf[2+6],buf[3+6],buf[4+6],buf[5+6]);unsigned short mac_type = 0;mac_type = ntohs(*(unsigned short *)(buf + 12));printf("%s-->%s:",src_mac,dst_mac);if(mac_type == 0x0800){printf("IP报文\n");unsigned char *ip_p = buf+14;int ip_head_len = ((*ip_p)&0x0f)*4; //从第一个字节里取ip长度char src_ip[16] = "";char dst_ip[16] = "";inet_ntop(AF_INET, (void *)ip_p+12, src_ip, 16);inet_ntop(AF_INET, (void *)ip_p+12, dst_ip, 16);printf("\t%s--->%s:", stc_ip, dst_ip);char ip_type = *(ip_p+9);if(ip_type == 1){printf("ICMP报文"\n");}else if(ip_type == 2){printf("IGMP报文"\n");}else if(ip_type == 6){printf("TCP报文\n");unsigned char *tcp_p = buf+14+ip_head_len;printf("\t%hu--->%hu:", ntohs(*(unsigned short *)tcp_p),\ntohs(*(unsigned short *)(tcp_p+2)));int tcp_head_len = ((*(tcp_p +12))>>4)*4;//应用数据printf("%s\n", tcp_p+tcp_head_len);}else if(ip_type == 17){printf("UDP报文\n");unsigned char *tcp_p = buf+14+ip_head_len;printf("\t%hu--->%hu:", ntohs(*(unsigned short *)udp_p),\ntohs(*(unsigned short *)(udp_p+2)));//应用数据printf("%s\n", udp_p+8);}else{printf("未知报文\n");}}else if(mac_type == 0x0806){printf("ARP报文\n");}else if(mac_type == 0x8035){printf("RARP报文\n");}else{printf("其他报文\n");}}close(sockfd);return 0;
}
十八. arp报文(扫描局域网mac)
广播请求,对方单播应答。
#include <stdio.h>
#include <sys/cocket.h> //socket
#include <netinet/ether.h> //ETH_P_ALL
#include <sys/ioctl.h> //ioctl
#include <net/if.h> //struct ifreq
#include <string.h> //strncpy
#include <netpacket/packet.h> //struct sockaddr_ll
#include <arpa/inet.h> //inet_ntopint Sendto(int sockfd, unsigned char *msg, int len, char *name)
{//获取网络接口类型struct ifreq ethreq;strncpy(ethreq.ifr_name, name, IFNAMSIZ);ioctl(sockfd, SIOCGIFINDEX, ðreq);//定义一个网络接口变量struct sockaddr_ll sll;bzero(&sll, sizeof(sll));sll.sll_ifindex = ethreq.ifr_ifindex;len = sendto(sockfd, msg, len, 0, (struct sockaddr *)&sll, sizeof(sll));return len;
}int main(int argc, char const *argv[])
{//创建原始套接字int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));//组包unsigned char msg[1500] = {/*--------------以太网mac头部---------14字节--------*/0xff,0xff,0xff,0xff,0xff,0xff, /*目的mac地址*/0x00,0x0c,0x29,0x6e,0x18,0x47, /*源mac地址,既本地mac地址*/0x08,0x06, /*arp报文*//*--------------arp报文---------------28字节---*/0x00,0x01, /*硬件类型*/0x08,0x00, /*协议类型*/6, /*硬件地址长度*/4, /*协议地址长度*/0x00,0x01, /*arp选项1表示请求*/0x00,0x0c,0x29,0x6e,0x18,0x47, /*源mac地址,既本地mac地址*/10,9,21,201,/源IP*/0x00,0x00,0x00,0x00,0x00,0x00, /*目的mac地址*/10,9,21,244 /*目的mac地址*/}int len = 42;//发送报文Sendto(sockfd, msg, len, "eth0");//接收arp应答while(1){unsigned char buf[1500] = "";int len = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);//判断是否是arp应答unsigned short op = ntohs(*(unsigned short *)(buf + 20));if(op != 2){continue;}else //arp应答到来{ char src_mac[18] = "";sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x:",\buf[22+0],buf[22+1],buf[22+2],buf[22+3],buf[22+4],buf[22+5]);char src_ip[16] = "";inet_ntop(AF_INET, (void *)buf+28,src_ip,16);printf("%s----->%s\n",src_ip,src_mac);break;}}close(sockfd);return 0; }
千峰物联网__网络编程
【Linux网络编程】相关推荐
- Linux网络编程--进程间通信(一)
进程间通信简介(摘自<Linux网络编程>p85) AT&T 在 UNIX System V 中引入了几种新的进程通讯方式,即消息队列( MessageQueues),信号量( s ...
- Linux io模型及函数调用,Linux 网络编程的5种IO模型:信号驱动IO模型
Linux 网络编程的5种IO模型:信号驱动IO模型 背景 这一讲我们来看 信号驱动IO 模型. 介绍 情景引入: 在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个 ...
- Linux网络编程实例分析
最近由于工作原因需要温习一下Linux网络编程的部分基础知识,因此对之前写的Socket网络通信的代码进行了进一步优化和拓展,在不关闭一次Socket连接的基础上,对服务端加入循环读写的功能,同时加入 ...
- Linux网络编程必看书籍推荐
首先要说讲述计算机网络和TCP/IP的书很多. 先要学习网络知识才谈得上编程 讲述计算机网络的最经典的当属Andrew S.Tanenbaum的<计算机网络>第五版,这本书难易适中. &l ...
- [Linux网络编程学习笔记]套接字地址结构
好久没有看那Linux网络编程这本书了,今天看到了重点部分-TCP套接字.下面先来看看套接字的地址结构 Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构.在头文件<li ...
- linux网络编程二:基础socket, bind, listen, accept, connect
linux网络编程二:基础socket, bind, listen, accept, connect 1. 创建socket #include <sys/types.h> #inc ...
- linux网络编程常用函数详解与实例(socket--bind--listen--accept)
常用的网络命令: netstat 命令netstat是用来显示网络的连接,路由表和接口统计等网络的信息.netstat有许多的选项我们常用的选项是 -an 用来显示详细的网络状态.至于其它的选项我们可 ...
- 基于UDP客户端服务器的编程模型-linux网络编程
坚持在代码中注释,边读代码边学习Linux网络编程 使用到的发送函数原型: #include <sys/types.h>#include <sys/socket.h>ssize ...
- 【Linux】一步一步学Linux网络编程教程汇总(更新中......)
00. 目录 文章目录 00. 目录 01. 基础理论知识 02. 初级编程 03. 高级编程 04. LibEvent库 05. 06. 07. 01. 基础理论知识 [Linux网络编程]网络协议 ...
- 【Linux网络编程】并发服务器之select模型
00. 目录 文章目录 00. 目录 01. 概述 02. I/O复用技术概述 03. select模型服务器实现思路 04. select模型服务器实现 05. 附录 01. 概述 服务器设计技术有 ...
最新文章
- Jeff Dean回顾谷歌2021
- [HTML]增加input标签的multiple属性上传的文件数
- 两个排序数组合并第k或前k个最小值问题
- 14.并发与异步 - 2.任务Task -《果壳中的c#》
- XSD(Schema)教程
- 犀牛重建曲面_【教程】Rhino犀牛面包机建模教学(含模型领取)
- java hibernate 包_java – Hibernate映射包
- Qt QSsh 使用 windows Qt实现ssh客户端
- AD学习之旅(10)— 导入元器件到PCB文件
- 转《美国企业家宣言》
- Hive处理数据基本操作流程
- MSP430 G2553 单片机 口袋板 日历 时钟 闹钟 万年历 电子时钟 秒表显示
- win10系统无法识别USB设备?【系统天地】
- 行人检测0-05:LFFD-行人训练数据制作以及训练
- 《硅谷钢铁侠》听书笔记
- VUE/使用echarts格式化浮窗自定义按钮及事件
- winform pictureBox后台显示图片
- 2.Johann Hari: Everything you think you know about addiction is wrong | TED Talk
- 家庭必备!有你物联智能家居7大家居智能安防设备
- 有向有环图的层次布局算法(一)
热门文章
- VC编译zxing-cpp(一维码或二维码库)
- 全网最全的ChatGPT提示词
- Device eth1 has different MAC address
- 亚马逊运营如何优化Listing和广告词?
- 学习编程的五个关键点!你需要get它,并运用!
- Sprin中Bean的顺序
- 【数据分析中的常用模型】篇1:人货场模型:如何开好一家便利店
- 雄鹿助教大赞易建联 将成为移动性最好的长人
- 【K70例程】022I2S音频驱动_SGTL5000_SDHC_MQX_K70EKT7
- 微信小程序与oracle交互,微信小程序和web之间的交互