2019-07-16 16:27:00 qq_38474871 阅读数 170
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

    课程内容如下: 通信协议的概念 ISO、OSI七层参考模型详解 TCP、IP模型详解 网络接口层详解 IP协议详解 IP地址和MAC地址特征分析 有了IP地址为什么还要使用MAC地址 ARP工作过程及工作原理解析 ICMP知识解析 ping命令使用详解 tracert命令使用详解 TCP详解 UDP协议详解 网络通信协议(应用层)协议

    8297 人正在学习 去看看 深博

一、设计目的

PING程序是我们使用的比较多的用于测试网络连通性的程序。PING程序基于ICMP,使用ICMP的回送请求和回送应答来工作。由计算机网络课程知道,ICMP是基于IP的一个协议,ICMP包通过IP的封装之后传递。

课程设计中选取PING程序的设计,其目的是通过PING程序的设计,能初步掌握TCP/IP网络协议的基本实现方法,对网络的实现机制有进一步的认识。

熟悉SOCKET的编程,包括基本的系统调用如SOCKET、BIND等。

二、设计内容

2.1 RAW模式的SOCKET编程

PING程序是面向用户的应用程序,该程序使用ICMP的封装机制,通过IP协议来工作。为了实现直接对IP和ICMP包进行操作,实验中使用RAW模式的SOCKET编程。

2.2 具体内容

2.2.1 定义数据结构

定义IP数据报、ICMP包等相关的数据结构。

18702784-c11001e2c97c4852.png

点击此处下载源码

2011-08-08 21:37:27 cameory 阅读数 175
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

    课程内容如下: 通信协议的概念 ISO、OSI七层参考模型详解 TCP、IP模型详解 网络接口层详解 IP协议详解 IP地址和MAC地址特征分析 有了IP地址为什么还要使用MAC地址 ARP工作过程及工作原理解析 ICMP知识解析 ping命令使用详解 tracert命令使用详解 TCP详解 UDP协议详解 网络通信协议(应用层)协议

    8297 人正在学习 去看看 深博
基本需求: 定时测试被监控的设备是否可以ping通,如果ping不通的需要发出告警信息。

方案思路: 运用java调用服务器的自身命令来简单实现ping功能 ,本文只是讲述如何运用Java简单实现Ping的功能,至于告警信息的发送方式有很多种(比如短信 、邮件 、Syslog 、MSN 等等),在以前的文章中已经描述过,这里就不再一一详述了。

实现方式 : 根据不同的情况可分为如下两种
直接调用监控服务器的ping命令去测试需要监控的设备
通过指定服务器测试能否ping通 需要监控的设备 (运用Mina实现 )
下面将给出上述的两种实现的详细过程:

一、直接调用服务器本身的ping命令
TestPingCmd.java
Java代码
package michael.net;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @blog http://sjsky.iteye.com
* @author Michael
*/
public class TestPingCmd {

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {

// 读取txt文件中的IP列表
TestPingCmd pinger = new TestPingCmd();
List<String> iplist = pinger
.getIpListFromTxt("d:/test/idc_ping_ip.txt");

// List<String> iplist = new ArrayList<String>();
// iplist.add("222.*.*.*");
// iplist.add("222.*.*.*");
// iplist.add("222.*.*.*");
// iplist.add("222.*.*.*");
// iplist.add("222.*.*.*");
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(50, 60, 60,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50),
new ThreadPoolExecutor.CallerRunsPolicy());
long startTime = System.currentTimeMillis();
final int maxCount = 4;
for (final String ip : iplist) {
executorPool.execute(new Runnable() {
public void run() {
TestPingCmd pinger = new TestPingCmd();
Integer countSucce = pinger.doPingCmd(ip, maxCount);
if (null != countSucce) {
System.out.println("host:[ " + ip + " ] ping cout: "
+ maxCount + " success: " + countSucce);

} else {
System.out
.println("host:[ " + ip + " ] ping cout null");
}
}

});
}
while (executorPool.getActiveCount() > 0) {
Thread.sleep(100);
}
System.out.println("complete ping jobs count = " + iplist.size()
+ " , total used time(ms) = "
+ (System.currentTimeMillis() - startTime));
executorPool.shutdown();
}

/**
* @param destIp
* @param maxCount
* @return
*/
public Integer doPingCmd(String destIp, int maxCount) {
LineNumberReader input = null;
try {
String osName = System.getProperties().getProperty("os.name");
String pingCmd = null;
if (osName.startsWith("Windows")) {
pingCmd = "cmd /c ping -n {0} {1}";
pingCmd = MessageFormat.format(pingCmd, maxCount, destIp);
} else if (osName.startsWith("Linux")) {
pingCmd = "ping -c {0} {1}";
pingCmd = MessageFormat.format(pingCmd, maxCount, destIp);
} else {
System.out.println("not support OS");
return null;
}
Process process = Runtime.getRuntime().exec(pingCmd);
InputStreamReader ir = new InputStreamReader(process
.getInputStream());
input = new LineNumberReader(ir);
String line;
List<String> reponse = new ArrayList<String>();

while ((line = input.readLine()) != null) {
if (!"".equals(line)) {
reponse.add(line);
// System.out.println("====:" + line);
}
}
if (osName.startsWith("Windows")) {
return parseWindowsMsg(reponse, maxCount);
} else if (osName.startsWith("Linux")) {
return parseLinuxMsg(reponse, maxCount);
}

} catch (IOException e) {
System.out.println("IOException " + e.getMessage());

} finally {
if (null != input) {
try {
input.close();
} catch (IOException ex) {
System.out.println("close error:" + ex.getMessage());

}
}
}
return null;
}

private int parseWindowsMsg(List<String> reponse, int total) {
int countTrue = 0;
int countFalse = 0;
for (String str : reponse) {
if (str.startsWith("来自") || str.startsWith("Reply from")) {
countTrue++;
}
if (str.startsWith("请求超时") || str.startsWith("Request timed out")) {
countFalse++;
}
}
return countTrue;
}

private int parseLinuxMsg(List<String> reponse, int total) {
int countTrue = 0;
for (String str : reponse) {
if (str.contains("bytes from") && str.contains("icmp_seq=")) {
countTrue++;
}
}
return countTrue;
}

/**
* @param filepath
* @return list
*/
public List<String> getIpListFromTxt(String filepath) {
BufferedReader br = null;
List<String> iplist = new ArrayList<String>();
try {
File file = new File(filepath);
br = new BufferedReader(new FileReader(file));
while (br.ready()) {
String line = br.readLine();
if (null != line && !"".equals(line)) {
iplist.add(line);
}
}
} catch (Exception e) {
e.printStackTrace(System.out);

} finally {
if (null != br) {
try {
br.close();
} catch (Exception ex) {
ex.printStackTrace(System.out);
}
}
}
return iplist;
}

}

读取文件中IP列表(1001个IP)运行结果:
complete ping jobs count = 1001 , total used time(ms) = 483536


二、通过指定服务器去ping测试

主要思路:利用Mina在指定的第三方服务器上运行server端,然后实现客户端和 第三方 服务器建立socket连接,发送ping任务的消息给第三方服务器,第三方服务器再把执行结果实时反馈给客户端。
代码包括四个类:
服务端:PingServerIoHandler.java PingServer.java
客户端:PingClientIoHandler.java PingClient.java
Java代码
package michael.mina.ping;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.text.MessageFormat;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;

/**
* @blog http://sjsky.iteye.com
* @author Michael
*/
public class PingServerIoHandler extends IoHandlerAdapter {
private String logId = "SERVER:: ";
private int msgCount = 0;

@Override
public void exceptionCaught(IoSession pSession, Throwable pCause)
throws Exception {
System.out.println(logId + "发生异常:" + pCause.getLocalizedMessage());
}

@Override
public void messageReceived(IoSession pSession, Object pMessage)
throws Exception {
String msg = String.valueOf(pMessage);
msgCount++;
System.out.println(logId + "收到客户端第 " + msgCount + " 条消息:" + msg);
pSession.write(msgCount);

if (msg.startsWith("ping")) {
String destIp = msg.split(" ")[1];
doPingCmd(pSession, destIp);
}

}

@Override
public void messageSent(IoSession pSession, Object pMessage)
throws Exception {
System.out.println(logId + "发出消息:" + pMessage);
}

@Override
public void sessionClosed(IoSession pSession) throws Exception {
System.out.println(logId + "one client closed ");
}

@Override
public void sessionCreated(IoSession pSession) throws Exception {
System.out.println(logId + "sessionCreated ");
}

@Override
public void sessionIdle(IoSession pSession, IdleStatus pStatus)
throws Exception {
super.sessionIdle(pSession, pStatus);
}

@Override
public void sessionOpened(IoSession pSession) throws Exception {
System.out.println(logId + "sessionOpened ");
}

private Integer doPingCmd(IoSession pSession, String destIp) {
LineNumberReader input = null;
int maxCount = 4;
try {
String osName = System.getProperties().getProperty("os.name");
String pingCmd = null;
if (osName.startsWith("Windows")) {
pingCmd = "cmd /c ping -n {0} {1}";
pingCmd = MessageFormat.format(pingCmd, maxCount, destIp);
} else if (osName.startsWith("Linux")) {
pingCmd = "ping -c {0} {1}";
pingCmd = MessageFormat.format(pingCmd, maxCount, destIp);
} else {
System.out.println("not support OS");
return null;
}
Process process = Runtime.getRuntime().exec(pingCmd);
InputStreamReader ir = new InputStreamReader(process
.getInputStream());
input = new LineNumberReader(ir);
String line;

while ((line = input.readLine()) != null) {
if (!"".equals(line)) {
pSession.write(line);
}
}
} catch (IOException e) {
System.out.println("IOException " + e.getMessage());

} finally {
if (null != input) {
try {
input.close();
} catch (IOException ex) {
System.out.println("close error:" + ex.getMessage());

}
}
}
return null;
}
}

