C/C++:Winsock网络编程—ping命令的简单实现

Winsock网络编程—ping命令的简单实现

前言

先声明 博主实现的是Windows平台的ping命令的简单实现,没有做域名解析,只能直接ping ip。我们要实现ping 肯定得先知道ping的实现原理,ping 发送的 ICMP报文。实际上的落脚点 就是对 ICMP协议和IP协议 结构的学习 以及 如何使用Winsock API 来实现ICMP报文的组包和解包。需要使用wireshark 抓包软件 配合学习,这样可以验证你分析的对不对。

网络协议基础知识

ip协议结构图:

  1. ping 中有显示 TTL 值,这个就是从ip头部中取得。
  2. ip头部的长度是从 IHL 中的值。
    在这里插入图片描述

icmp协议结构图:

在这里插入图片描述

类型和代码字段 所有情况如下,我们主要用的是 请求回显 和 回显应答
在这里插入图片描述

字节序问题

只有当 数据类型长度超过1个字节是 才会出现字节序问题。

网络传输的字节序和本地存储的字节序 有可能是一样 也有可能不一样。

网络字节序 是大端对齐模式(低地址 放高字节,高地址 放低字节)。

本地字节序 就要分CPU架构了,一般小型计算机 都是小端对齐模式(低地址 放低字节,高地址 放高字节)。

Winsock api 函数

完成 icmp报文的发送和接受 使用的api 函数有:

  1. setsockopt

https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-setsockopt

int WSAAPI setsockopt(
SOCKET s,
int level, // 选项级别
int optname, // 选项名
const char *optval, // 选项值
int optlen // 选项的长度
);

  1. sendto

https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-sendto

int WSAAPI sendto(
SOCKET s,
const char *buf,
int len,
int flags, // 调用模式flag
const sockaddr *to,
int tolen
);

  1. recvfrom

https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-recvfrom

int recvfrom(
SOCKET s,
char *buf,
int len,
int flags,
sockaddr *from, // 写出参数,为源地址
int *fromlen
);

  1. u_short ntohs(u_short hostshort); 网络字节序 转本地字节序

实现效果

先看下效果 再说
在这里插入图片描述

和 wireshark 抓包数据对比
在这里插入图片描述

实现代码

一次ICMP 报文请求的核心代码