Java代码
package michael.mina.ping;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

/**
* @blog http://sjsky.iteye.com
* @author Michael
*/
public class PingServer {

private static final int PORT = 54321;

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
IoAcceptor acceptor = new NioSocketAcceptor();

acceptor.getFilterChain().addLast("logger", new LoggingFilter());
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"))));
acceptor.setHandler(new PingServerIoHandler());
acceptor.bind(new InetSocketAddress(PORT));

System.out.println("服务端已启动,监听端口:" + PORT);

}

}


Java代码
package michael.mina.ping;

import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;

/**
* @blog http://sjsky.iteye.com
* @author Michael
*/
public class PingClientIoHandler extends IoHandlerAdapter {

private String logId = "CLIENT:: ";

@Override
public void exceptionCaught(IoSession pSession, Throwable pCause)
throws Exception {
System.out.println(logId + "发生异常:" + pCause.getLocalizedMessage());
}

@Override
public void messageReceived(IoSession pSession, Object pMessage)
throws Exception {
String count = String.valueOf(pMessage);
System.out.println(logId + "服务端收到的消息数 = " + count);
}

@Override
public void messageSent(IoSession pSession, Object pMessage)
throws Exception {
System.out.println(logId + "发出消息:" + pMessage);
}

@Override
public void sessionClosed(IoSession pSession) throws Exception {
System.out.println(logId + "one client closed ");
}

@Override
public void sessionCreated(IoSession pSession) throws Exception {
System.out.println(logId + "sessionCreated ");
}

@Override
public void sessionIdle(IoSession pSession, IdleStatus pStatus)
throws Exception {
super.sessionIdle(pSession, pStatus);
}

@Override
public void sessionOpened(IoSession pSession) throws Exception {
System.out.println(logId + "sessionOpened ");
}
}

Java代码
package michael.mina.ping;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.SocketConnector;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

/**
* @blog http://sjsky.iteye.com
* @author Michael
*/
public class PingClient {

private static final int PORT = 54321;

/**
* IP列表
* @param ipList
*/
public void createPingClient(List<String> ipList) {
SocketConnector connector = new NioSocketConnector();

DefaultIoFilterChainBuilder chain = connector.getFilterChain();

// 设定过滤器一行一行读取数据
chain.addLast("codec", new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.forName("UTF-8"))));

// 注册消息处理器
connector.setHandler(new PingClientIoHandler());
connector.setConnectTimeoutMillis(30 * 1000L);
// 连接服务器
ConnectFuture cf = connector.connect(new InetSocketAddress("127.0.0.1",
54321));
cf.awaitUninterruptibly();
IoSession session = cf.getSession();
for (String ip : ipList) {
session.write("ping " + ip);
}
session.getCloseFuture().awaitUninterruptibly();
connector.dispose();
System.out.println("-------------------");
}

/**
* 控制台输入
* @param ipList
*/
public void createPingClient() {
SocketConnector connector = new NioSocketConnector();

DefaultIoFilterChainBuilder chain = connector.getFilterChain();

// 设定过滤器一行一行读取数据
chain.addLast("codec", new ProtocolCodecFilter(
new TextLineCodecFactory(Charset.forName("UTF-8"))));

// 注册消息处理器
connector.setHandler(new PingClientIoHandler());
connector.setConnectTimeoutMillis(30 * 1000L);
// 连接服务器
ConnectFuture cf = connector.connect(new InetSocketAddress("127.0.0.1",
54321));
cf.awaitUninterruptibly();
IoSession session = cf.getSession();
Scanner input = new Scanner(System.in).useDelimiter("\\r\\n");
while (input.hasNext()) {
String s = input.next();
if (s.equals("quit")) {
break;
}
session.write(s);
}
// cf.getSession().getCloseFuture().awaitUninterruptibly();
connector.dispose();
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
PingClient tester = new PingClient();
List<String> iplist = new ArrayList<String>();
iplist.add("192.168.8.89");
iplist.add("192.168.8.93");
iplist.add("192.168.8.109");
iplist.add("192.168.8.117");
iplist.add("192.168.8.118");
tester.createPingClient(iplist);
}

}

BTW:先运行server端,在运行client端( )。
server端日志如下:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
服务端已启动,监听端口:54321
SERVER:: sessionCreated
SERVER:: sessionOpened
SERVER:: 收到客户端第 1 条消息:ping 192.168.8.89
SERVER:: 发出消息:1
SERVER:: 收到客户端第 2 条消息:ping 192.168.8.93
SERVER:: 收到客户端第 3 条消息:ping 192.168.8.109
SERVER:: 收到客户端第 4 条消息:ping 192.168.8.117
SERVER:: 收到客户端第 5 条消息:ping 192.168.8.118
SERVER:: 发出消息:正在 Ping 192.168.8.89 具有 32 字节的数据:
SERVER:: 发出消息:来自 192.168.8.89 的回复: 字节=32 时间=5ms TTL=60
SERVER:: 发出消息:来自 192.168.8.89 的回复: 字节=32 时间=3ms TTL=59
SERVER:: 发出消息:来自 192.168.8.89 的回复: 字节=32 时间=4ms TTL=59
SERVER:: 发出消息:来自 222.73.88.89 的回复: 字节=32 时间=41ms TTL=59
SERVER:: 发出消息:192.168.8.89 的 Ping 统计信息:
SERVER:: 发出消息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
SERVER:: 发出消息:往返行程的估计时间(以毫秒为单位):
SERVER:: 发出消息: 最短 = 3ms,最长 = 41ms,平均 = 13ms
SERVER:: 发出消息:2
SERVER:: 发出消息:正在 Ping 192.168.8.93 具有 32 字节的数据:
SERVER:: 发出消息:来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
SERVER:: 发出消息:来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
SERVER:: 发出消息:来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
SERVER:: 发出消息:来自 192.168.8.93 的回复: 字节=32 时间=3ms TTL=59
SERVER:: 发出消息:192.168.8.93 的 Ping 统计信息:
SERVER:: 发出消息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
SERVER:: 发出消息:往返行程的估计时间(以毫秒为单位):
SERVER:: 发出消息: 最短 = 3ms,最长 = 4ms,平均 = 3ms
SERVER:: 发出消息:3
SERVER:: 发出消息:正在 Ping 192.168.8.109 具有 32 字节的数据:
SERVER:: 发出消息:来自 192.168.8.109 的回复: 字节=32 时间=13ms TTL=249
SERVER:: 发出消息:来自 192.168.8.109 的回复: 字节=32 时间=36ms TTL=249
SERVER:: 发出消息:来自 192.168.8.109 的回复: 字节=32 时间=61ms TTL=249
SERVER:: 发出消息:来自 192.168.8.109 的回复: 字节=32 时间=83ms TTL=249
SERVER:: 发出消息:192.168.8.109 的 Ping 统计信息:
SERVER:: 发出消息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
SERVER:: 发出消息:往返行程的估计时间(以毫秒为单位):
SERVER:: 发出消息: 最短 = 13ms,最长 = 83ms,平均 = 48ms
SERVER:: 发出消息:4
SERVER:: 发出消息:正在 Ping 192.168.8.117 具有 32 字节的数据:
SERVER:: 发出消息:来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
SERVER:: 发出消息:来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
SERVER:: 发出消息:来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
SERVER:: 发出消息:来自 192.168.8.117 的回复: 字节=32 时间=5ms TTL=249
SERVER:: 发出消息:192.168.8.117 的 Ping 统计信息:
SERVER:: 发出消息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
SERVER:: 发出消息:往返行程的估计时间(以毫秒为单位):
SERVER:: 发出消息: 最短 = 4ms,最长 = 5ms,平均 = 4ms
SERVER:: 发出消息:5
SERVER:: 发出消息:正在 Ping 192.168.8.118 具有 32 字节的数据:
SERVER:: 发出消息:来自 192.168.8.118 的回复: 字节=32 时间=3ms TTL=251
SERVER:: 发出消息:来自 192.168.8.118 的回复: 字节=32 时间=3ms TTL=251
SERVER:: 发出消息:来自 192.168.8.118 的回复: 字节=32 时间=5ms TTL=251
SERVER:: 发出消息:来自 192.168.8.118 的回复: 字节=32 时间=4ms TTL=251
SERVER:: 发出消息:192.168.8.118 的 Ping 统计信息:
SERVER:: 发出消息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
SERVER:: 发出消息:往返行程的估计时间(以毫秒为单位):
SERVER:: 发出消息: 最短 = 3ms,最长 = 5ms,平均 = 3ms

client端日志如下 :
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
CLIENT:: sessionCreated
CLIENT:: sessionOpened
CLIENT:: 发出消息:ping 192.168.8.89
CLIENT:: 发出消息:ping 192.168.8.93
CLIENT:: 发出消息:ping 192.168.8.109
CLIENT:: 发出消息:ping 192.168.8.117
CLIENT:: 发出消息:ping 192.168.8.118
CLIENT:: 服务端收到的消息数 = 1
CLIENT:: 服务端收到的消息数 = 正在 Ping 192.168.8.89 具有 32 字节的数据:
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.89 的回复: 字节=32 时间=5ms TTL=60
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.89 的回复: 字节=32 时间=3ms TTL=59
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.89 的回复: 字节=32 时间=4ms TTL=59
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.89 的回复: 字节=32 时间=41ms TTL=59
CLIENT:: 服务端收到的消息数 = 192.168.8.89 的 Ping 统计信息:
CLIENT:: 服务端收到的消息数 = 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
CLIENT:: 服务端收到的消息数 = 往返行程的估计时间(以毫秒为单位):
CLIENT:: 服务端收到的消息数 = 最短 = 3ms,最长 = 41ms,平均 = 13ms
CLIENT:: 服务端收到的消息数 = 2
CLIENT:: 服务端收到的消息数 = 正在 Ping 192.168.8.93 具有 32 字节的数据:
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.93 的回复: 字节=32 时间=4ms TTL=59
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.93 的回复: 字节=32 时间=3ms TTL=59
CLIENT:: 服务端收到的消息数 = 192.168.8.93 的 Ping 统计信息:
CLIENT:: 服务端收到的消息数 = 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
CLIENT:: 服务端收到的消息数 = 往返行程的估计时间(以毫秒为单位):
CLIENT:: 服务端收到的消息数 = 最短 = 3ms,最长 = 4ms,平均 = 3ms
CLIENT:: 服务端收到的消息数 = 3
CLIENT:: 服务端收到的消息数 = 正在 Ping 192.168.8.109 具有 32 字节的数据:
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.109 的回复: 字节=32 时间=13ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.109 的回复: 字节=32 时间=36ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.109 的回复: 字节=32 时间=61ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.109 的回复: 字节=32 时间=83ms TTL=249
CLIENT:: 服务端收到的消息数 = 192.168.8.109 的 Ping 统计信息:
CLIENT:: 服务端收到的消息数 = 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
CLIENT:: 服务端收到的消息数 = 往返行程的估计时间(以毫秒为单位):
CLIENT:: 服务端收到的消息数 = 最短 = 13ms,最长 = 83ms,平均 = 48ms
CLIENT:: 服务端收到的消息数 = 4
CLIENT:: 服务端收到的消息数 = 正在 Ping 192.168.8.117 具有 32 字节的数据:
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.117 的回复: 字节=32 时间=4ms TTL=249
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.117 的回复: 字节=32 时间=5ms TTL=249
CLIENT:: 服务端收到的消息数 = 192.168.8.117 的 Ping 统计信息:
CLIENT:: 服务端收到的消息数 = 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
CLIENT:: 服务端收到的消息数 = 往返行程的估计时间(以毫秒为单位):
CLIENT:: 服务端收到的消息数 = 最短 = 4ms,最长 = 5ms,平均 = 4ms
CLIENT:: 服务端收到的消息数 = 5
CLIENT:: 服务端收到的消息数 = 正在 Ping 192.168.8.118 具有 32 字节的数据:
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.118 的回复: 字节=32 时间=3ms TTL=251
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.118 的回复: 字节=32 时间=3ms TTL=251
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.118 的回复: 字节=32 时间=5ms TTL=251
CLIENT:: 服务端收到的消息数 = 来自 192.168.8.118 的回复: 字节=32 时间=4ms TTL=251
CLIENT:: 服务端收到的消息数 = 192.168.8.118 的 Ping 统计信息:
CLIENT:: 服务端收到的消息数 = 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
CLIENT:: 服务端收到的消息数 = 往返行程的估计时间(以毫秒为单位):
CLIENT:: 服务端收到的消息数 = 最短 = 3ms,最长 = 5ms,平均 = 3ms
2015-10-10 19:44:41 u014634338 阅读数 9949
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

    课程内容如下: 通信协议的概念 ISO、OSI七层参考模型详解 TCP、IP模型详解 网络接口层详解 IP协议详解 IP地址和MAC地址特征分析 有了IP地址为什么还要使用MAC地址 ARP工作过程及工作原理解析 ICMP知识解析 ping命令使用详解 tracert命令使用详解 TCP详解 UDP协议详解 网络通信协议(应用层)协议

    8297 人正在学习 去看看 深博

一、IP协议

IP协议是TCP/IP协议族所依赖的传送机制,提供无连接不可靠的数据报服务。IP的无连接特性意味着每个IP报文都是独立寻径的,因此当一个源主机发送多个报文给同一目的主机时,这些报文可能出现错序,丢失或者部分报文产生错误等现象,因此为了保证数据传送的可靠性,必须在IP层之上通过TCP协议提供有序,带确认数据的传输服务。

1.IP协议格式

IP报文由报文头部和数据两部分构成,其中头部信息格式如下图所示,头部占20-60个字节,无选项option时,头部为20字节,最多可以携带40字节选项,报文最大长度为65535字节。


(1)版本(version)  4比特,定义了当前IP协议的版本,目前通常是数字4,即IPV4

(2)头部长度(ihl)   4比特,按4字节单位定义IP报文的头部总长度,因此未携带任何选项的IP报文头部长度为20字节,则ihl值为5(5*4=20),当选项长度达到最大值40字节时,ihl长度为15 (15*4=60)。

(3)服务类型(tos)  8比特,用于指示路由器将如何处理IP报文

(4)总长度(tot_len)16比特,报文头部加数据的总长度,IP报文携带的上层数据长度为:数据长度=总长度-头部长度=总长度-(ihl*4)。之所以需要总长度这个字段,是因为在某些情况下底层协议为了满足最小帧长的限制,会添加填充数据,例如以太协议要求每个数据帧最小必须为46字节,当来自上层的IP报文总长度小于46字节时,将添加填充数据以满足最小帧长,于是必须通过总长度这个字段来记录实际IP层报文的总长度,参考如图所示:


(5)报文标识(id)  16比特,用于标识多个IP分段所对应的原始IP分组的ID。

(6)分段标识(frag)3比特,用于声明一个IP报文是否是某个原始报文的分段,或者声明是否允许一个IP原始报文被分段。

(7)分段偏移(offset) 13比特,标识一个IP分段的数据在原始IP报文中的偏移值,注意该值,必须是8的整数倍。

(8)生存时间(ttl)   8比特, 一个IP报文在网上所允许的最大生存时间,该值实际为最大跳数,当源主机产生一个IP报文后,该字段将填写一个初始值,随后该报文每经过一个路由器则路由器将对该字段值进行减一操作,当该字段值变成0后,路由器将丢弃此报文。

(9)协议(protocol) 8比特,用于标识IP报文承载的上层数据的协议类型,例如可以是TCP,UDP,ICMP和IGMP等。

(10)头部校验和(check) 16比特,IP头部数据的检验和。

(11)源地址(saddr) 32比特,报文的源IP地址。

(12)目的地址(daddr)32比特,报文的目的IP地址。

(13)选项(option) 变长且最大不超过40字节。


2.IP协议头的c语言定义

struct iphdr
{
#if defined _LITTLE_ENDIAN_BITFIELD //小端机
    u8 hlen:4,
ver: 4;
#elif defined _BIG_ENFIAN_BITFELD  //大端机
    u8 ver:4,
hlen:4;
#endif
    