int ping(char *szDestIp)
{
	//printf("destIp = %s\n",szDestIp);
    int bRet = 1;
    
    WSADATA wsaData;
    int nTimeOut = 1000;//1s  
    char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };
    icmp_header *pIcmp = (icmp_header *)szBuff;
    char icmp_data[32] = { 0 };

    WSAStartup(MAKEWORD(2, 2), &wsaData);
    // 创建原始套接字
    SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    
    // 设置接收超时
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));

    // 设置目的地址
    sockaddr_in dest_addr;
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);
    dest_addr.sin_port = htons(0);

    // 构造ICMP封包
    pIcmp->icmp_type = ICMP_ECHO_REQUEST;
    pIcmp->icmp_code = 0;
    pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
    pIcmp->icmp_sequence = 0;
    pIcmp->icmp_checksum = 0;

    // 填充数据,可以任意 
    memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);
    
    // 计算校验和
    pIcmp->icmp_checksum = chsum((struct icmp_header *)szBuff, sizeof(szBuff));

    sockaddr_in from_addr;
    char szRecvBuff[1024];
    int nLen = sizeof(from_addr);
    int ret,flag = 0; 

	DWORD  start = GetTickCount();
    ret = sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));
    //printf("ret = %d ,errorCode:%d\n",ret ,WSAGetLastError() ); 
   
	int i = 0; 
	//这里一定要用while循环,因为recvfrom 会接受到很多报文,包括 发送出去的报文也会被收到! 不信你可以用 wireshark 抓包查看,这个问题纠结来了一晚上 才猜想出来! 
    while(1){
    	if(i++ > 5){// icmp报文 如果到不了目标主机,是不会返回报文,多尝试几次接受数据,如果都没收到 即请求失败 
    		flag = 1;
    		break;
		}
    	memset(szRecvBuff,0,1024);
    	//printf("errorCode1:%d\n",WSAGetLastError() ); 
    	int ret = recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);
    	//printf("errorCode2:%d\n",WSAGetLastError() ); 
    	//printf("ret=%d,%s\n",ret,inet_ntoa(from_addr.sin_addr)) ; 
    	//接受到 目标ip的 报文 
    	if( strcmp(inet_ntoa(from_addr.sin_addr),szDestIp) == 0)  {
    		respNum++;
    		break;
		}
	} 
	
    
    	
	DWORD  end = GetTickCount();
	DWORD time = end -start; 
	
	if(flag){
		printf("请求超时。\n");
		return bRet;
	}
	sumTime += time;
	if( minTime > time){
		minTime = time;
	}
	if( maxTime < time){
		maxTime = time;
	}
	
	
	
	// Windows的原始套接字 开发,系统没有去掉IP协议头,需要程序自己处理。
	// ip头部的第一个字节(只有1个字节不涉及大小端问题),前4位 表示 ip协议版本号,后4位 表示IP 头部长度(单位为4字节)
	char ipInfo = szRecvBuff[0];
	// ipv4头部的第9个字节为TTL的值
	char ttl = szRecvBuff[8];
	//printf("ipInfo = %x\n",ipInfo);
	
	
	int ipVer = ipInfo >> 4;
	int ipHeadLen = ((char)( ipInfo << 4) >> 4) * 4;
	if( ipVer  == 4) {
		//ipv4 
		//printf("ipv4 len = %d\n",ipHeadLen);
		// 跨过ip协议头,得到ICMP协议头的位置,不过是网络字节序。 
		// 网络字节序 是大端模式 低地址 高位字节 高地址 低位字节。-> 转换为 本地字节序 小端模式 高地址高字节 低地址低字节   
		icmp_header* icmp_rep = (icmp_header*)(szRecvBuff + ipHeadLen);
		//由于校验和是 2个字节 涉及大小端问题,需要转换字节序 
		unsigned short checksum_host = ntohs(icmp_rep->icmp_checksum);// 转主机字节序 和wireshark 抓取的报文做比较 
			
		//printf("type = %d ,checksum_host = %x\n",icmp_rep,checksum_host);

		if(icmp_rep->icmp_type == 0){ //回显应答报文 
			//来自 61.135.169.121 的回复: 字节=32 时间=1ms TTL=57
			printf("来自 %s 的回复:字节=32 时间=%2dms TTL=%d checksum=0x%x \n", szDestIp, time, ttl, checksum_host);
		} else{
			bRet = 0;
			printf("请求超时。type = %d\n",icmp_rep->icmp_type);
		}	
	}else{
		// ipv6 icmpv6 和 icmpv4 不一样,要做对应的处理 
		//printf("ipv6 len = %d\n",ipLen); 
	} 
	
    return bRet;
}

代码下载

就一个C文件 可以用IDE打开运行,需要链接 ws2_32.lib 库。博主用的devc++,完整工程代码下载,或者Github最新代码

  • 14
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Windows编程的自动化测试中,我们可以使用C/C++编程语言来发送和接收Telnet命令。Telnet命令是一种远程登录协议,可用于通过网络连接到远程主机并执行命令。 要发送Telnet命令,我们可以使用C/C++网络编程库,如Winsock。首先,我们需要建立一个Socket连接到远程主机的Telnet端口(默认为23)。然后,我们可以使用Socket发送命令字符串到Telnet服务器。 例如,如果我们想要发送命令"ls"并接收输出,我们可以使用以下C/C++代码: ```cpp #include <winsock2.h> int main() { // 初始化Winsock WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 建立Socket连接到Telnet服务器 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(23); // Telnet默认端口 serverAddr.sin_addr.s_addr = inet_addr("远程主机IP地址"); // 替换为远程主机的IP地址 connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); // 发送Telnet命令 char command[] = "ls\r\n"; send(sock, command, strlen(command), 0); // 接收命令输出 char buffer[4096]; int bytesRead = recv(sock, buffer, sizeof(buffer), 0); if (bytesRead > 0) { // 处理命令输出 buffer[bytesRead] = '\0'; printf("接收到输出: %s", buffer); } // 关闭Socket连接 closesocket(sock); // 清理Winsock WSACleanup(); return 0; } ``` 以上代码演示了如何使用C/C++编程语言通过Telnet发送和接收命令。我们可以根据需要修改命令和远程主机的IP地址,以适应特定的自动化测试需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值