    u8 tos;
    u16 tot_len;
    u16 id;
    u16 frag_off;
    u8 ttl;
    u8 protocol;
    u16 check;
    u32 saddr;
    u32 daddr;
};

二、ICMP协议

(1)ICMP消息类型

ICMP消息分为两大类,错误报告消息和查询消息,这里仅介绍查询消息,每个查询消息类型均包括一对请求和应答消息。


(2)ICMP消息通用格式

ICMP消息包括8字节的头部和变长数据两个部分,其中所有消息类型头部的前4个字节均相同,头部其余4个字节随消息的不同而不同。如图所示:


ICMP消息头部的头4个字节分别是消息类型tye,消息代码code和校验和checksum,其中checksum字段包括头部和数据两部分,而并非仅头部,查询消息的数据部分data包含了用于查询所需要的额外数据。

(3)ICMP查询请求和应答消息格式

ICMP回应请求(echo-request)和应答消息(echo-reply)用于诊断两个系统(主机或路由器)之间是否能够进行通信,当其中一方发送回应请求消息给另一方时,接收到回应请求消息的主机或者路由器将以应答消息进行应答,常用的网络ping命令就是基于此消息类型的,如下图所示 其中type字段为8表示回应请求,0表示应答,code字段暂未是要你管,为0.


(4)ICMP消息格式的C语言定义

 

struct icmphdr
{
    u8 type;
    u8 code;
    u16 checksum;
    union
    {
        struct
        {
            u16 id;
            u16 sequence;
        }echo;
        
        u32 gateway;
        struct
        {
            u16 unused;
            u16 mtu;
        }frag; //pmtu发现
    }un;
    
    //u32  icmp_timestamp[2];//时间戳
    //ICMP数据占位符
    u8 data[0];
#define icmp_id un.echo.id
#define icmp_seq un.echo.sequence
};

ping程序实现:

#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>   
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netdb.h>
#include<errno.h>
#include<arpa/inet.h>
#include<signal.h>
#include<netinet/in.h>

#ifndef _LITTLE_ENDIAN_BITFIELD
#define _LITTLE_ENDIAN_BITFIELD
#endif

#define IP_HSIZE sizeof(struct iphdr)   //定义IP_HSIZE为ip头部长度
#define IPVERSION  4   //定义IPVERSION为4,指出用ipv4



#define ICMP_ECHOREPLY 0 //Echo应答
#define ICMP_ECHO      8 //Echo请求

#define BUFSIZE 1500     //发送缓存最大值
#define DEFAULT_LEN 56   //ping 消息数据默认大小

//数据类型别名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

//ICMP消息头部

struct icmphdr
{
    u8 type;
    u8 code;
    u16 checksum;
    union
    {
        struct
        {
            u16 id;
            u16 sequence;
        }echo;
        
        u32 gateway;
        struct
        {
            u16 unused;
            u16 mtu;
        }frag; //pmtu发现
    }un;
    u32  icmp_timestamp[2];//时间戳
    //ICMP数据占位符
    u8 data[0];
#define icmp_id un.echo.id
#define icmp_seq un.echo.sequence
};

#define ICMP_HSIZE sizeof(struct icmphdr)
struct iphdr
{
#if defined _LITTLE_ENDIAN_BITFIELD
    u8 hlen:4,
ver: 4;
#elif defined _BIG_ENFIAN_BITFELD
    u8 ver:4,
hlen:4;
#endif
    
    u8 tos;
    u16 tot_len;
    u16 id;
    u16 frag_off;
    u8 ttl;
    u8 protocol;
    u16 check;
    u32 saddr;
    u32 daddr;
};
char hello[]="hello this is  a ping test.";
char *hostname; //被ping的主机
int  datalen=DEFAULT_LEN;//ICMP消息携带的数据长度
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int nsent;//发送的ICMP消息序号
int nrecv;
pid_t pid;//ping程序的进程pid
struct timeval recvtime; //收到ICMP应答的时间戳
int sockfd; //发送和接收原始套接字
struct sockaddr_in dest;//被ping主机的ip
struct sockaddr_in from;//发送ping应答消息的主机ip

struct sigaction act_alarm;
struct sigaction act_int;


//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒
struct itimerval val_alarm;

//函数原型
void alarm_handler(int);//SIGALRM处理程序
void int_handler(int);//SIGINT处理程序
void set_sighandler();//设置信号处理程序
void send_ping();//发送ping消息
void recv_reply();//接收ping应答
u16 checksum(u8 *buf,int len);//计算校验和
int handle_pkt();//ICMP应答消息处理
void get_statistics(int ,int);//统计ping命令的检测结果
void bail(const char *);//错误报告
int main(int argc,char **argv)  //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数
{
    val_alarm.it_interval.tv_sec = 1;
    val_alarm .it_interval.tv_usec=0;
    val_alarm  .it_value.tv_sec=0;
    val_alarm  .it_value.tv_usec=1;
    struct hostent *host; //该结构体属于include<netdb.h>
    int on =1;
    
    if((host=gethostbyname(argv[1]))==NULL)
    {    //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针,
        perror("can not understand the host name");   //理解不了输入的地址
        exit(1);
    }
    
    hostname=argv[1];//取出地址名
    
    memset(&dest,0,sizeof dest);  //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零
    dest.sin_family=PF_INET;  //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族
    dest.sin_port=ntohs(0);   //端口号,ntohs()返回一个以主机字节顺序表达的数。
    dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化
    
    if((sockfd = socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
    { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功
        perror("raw socket created error");
        exit(1);
    }
    
    setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。
    pid=getpid(); //getpid函数用来取得目前进程的进程识别码
    printf("PID:%d\n",pid);
    set_sighandler();//对信号处理
    printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen);
    
    if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数
        bail("setitimer fails.");
    
    
    recv_reply();//接收ping应答
    
    return 0;
}
//发送ping消息
void send_ping()
{
    struct iphdr *ip_hdr;   //iphdr为IP头部结构体
    struct icmphdr *icmp_hdr;   //icmphdr为ICMP头部结构体
    int len;
    int len1;
    icmp_hdr=(struct icmphdr *)(sendbuf);  //字符串指针
    icmp_hdr->type=ICMP_ECHO;    //初始化ICMP消息类型type
    icmp_hdr->code=0;    //初始化消息代码code
    icmp_hdr->icmp_id=pid;   //把进程标识码初始给icmp_id
    icmp_hdr->icmp_seq=nsent++;  //发送的ICMP消息序号赋值给icmp序号
    gettimeofday((struct timeval *)icmp_hdr->icmp_timestamp,NULL); // 获取当前时间
    memcpy(icmp_hdr->data, hello, strlen(hello));
    
    len=ICMP_HSIZE+strlen(hello);
    icmp_hdr->checksum=0;    //初始化
    icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len);  //计算校验和
    
  //  printf("The send pack checksum is:0x%x\n",icmp_hdr->checksum);
    sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据
}
//接收程序发出的ping命令的应答
void recv_reply()
{
    int n;
    socklen_t len;
    int errno;
    
    n=nrecv=0;
    len=sizeof(from);   //发送ping应答消息的主机IP
    
    while(nrecv<4)
    {
        if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0)
        { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.
            if(errno==EINTR)  //EINTR表示信号中断
                continue;
            bail("recvfrom error");
        }
        
        gettimeofday(&recvtime,NULL);   //记录收到应答的时间
        
        if(handle_pkt())    //接收到错误的ICMP应答信息
            
            continue;
        nrecv++;
    }
    
    get_statistics(nsent,nrecv);     //统计ping命令的检测结果
}
//计算校验和
u16 checksum(u8 *buf,int len)
{
    u32 sum=0;
    u16 *cbuf;
    
    cbuf=(u16 *)buf;
    
    while(len>1)
    {
        sum+=*cbuf++;
        len-=2;
    }
    
    if(len)
        sum+=*(u8 *)cbuf;
    
    sum=(sum>>16)+(sum & 0xffff);
    sum+=(sum>>16);
    
    return ~sum;
}
//ICMP应答消息处理
int handle_pkt()
{
    struct iphdr *ip;
    struct icmphdr *icmp;
    
    int ip_hlen;
    u16 ip_datalen; //ip数据长度
    double rtt; // 往返时间
    struct timeval *sendtime;
    
    ip=(struct iphdr *)recvbuf;
    
    ip_hlen=ip->hlen << 2;
    ip_datalen=ntohs(ip->tot_len)-ip_hlen;
    
    icmp=(struct icmphdr *)(recvbuf+ip_hlen);
    
    u16 sum=(u16)checksum((u8 *)icmp,ip_datalen);
   // printf("The recv pack checksum is:0x%x\n",sum);
    if(sum) //计算校验和
        return -1;
    
    
    if(icmp->icmp_id!=pid)
        return -1;
    if(icmp->type!=ICMP_ECHOREPLY)
        return -1;
    
    sendtime=(struct timeval *)icmp->icmp_timestamp; //发送时间
    rtt=((&recvtime)->tv_sec-sendtime->tv_sec)*1000+((&recvtime)->tv_usec-sendtime->tv_usec)/1000.0;// 往返时间
    //打印结果
    printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n",
           ip_datalen, //IP数据长度
           inet_ntoa(from.sin_addr),    //目的ip地址
           icmp->icmp_seq, //icmp报文序列号
           ip->ttl,  //生存时间
           rtt);    //往返时间
    
    return 0;
}
//设置信号处理程序
void set_sighandler()
{
    act_alarm.sa_handler=alarm_handler;
    if(sigaction(SIGALRM,&act_alarm,NULL)==-1)  //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。
        bail("SIGALRM handler setting fails.");
    
    act_int.sa_handler=int_handler;
    if(sigaction(SIGINT,&act_int,NULL)==-1)
        bail("SIGALRM handler setting fails.");
}
//统计ping命令的检测结果
void get_statistics(int nsent,int nrecv)
{
    printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。
    printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n",
           nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);
}
//错误报告
void bail(const char * on_what)
{
    fputs(strerror(errno),stderr);  //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。
    fputs(":",stderr);
    fputs(on_what,stderr);
    fputc('\n',stderr); //送一个字符到一个流中
    exit(1);
}

//SIGINT(中断信号)处理程序
void int_handler(int sig)
{
    get_statistics(nsent,nrecv);    //统计ping命令的检测结果
    close(sockfd);  //关闭网络套接字
    exit(1);
}
//SIGALRM(终止进程)处理程序
void alarm_handler(int signo)
{
    send_ping();    //发送ping消息
}


程序结果(注意这里的PID):

下面我们在对我们写的ping程序进行抓包分析

这是通过抓包抓到的我们发的ICMP请求包,当然还有ICMP应答报,可以先看看后面的id,和seq,后面我们会再次提到,首先我们对其中一个包展开分析:

这里显示的是IP报文字段,可以看到我们前面所展示的IP报头里面所包含的东西,比较简单,就不一一分析了。

然后我们在来看看我我们ICMP报文:

可以看到,搜下是类型type=8,说明是个ICMP请求,code=0,然后是校验和,接下来是标识符,这里怎么有两个呢,其实一个是大端表示,一个是小端表示的结果,这里的标识符是我们的进程ID,可以看我前面ping的时候输出了进程ID,然后是序列号,可以对照几个包分析,开始序列号(大端和小端表示)是为0,然后一次递增,这是我们在程序里面所设定的。接下来是时间戳,开始的时候icmp结构里面我没有放时间戳,这样系统会放在数据位置,我来我单独放了一个时间戳结构这样把它和data分开,关于时间戳大小我也是抓包分析得出来的,是8个字节。接下来便是我们的数据部分,为了很好的区分数据区域和前面的头,特别把数据部分拿出来分析:

可以看到数据部分恰好是从我们自己定义的数据地方开始的,数据前面就是时间戳,开始调试的时候,数据总是被覆盖,后来才找出了里面的时间戳占了8个字节。所以调整后刚好合适。当然不同的类型和代码会导致后面的结构有些不一样,这需要调整。

上面的代码中ICMP和IP都是我们自己定义的。系统中也有提供相应的结构,我们直接调用就可以了,不过必须先查看里面结构是如何定义的。在我这里,ICMP中的数据是单独定义在外面的,不是一起放在里面的,所以,数据部分需要自己声明一个结构,具体名称可以查看自己系统里面多对应的头文件。

我这上面是这样的:

struct icmp_filter *icmp_data;

注意一下下面几句:

    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE);
    gettimeofday((struct timeval *)icmp_data,NULL); // 获取当前时间
    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE+sizeof(timeval)); //真正数据地方
    memcpy(icmp_data, hello, strlen(hello));
    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); //恢复数据指针

首先找到数据开始相应的位置,然后在开始的地方放时间戳,然后在找到真正数据开始的地方,写入数据,不要随意移动数据头指针,否则结果会有异常的。


下面是其实现:

#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netdb.h>
#include<errno.h>
#include<arpa/inet.h>
#include<signal.h>
#include<netinet/in.h>
#include<linux/ip.h>
#include <linux/icmp.h>


#ifndef _LITTLE_ENDIAN_BITFIELD
#define _LITTLE_ENDIAN_BITFIELD
#endif

#define IP_HSIZE sizeof(struct iphdr)   //定义IP_HSIZE为ip头部长度
#define IPVERSION  4   //定义IPVERSION为4,指出用ipv4
#define ICMP_HSIZE sizeof(struct icmphdr)


#define ICMP_ECHOREPLY 0 //Echo应答
#define ICMP_ECHO      8 //Echo请求

#define BUFSIZE 1500     //发送缓存最大值
#define DEFAULT_LEN 56   //ping 消息数据默认大小

//数据类型别名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

char hello[]="hello this is  a ping test.";
struct icmp_filter *icmp_data;

char *hostname; //被ping的主机
int  datalen=DEFAULT_LEN;//ICMP消息携带的数据长度
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int nsent;//发送的ICMP消息序号
int nrecv;
pid_t pid;//ping程序的进程pid
struct timeval recvtime; //收到ICMP应答的时间戳
int sockfd; //发送和接收原始套接字
struct sockaddr_in dest;//被ping主机的ip
struct sockaddr_in from;//发送ping应答消息的主机ip

struct sigaction act_alarm;
struct sigaction act_int;


//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒
struct itimerval val_alarm;

//函数原型
void alarm_handler(int);//SIGALRM处理程序
void int_handler(int);//SIGINT处理程序
void set_sighandler();//设置信号处理程序
void send_ping();//发送ping消息
void recv_reply();//接收ping应答
u16 checksum(u8 *buf,int len);//计算校验和
int handle_pkt();//ICMP应答消息处理
void get_statistics(int ,int);//统计ping命令的检测结果
void bail(const char *);//错误报告
int main(int argc,char **argv)  //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数
{
    val_alarm.it_interval.tv_sec = 1;
    val_alarm .it_interval.tv_usec=0;
    val_alarm  .it_value.tv_sec=0;
    val_alarm  .it_value.tv_usec=1;
    struct hostent *host; //该结构体属于include<netdb.h>
    int on =1;
    
    if(argc<2){      //判断是否输入了地址
        printf("Usage: %s hostname\n",argv[0]);
        exit(1);
    }
    
    if((host=gethostbyname(argv[1]))==NULL)
    {    //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针,
        perror("can not understand the host name");   //理解不了输入的地址
        exit(1);
    }
    
    hostname=argv[1];//取出地址名
    
    memset(&dest,0,sizeof dest);  //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零
    dest.sin_family=PF_INET;  //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族
    dest.sin_port=ntohs(0);   //端口号,ntohs()返回一个以主机字节顺序表达的数。
    dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化
    
    if((sockfd = socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
    { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功
        perror("raw socket created error");
        exit(1);
    }
    
    setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。
    pid=getpid(); //getpid函数用来取得目前进程的进程识别码
    printf("PID:%d\n",pid);
    set_sighandler();//对信号处理
    printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen);
    
    if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数
        bail("setitimer fails.");
    
    
    recv_reply();//接收ping应答
    
    return 0;
}
//发送ping消息
void send_ping()
{
    
    struct ip *ip_hdr;   //iphdr为IP头部结构体
    struct icmphdr *icmp_hdr;   //icmphdr为ICMP头部结构体
    int len;
    int len1;
    icmp_hdr=(struct icmphdr *)(sendbuf);  //字符串指针
    icmp_hdr->type=ICMP_ECHO;//初始化ICMP消息类型type
    
    icmp_hdr->code=0;    //初始化消息代码code
    icmp_hdr->un.echo.id=pid;   //把进程标识码初始给icmp_id
    icmp_hdr->un.echo.sequence=nsent++;  //发送的ICMP消息序号赋值给icmp序号
    
    
    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE);
    gettimeofday((struct timeval *)icmp_data,NULL); // 获取当前时间
    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE+sizeof(timeval)); //真正数据地方
    memcpy(icmp_data, hello, strlen(hello));
    icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); //恢复数据指针
    len=ICMP_HSIZE+sizeof(icmp_filter)+strlen(hello);
    icmp_hdr->checksum=0;    //初始化
    icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len);  //计算校验和
    
    printf("The send pack checksum is:0x%x\n",icmp_hdr->checksum);
    sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据
}
//接收程序发出的ping命令的应答
void recv_reply()
{
    int n;
    socklen_t len;
    int errno;
    
    n=nrecv=0;
    len=sizeof(from);   //发送ping应答消息的主机IP
    
    while(nrecv<4)
    {
        if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0)
        { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.
            if(errno==EINTR)  //EINTR表示信号中断
                continue;
            bail("recvfrom error");
        }
        
        gettimeofday(&recvtime,NULL);   //记录收到应答的时间
        
        if(handle_pkt())    //接收到错误的ICMP应答信息
            
            continue;
        nrecv++;
    }
    
    get_statistics(nsent,nrecv);     //统计ping命令的检测结果
}
//计算校验和
u16 checksum(u8 *buf,int len)
{
    u32 sum=0;
    u16 *cbuf;
    
    cbuf=(u16 *)buf;
    
    while(len>1)
    {
        sum+=*cbuf++;
        len-=2;
    }
    
    if(len)
        sum+=*(u8 *)cbuf;
    
    sum=(sum>>16)+(sum & 0xffff);
    sum+=(sum>>16);
    
    return ~sum;
}
//ICMP应答消息处理
int handle_pkt()
{
    struct iphdr *ip;
    struct icmphdr *icmp;
    
    int ip_hlen;
    u16 ip_datalen; //ip数据长度
    double rtt; // 往返时间
    struct timeval *sendtime;
    
    ip=(struct iphdr *)recvbuf;
    
    ip_hlen=ip->ihl << 2;
    ip_datalen=ntohs(ip->tot_len)-ip_hlen;
    
    icmp=(struct icmphdr *)(recvbuf+ip_hlen);
    
    u16 sum=(u16)checksum((u8 *)icmp,ip_datalen);
    printf("The recv pack checksum is:0x%x\n",sum);
    if(sum) //计算校验和
        return -1;
    
    
    if(icmp->un.echo.id!=pid)
        return -1;
    if(icmp->type!=ICMP_ECHOREPLY)
        return -1;
    
    sendtime=(struct timeval *)icmp_data; //发送时间
    rtt=((&recvtime)->tv_sec-sendtime->tv_sec)*1000+((&recvtime)->tv_usec-sendtime->tv_usec)/1000.0;// 往返时间
    //打印结果
    printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n",
           ip_datalen, //IP数据长度
           inet_ntoa(from.sin_addr),    //目的ip地址
           icmp->un.echo.sequence, //icmp报文序列号
           ip->ttl,  //生存时间
           rtt);    //往返时间
    
    return 0;
}
//设置信号处理程序
void set_sighandler()
{
    act_alarm.sa_handler=alarm_handler;
    if(sigaction(SIGALRM,&act_alarm,NULL)==-1)  //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。
        bail("SIGALRM handler setting fails.");
    
    act_int.sa_handler=int_handler;
    if(sigaction(SIGINT,&act_int,NULL)==-1)
        bail("SIGALRM handler setting fails.");
}
//统计ping命令的检测结果
void get_statistics(int nsent,int nrecv)
{
    printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。
    printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n",
           nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);
}
//错误报告
void bail(const char * on_what)
{
    fputs(strerror(errno),stderr);  //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。
    fputs(":",stderr);
    fputs(on_what,stderr);
    fputc('\n',stderr); //送一个字符到一个流中
    exit(1);
}

//SIGINT(中断信号)处理程序
void int_handler(int sig)
{
    get_statistics(nsent,nrecv);    //统计ping命令的检测结果
    close(sockfd);  //关闭网络套接字
    exit(1);
}
//SIGALRM(终止进程)处理程序
void alarm_handler(int signo)
{
    send_ping();    //发送ping消息
    
}

测试平台:Ubuntu 14.04 +gcc 4.8.4

下面是改良版,删除了没必要的东西,重写了部分代码,看着要好很多,也很好理解:

#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netdb.h>
#include<errno.h>
#include<arpa/inet.h>
#include<signal.h>
#include<netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_systm.h>
#define BUFSIZE 1500     //发送缓存最大值

//数据类型别名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

char hello[]="hello this is  a ping test.";

char *hostname; //被ping的主机
int  datalen=56;//ICMP消息携带的数据长度
char sendbuf[BUFSIZE];
char recvbuf[BUFSIZE];
int nsent;//发送的ICMP消息序号
int nrecv;
pid_t pid;//ping程序的进程pid
struct timeval recvtime; //收到ICMP应答的时间戳
int sockfd; //发送和接收原始套接字
struct sockaddr_in dest;//被ping主机的ip
struct sockaddr_in from;//发送ping应答消息的主机ip

struct sigaction act_alarm;
struct sigaction act_int;


//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒
struct itimerval val_alarm;

//函数原型
void alarm_handler(int);//SIGALRM处理程序
void int_handler(int);//SIGINT处理程序
void set_sighandler();//设置信号处理程序
void send_ping();//发送ping消息
void recv_reply();//接收ping应答
u16 checksum(u8 *buf,int len);//计算校验和
int handle_pkt(int len);//ICMP应答消息处理
void get_statistics(int ,int);//统计ping命令的检测结果
void bail(const char *);//错误报告
int main(int argc,char **argv)  //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数
{
    val_alarm.it_interval.tv_sec = 1;
    val_alarm .it_interval.tv_usec=0;
    val_alarm  .it_value.tv_sec=0;
    val_alarm  .it_value.tv_usec=1;
    struct hostent *host; //该结构体属于include<netdb.h>
    int on =1;
    
    if(argc<2){      //判断是否输入了地址
        printf("Usage: %s hostname\n",argv[0]);
        exit(1);
    }
    
    if((host=gethostbyname(argv[1]))==NULL)
    {    //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针,
        perror("can not understand the host name");   //理解不了输入的地址
        exit(1);
    }
    
    hostname=argv[1];//取出地址名
    
    memset(&dest,0,sizeof dest);  //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零
    dest.sin_family=PF_INET;  //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族
    dest.sin_port=ntohs(0);   //端口号,ntohs()返回一个以主机字节顺序表达的数。
    dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化
    
    if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
    { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功
        perror("raw socket created error");
        exit(1);
    }
    
    setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。
    pid=getpid(); //getpid函数用来取得目前进程的进程识别码
    printf("PID:%d\n",pid);
    set_sighandler();//对信号处理
    printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen);
    
    if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数
        bail("setitimer fails.");
    
    
    recv_reply();//接收ping应答
    
    return 0;
}
//发送ping消息
void send_ping()
{
    
    struct ip *ip;   //ip为IP头部结构体
    struct icmp *icmp;   //icmp为ICMP头部结构体
    int len;
    int len1;
    icmp=(struct icmp *)(sendbuf);  //字符串指针
    icmp->icmp_type=ICMP_ECHO;//初始化ICMP消息类型type
    
    icmp->icmp_code=0;    //初始化消息代码code
    icmp->icmp_id=pid;   //把进程标识码初始给icmp_id
    icmp->icmp_seq=nsent++;  //发送的ICMP消息序号赋值给icmp序号
    gettimeofday((struct timeval *)icmp->icmp_data,NULL); // 获取当前时间
    memcpy(icmp->icmp_data+sizeof(timeval), hello, strlen(hello));
    len=8+sizeof(timeval)+strlen(hello);
    printf("%d\n",len);
    icmp->icmp_cksum=0;    //初始化
    icmp->icmp_cksum=checksum((u8 *)icmp,len);  //计算校验和
    sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据
}
//接收程序发出的ping命令的应答
void recv_reply()
{
    int n;
    socklen_t len;
    int errno;
    n=nrecv=0;
    
    while(nrecv<4)
    {
        if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0)
        { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.
            if(errno==EINTR)  //EINTR表示信号中断
                continue;
            bail("recvfrom error");
        }
        
        gettimeofday(&recvtime,NULL);   //记录收到应答的时间
        
        if(handle_pkt(n))    //接收到错误的ICMP应答信息
            
            continue;
        nrecv++;
    }
    
    get_statistics(nsent,nrecv);     //统计ping命令的检测结果
}
//计算校验和
u16 checksum(u8 *buf,int len)
{
    u32 sum=0;
    u16 *cbuf;
    
    cbuf=(u16 *)buf;
    
    while(len>1)
    {
        sum+=*cbuf++;
        len-=2;
    }
    
    if(len)
        sum+=*(u8 *)cbuf;
    
    sum=(sum>>16)+(sum & 0xffff);
    sum+=(sum>>16);
    
    return ~sum;
}
//ICMP应答消息处理
int handle_pkt(int len)
{
    struct ip *ip;
    struct icmp *icmp;
    
    int ip_hlen,icmplen;
    double rtt; // 往返时间
    struct timeval *sendtime;
    
    ip=(struct ip *)recvbuf;
    
    ip_hlen=ip->ip_hl<< 2;
    icmp=(struct icmp *)(recvbuf+ip_hlen);
    icmplen=len-ip_hlen;
    if(icmp->icmp_id!=pid)
        return -1;
    if(icmp->icmp_type!=ICMP_ECHOREPLY)
        return -1;
    
    sendtime=(struct timeval *)icmp->icmp_data; //发送时间
    
    if((recvtime.tv_usec-=sendtime->tv_usec)<0)
    {
        recvtime.tv_sec--;
        recvtime.tv_usec+=1000000;
    }
    recvtime.tv_sec-=sendtime->tv_sec;
    
    rtt=recvtime.tv_sec*1000.0+recvtime.tv_usec/1000.0;// 往返时间
    //打印结果
    printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n",
           icmplen, //icmp数据长度
           inet_ntoa(from.sin_addr),    //目的ip地址
           icmp->icmp_seq, //icmp报文序列号
           ip->ip_ttl,  //生存时间
           rtt);    //往返时间
    
    return 0;
}
//设置信号处理程序
void set_sighandler()
{
    act_alarm.sa_handler=alarm_handler;
    if(sigaction(SIGALRM,&act_alarm,NULL)==-1)  //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。
        bail("SIGALRM handler setting fails.");
    
    act_int.sa_handler=int_handler;
    if(sigaction(SIGINT,&act_int,NULL)==-1)
        bail("SIGALRM handler setting fails.");
}
//统计ping命令的检测结果
void get_statistics(int nsent,int nrecv)
{
    printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。
    printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n",
           nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);
}
//错误报告
void bail(const char * on_what)
{
    fputs(strerror(errno),stderr);  //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。
    fputs(":",stderr);
    fputs(on_what,stderr);
    fputc('\n',stderr); //送一个字符到一个流中
    exit(1);
}

//SIGINT(中断信号)处理程序
void int_handler(int sig)
{
    get_statistics(nsent,nrecv);    //统计ping命令的检测结果
    close(sockfd);  //关闭网络套接字
    exit(1);
}
//SIGALRM(终止进程)处理程序
void alarm_handler(int signo)
{
    send_ping();    //发送ping消息
    
}



2011-09-03 15:11:00 weixin_30338481 阅读数 4
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

    课程内容如下: 通信协议的概念 ISO、OSI七层参考模型详解 TCP、IP模型详解 网络接口层详解 IP协议详解 IP地址和MAC地址特征分析 有了IP地址为什么还要使用MAC地址 ARP工作过程及工作原理解析 ICMP知识解析 ping命令使用详解 tracert命令使用详解 TCP详解 UDP协议详解 网络通信协议(应用层)协议

    8297 人正在学习 去看看 深博

UNP1里面给出了一个ping程序的实现,里面包含了ipv4和ipv6两个版本。

经过学习,对里面的代码做了一点点小得修改(还原了基本的API),再加了一点注释,测试可以通过。

经过手敲了这段代码,收获还是很大的。对raw socket的编程有了基本的概念,同时也对icmp包和ip包有了更深入的了解。

修改后的代码如下,总共分为三个文件:

ping.h

#include<stdio.h>
#include<time.h>
#include<errno.h>
#include<unistd.h>
#include<stdlib.h>
#include<netdb.h>
#include<string.h>
#include<strings.h>
#include<netinet/in.h>
#include<pthread.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
#include<signal.h>
#define BUFSIZE 1500

char sendbuf[BUFSIZE];

int datalen;
char *host;
int nsent;
pid_t pid;
int sockfd;
int verbose;

void proc_v4(char *,ssize_t,struct msghdr*,struct timeval *); 
void send_v4(void);
void readloop(void);
void sig_alarm(int);
void tv_sub(struct timeval *,struct timeval *); 

struct proto{
    void (*fproc)(char *,ssize_t,struct msghdr *,struct timeval *); 
    void (*fsend)(void);
    void (*finit)(void);
    struct sockaddr *sasend;
    struct sockaddr *sarecv;
    socklen_t salen;
    int icmpproto;
}*pr;

main.c:获取目标ip

#include"ping.h"
struct proto proto_v4={proc_v4,send_v4,NULL,NULL,NULL,0,IPPROTO_ICMP};

int datalen=56;
struct addrinfo *host_serv(const char *host,const char *serv,int family,int socktype)
{
    int n;
    struct addrinfo hints,*res;
    
    bzero(&hints,sizeof(hints));
    hints.ai_flags=AI_CANONNAME;
    hints.ai_family=family;
    hints.ai_socktype=socktype;
    if((n=getaddrinfo(host,serv,&hints,&res))!=0){
        return NULL;    
    }   
    return (res);
}

int main(int argc,char *argv[])
{
    int c;  
    struct addrinfo *ai;
    char h[20]={0};
    opterr=0;
    while((c=getopt(argc,argv,"v"))!=-1){
        switch(c){
            case 'v':
                verbose++;
                break;  
            case '?':
                printf("unrecognized option: %c\n",c);
                return 0;
        }   
    
    }   
    if(optind!=argc-1)
        printf("usage: ping [-v] <hostname>\n");
    host=argv[optind];
    pid=getpid() & 0xffff;
    signal(SIGALRM,sig_alarm);
    ai=host_serv(host,NULL,0,0);
    inet_ntop(AF_INET,&((struct sockaddr_in*)(ai->ai_addr))->sin_addr,h,sizeof(h));
    printf("PING %s (%s):%d data bytes\n",
            ai->ai_canonname?ai->ai_canonname:h,h,datalen);

    if(ai->ai_family==AF_INET){
        pr=&proto_v4;
    }else{
        printf("unknown address family %d\n",ai->ai_family);
    }

    pr->sasend=ai->ai_addr;
    pr->sarecv=(struct sockaddr*)calloc(1,ai->ai_addrlen);
    pr->salen=ai->ai_addrlen;
    readloop();
    exit(0);
}

readloop.c:发送和接收icmp包

#include"ping.h"

//get rtt
void tv_sub(
        struct timeval *out,    //time,tv_sev is microseconds
        struct timeval *in)
{
    if((out->tv_usec-=in->tv_usec)<0){
        --out->tv_sec;
        out->tv_sec+=1000000;
    }
    out->tv_sec-=in->tv_sec;
}

void proc_v4(char *ptr,ssize_t len,struct msghdr *msg,struct timeval * tvrecv)
{
    int hlen1,icmplen;
    char host[20];
    double rtt;
    struct ip *ip;
    struct icmp *icmp;
    struct timeval *tvsend;

    ip=(struct ip*)ptr;
    hlen1=ip->ip_hl<<2;//get ipdatagram length,include option
    if(ip->ip_p!=IPPROTO_ICMP){
        return;
    }
    icmp=(struct icmp*)(ptr+hlen1);
    if((icmplen=len-hlen1)<8)
        return;
    if(icmp->icmp_type==ICMP_ECHOREPLY){
        if(icmp->icmp_id!=pid)
            return;
        if(icmplen<16)
            return;
        tvsend=(struct timeval*)icmp->icmp_data;
        tv_sub(tvrecv,tvsend);
        rtt=tvrecv->tv_sec*1000.0+tvrecv->tv_usec/1000.0;
        printf("%d bytes from %s:seq=%u,ttl=%d,rtt=%.3f ms\n",icmplen,inet_ntop(AF_INET,&((struct sockaddr_in*)(pr->sarecv))->sin_addr,host,sizeof(host)),icmp->icmp_seq,ip->ip_ttl,rtt);
    }
}

//call send data func
void sig_alarm(int signo)
{
    (*pr->fsend)();
    alarm(1);
    return;
}

//create checksum
uint16_t in_cksum(uint16_t *addr,int len)
{
    int nleft=len;
    uint32_t sum=0;
    uint16_t *w=addr;
    uint16_t answer=0;

    while(nleft>1){
        sum+=*w++;
        nleft-=2;
    }
    if(nleft==1){
        *(unsigned char *)(&answer)=*(unsigned char *)w;
        sum+=answer;
    }
    sum=(sum >> 16)+(sum & 0xffff);
    sum+=(sum>>16);
    answer=~sum;
    return answer;
}

//send icmp data
void send_v4(void){
    int len;
    struct icmp *icmp;

    //init icmp datagram
    icmp=(struct icmp*)sendbuf;
    icmp->icmp_type=ICMP_ECHO;  //type
    icmp->icmp_code=0;          //code
    icmp->icmp_id=pid;
    icmp->icmp_seq=nsent++;
    gettimeofday((struct timeval *)icmp->icmp_data,NULL);//get send time
    len=8+datalen;
    icmp->icmp_cksum=0;
    icmp->icmp_cksum=in_cksum((u_short *)icmp,len);

    sendto(sockfd,sendbuf,len,0,pr->sasend,pr->salen);//send data
}

void readloop(void){
    int i=0;
    int size;
    char recvbuf[BUFSIZE];  //get the response
    char controlbuf[BUFSIZE];
    struct msghdr msg;
    struct iovec iov;       //send data
    ssize_t n;
    struct timeval tval;

    sockfd=socket(pr->sasend->sa_family,SOCK_RAW,pr->icmpproto);

    //set effective uid to real uid
    setuid(getuid());
    if(pr->finit)
        (*pr->finit)();

    size=60*1024;
    setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));//set recvbuf size

    sig_alarm(SIGALRM);//cal fun

    //init buffer
    iov.iov_base=recvbuf;   //set recvbuf
    iov.iov_len=sizeof(recvbuf);//recvbuf len
    msg.msg_name=pr->sarecv;//sockaddr
    msg.msg_iov=&iov;
    msg.msg_iovlen=1;
    msg.msg_control=controlbuf;

    //loop ping
    for(;;){
        msg.msg_namelen=pr->salen;
        msg.msg_controllen=sizeof(controlbuf);
        n=recvmsg(sockfd,&msg,0);   //receive data
        if(n<0){
            if(errno=EINTR)
                continue;
            else
                printf("recvmsg error\n");
        }
        gettimeofday(&tval,NULL);//receive time
        (*pr->fproc)(recvbuf,n,&msg,&tval);//handle receive data
    }
}

 

转载于:https://www.cnblogs.com/aLittleBitCool/archive/2011/09/03/2165423.html

2015-02-01 18:59:44 stpeace 阅读数 10321
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

    课程内容如下: 通信协议的概念 ISO、OSI七层参考模型详解 TCP、IP模型详解 网络接口层详解 IP协议详解 IP地址和MAC地址特征分析 有了IP地址为什么还要使用MAC地址 ARP工作过程及工作原理解析 ICMP知识解析 ping命令使用详解 tracert命令使用详解 TCP详解 UDP协议详解 网络通信协议(应用层)协议

    8297 人正在学习 去看看 深博

       ping功能很常用, 在Windows/Linux上, 我们经常用ping功能来探测对方是否在线。 那么, ping到底是怎么实现的呢? 在本文中, 我们自己来写一个ping, 当然, 功能肯定没有Windows/Linux自带的ping那么强大。 但是, 自己写写, 可以理解更深刻。

       说明, 为了简便起见, 程序中的众多异常, 我就不考虑了, 只聚焦主要功能。


       好, 直接上代码:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
	

// IP数据包头结构(如果不清楚每个字段的含义, 请查看IP报文格式, 我就不对每个字段进行注释了)
typedef struct iphdr 
{
	unsigned int headLen:4;
	unsigned int version:4;
	unsigned char tos;
	unsigned short totalLen;
	unsigned short ident;
	unsigned short fragAndFlags;
	unsigned char ttl;
	unsigned char proto;
	unsigned short checkSum;
	unsigned int sourceIP;
	unsigned int destIP;
}IpHeader;


// ICMP数据头结构
typedef struct ihdr 
{
	unsigned char iType;
	unsigned char iCode;	
	unsigned short iCheckSum;							
	unsigned short iID;
	unsigned short iSeq;
	unsigned long  timeStamp;
}IcmpHeader;


// 计算ICMP包的校验和(发送前要用)
unsigned short checkSum(unsigned short *buffer, int size) 
{
	unsigned long ckSum = 0;

	while(size > 1)
	{
		ckSum += *buffer++;
		size -= sizeof(unsigned short);
	}

	if(size) 
	{
		ckSum += *(unsigned char*)buffer;
	}	

	ckSum = (ckSum >> 16) + (ckSum & 0xffff);
	ckSum += (ckSum >>16);

	return unsigned short(~ckSum);
}


// 填充ICMP请求包的具体参数
void fillIcmpData(char *icmpData, int dataSize)
{
	IcmpHeader *icmpHead = (IcmpHeader*)icmpData;
	icmpHead->iType = 8;  // 8表示请求包
	icmpHead->iCode = 0;
	icmpHead->iID = (unsigned short)GetCurrentThreadId();
	icmpHead->iSeq = 0;
	icmpHead->timeStamp = GetTickCount();
	char *datapart = icmpData + sizeof(IcmpHeader);
	memset(datapart, 'x', dataSize - sizeof(IcmpHeader)); // 数据部分为xxx..., 实际上有32个x
	icmpHead->iCheckSum = checkSum((unsigned short*)icmpData, dataSize); // 千万要注意, 这个一定要放到最后
}


// 对返回的IP数据包进行解析,定位到ICMP数据
int decodeResponse(char *buf, int bytes, struct sockaddr_in *from, int tid)
{
	IpHeader *ipHead = (IpHeader *)buf;
	unsigned short ipHeadLen = ipHead->headLen * 4 ; 
	if (bytes < ipHeadLen + 8) // ICMP数据不完整, 或者不包含ICMP数据
	{
		return -1; 
	}

	IcmpHeader *icmpHead = (IcmpHeader*)(buf + ipHeadLen);  // 定位到ICMP包头的起始位置
	if (icmpHead->iType != 0) 	// 0表示回应包
	{
		return -2; 
	}

	if (icmpHead->iID != (unsigned short)tid) // 理应相等
	{ 
		return -3; 
	}

	int time = GetTickCount() - (icmpHead->timeStamp); // 返回时间与发送时间的差值
	if(time >= 0)
	{
		return time;
	}

	return -4; // 时间错误
}


// ping操作
int ping(const char *ip, unsigned int timeout)
{
	// 网络初始化
	WSADATA wsaData;
	WSAStartup(MAKEWORD(1, 1), &wsaData);
	unsigned int sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);	// 注意, 第三个参数非常重要, 指定了是icmp
	setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));  // 设置套接字的接收超时选项
	setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));  // 设置套接字的发送超时选项

	// 准备要发送的数据
	int  dataSize = sizeof(IcmpHeader) + 32; // 待会儿会有32个x
	char icmpData[1024] = {0};
	fillIcmpData(icmpData, dataSize);
	unsigned long startTime = ((IcmpHeader *)icmpData)->timeStamp;

	// 远程通信端	
	struct sockaddr_in dest;
	memset(&dest, 0, sizeof(dest));
	struct hostent *hp = gethostbyname(ip);
	memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
	dest.sin_family = hp->h_addrtype;

	// 发送数据
	sendto(sockRaw, icmpData, dataSize, 0, (struct sockaddr*)&dest, sizeof(dest));

	int iRet = -1;
	struct sockaddr_in from;
	int fromLen = sizeof(from);	
	while(1)
	{
		// 接收数据
		char recvBuf[1024] = {0};
		int iRecv = recvfrom(sockRaw, recvBuf, 1024, 0, (struct sockaddr*)&from, &fromLen);
		int time  = decodeResponse(recvBuf, iRecv, &from, GetCurrentThreadId());
		if(time >= 0)
		{
			iRet = 0;   // ping ok
			break;
		}
		else if( GetTickCount() - startTime >= timeout || GetTickCount() < startTime)
		{
			iRet = -1;  // ping超时
			break;
		}
	}

	// 释放网络
	closesocket(sockRaw);
	WSACleanup();

	return iRet;
}


// 主函数
int main()
{
	// 请用合法的ip地址, 如果ip地址不合法(最好用程序校验一下), 可能产生程序错误。 
	char szIPs[][16] = 
	{
		"192.168.1.1",    // 我的网关
		"192.168.1.100",  // 我的PC   (wifi接入)
		"192.168.1.101"   // 我的手机 (wifi接入)
	};

	int i = 0;
	int size = sizeof(szIPs) / sizeof(szIPs[0]);
	for(i = 0; i < size; i++)
	{
		int iRet = ping(szIPs[i], 3000); // 超时时间为3000ms

		if(0 == iRet)
		{
			printf("%s 在线\n", szIPs[i]);
		}
		else if(-1 == iRet)
		{
			printf("%s 超时\n", szIPs[i]);
		}
		else
		{
			printf("未知错误");
		}
	}

	return 0;
}

      结果:
192.168.1.1 在线
192.168.1.100 在线
192.168.1.101 在线

     在上述过程中, wireshark抓包结果为:

       有一点值得注意, 当arp缓存表中有ip-mac映射值时, Windows/Linux和我上面的ping不再发arp.  否则需要发arp.

       然后, 我把我手机的wifi关掉, 程序运行的结果为:

192.168.1.1 在线
192.168.1.100 在线
192.168.1.101 超时


       折腾一下午, 总算是基本搞出来了。 吃饭去得意


没有更多推荐了,返回首页