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

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

    8102 人正在学习 去看看 深博

一、设计目的

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 阅读数 142
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

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

    8102 人正在学习 去看看 深博
基本需求: 定时测试被监控的设备是否可以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
2018-07-27 16:04:56 huaxiandao 阅读数 3064
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

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

    8102 人正在学习 去看看 深博

       作为从事网络工作的人来说,了解ping的一些知识是非常有必要的,因此,趁着最近的功夫,借着网络上的一些前辈的博客,自己也动手写了一个简单的ping命令实现,主要参考的博主文章链接如下:

https://www.cnblogs.com/skyfsm/p/6348040.html?utm_source=itdadao&utm_medium=referral

       由于我们仅仅只是写一个ping的应用程序,所以其实也没有那么困难,但是还是需要有一些基本知识的了解,例如,ping时候基于icmp协议实现的,那起码要了解基本的icmp这种协议吧,不然连包都不会组就尴尬了,除此之外,包的收发其实与其他收发都类似,无非就是send/recv的过程,具体细节具体分析。

        为了是文章更丰满,这里还是简单说一下ICMP协议,ICMP是位于网络层的协议,是为网关和目标主机提供一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发送方,因此就存在差错报文从差错点发回源主机的流程,这就可能要经过多个子网,牵涉到路由选择的问题,因此ICMP报文基于IP协议来进行转发,所以ICMP数据包的发送需要进行两级封装,其报文构造大体如下图所示:

IP head一般是20个字节,其头部结构如下图所示:

ICMP head一般是8个字节,其头部结构如下图所示:

ICMP报文种类大体分为两类,询问报文和差错报文,我们通常ping的时候发送和回应的就是询问报文类,下面表格列出了几个常见的ICMP报文type

通过表格可以了解,当我们ping的时候,发送的icmp报文,type应该是8,而回复的icmp报文,type应该是0,这个在后面写ping的时候需要我们注意了。

下面就开始详细介绍ping代码了。

ping代码详解

(1)ICMP包的组装

/**
 * para: @icmphdr --- icmp报文的头部字段信息
 *       @seq     --- icmp头部字段中的seq序号,icmp报文的请求和应答这个字段要一致
 *       @length  --- icmp报文长度,不包括ip头
 * func:icmp报文头部信息填充,需要注意,icmp_cksum最开始一定要先赋值0        
 **/ 
void icmp_pack(struct icmp *icmphdr, int seq, int length)
{
    struct timeval *tvstart;
	
    icmphdr->icmp_type = ICMP_REQUEST;
    icmphdr->icmp_code = 0;
    icmphdr->icmp_cksum = 0;
    icmphdr->icmp_seq = seq;    
    icmphdr->icmp_id = pid & 0xffff;
    tvstart = (struct timeval *)icmphdr->icmp_data;
    gettimeofday(tvstart, NULL);
    icmphdr->icmp_cksum = cal_chksum((unsigned short *)icmphdr, length);    
}

        这里解释一下个字段的含义:

        icmp_type: 就是上面介绍icmp报文时的type字段,指明该报文的类型。

        icmp_code: code是子类型的意思,因为每个type下面可能还细分了不同的小类型,就由这个code标识。

        icmp_cksum: icmp报文校验值,必须先赋值为0,后面再计算填充,关于校验的算法后面会介绍。

        icmp_seq: 报文序列号,可用作发送报文顺序,举个例子,ping 发出icmp请求包每发送一个递增,可用于报文统计。

        icmp_id: 报文标识符,用于标识报文的,请求和应答该字段一致才表示正常收发来回,一般可填充进程号。

       除了这些报文头,icmp报文中还有数据字段,为了进行时间统计,常将时间信息存放于icmp_data字段。

(2)校验和计算

/**
 *para: @addr: --- 待计算校验和的报文
 *      @len: --- 报文长度
 *func: ICMP报文校验和计算,按2字节的反码和算法
 **/
unsigned short cal_chksum(unsigned short *addr, int len)
{
    int nleft;
    int sum;
    unsigned short *tmp;

    nleft = len;
    tmp = addr;
	sum = 0;
    while (nleft > 1) {
        sum += *tmp++;
        nleft-=2;
    }

    if (nleft == 1) {
        sum += *(unsigned char *)tmp;
    }

#if 0 
    while (sum >> 16) {
        sum = (sum & 0xffff) + (sum >> 16);
    }
#endif
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum>>16);//上一步可能产生溢出

    return (unsigned short)(~sum);   
}

        翻阅了大部分网上的程序资料,基本上都是这种写法,估计是经典套路,所以得到了大家的公认。这段代码虽然简单,但是

有的地方刚开始我也不是很理解,例如上面函数倒数第二句和第三句,为什么要有这个操作?经过多方查资料才知道:

        首先要理解反码的计算方式:

                                        反码求和之后,如果高位有进位,需要加到低位,所以才有第一句话,那为什么还有下面一句?主要是因为增加进位之后,sum又可能出现进位,所以就又加了一次,那么问题又来了,那要是再进位咋办?事实上,进行这两次操作之后,是不会再产生进位了,因为sum假如达到最大值0xffffffff,那么在进行第一个操作之后,sum的值就变为:

            sum = 0x1fffe

之后在进行第二句操作,sum的值变为

            sum = 0xffff

已经没有进位了,所以只需要两次操作就行了,据说(我还没看过)在《TCP/IP卷二》中,该写法换了另外一种写法,上面代码由#if和#endif注释的那一段。

(3)ICMP解包

        解包就是收到包对包进行分类甄别处理的流程,只要知道ICMP报文的格式,解包处理起来也比较容易,代码实现流程如下:

/**
 * para: @buf: --- 待解包的报文
 *       @len:--- 报文长度
 * func: 对接收的ICMP报文进行解包处理
 **/
int icmp_unpack(char *buf, int len)
{
    int iphdr_len;
    unsigned short recv_chk, tmp_chk;
    struct ip *ip_hdr;
    struct icmp *icmp;
    struct timeval recv_time;
    struct timeval *send_time;
    struct timeval offset_time;
    double time_ms;

    ip_hdr = (struct ip *)buf;
    iphdr_len = ip_hdr->ip_hl << 2;
    icmp = (struct icmp *)(buf + iphdr_len); //使指针跳过IP头指向ICMP头
	
    len -= iphdr_len;
    if (len < 8) {
        printf("[%s][%d]: unpack error, the pkt is not valid.\r\n", __FUNCTION__,        __LINE__);
        return -1;
    }
    /*get 收到icmp的时间,用于计算时间差值*/
    gettimeofday(&recv_time, NULL);

    /*先计算校验,看看收包校验是否正确*/
    recv_chk = icmp->icmp_cksum;
    icmp->icmp_cksum = 0;
    tmp_chk = cal_chksum((unsigned short *)icmp, len);
    if (tmp_chk != recv_chk) {
        printf("[%s][%d]: packet check sum is error.\r\n", __FUNCTION__, __LINE__);
        return -1;
    }

    switch (icmp->icmp_type) {
        case ICMP_REPLY:
            if (icmp->icmp_id != pid) {
                printf("[%s][%d]: packet's icmp_id is error.\r\n", __FUNCTION__, __LINE__);
                return -1;
            } 
            send_time =(struct timeval *)icmp->icmp_data;
            offset_time = get_tv_sub(&recv_time, send_time);
            time_ms = (offset_time.tv_sec * 1000) + (offset_time.tv_usec * 1.0)/1000;
            printf("%d bytes from %s: icmp_req=%d ttl=%d time=%4.2f\n",
                len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, time_ms);			
            break;
        case ICMP_DST_UNREACH:
            printf("From %s icmp_seq=%d Destination Host Unreachable\n",    inet_ntoa(ip_hdr->ip_src),
            icmp->icmp_seq);
            break;
        case ICMP_TIMEOUT:
            printf("time out!\r\n");
            break;
        default:
            break;
    }

    return 0;	
}

解包的处理流程倒没啥好说的,只要了解了ICMP报文格式就可以进行上面的操作,这里主要列出上述一些结构体,以方便后续查找

struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;               /* header length */
    unsigned int ip_v:4;                /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;                /* version */
    unsigned int ip_hl:4;               /* header length */
#endif
    u_int8_t ip_tos;                    /* type of service */
    u_short ip_len;                     /* total length */
    u_short ip_id;                      /* identification */
    u_short ip_off;                     /* fragment offset field */
#define IP_RF 0x8000                    /* reserved fragment flag */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
    u_int8_t ip_ttl;                    /* time to live */
    u_int8_t ip_p;                      /* protocol */
    u_short ip_sum;                     /* checksum */
    struct in_addr ip_src, ip_dst;      /* source and dest address */
  };

struct timeval
{
    time_t      tv_sec;     /* 秒 */
    suseconds_t tv_usec;    /* 微秒 */
};

struct icmp8   
{  
    u_char icmp_type; //type of message(报文类型)   
    u_char icmp_code; //type sub code(报文类型子码)   
    u_short icmp_cksum;  
    u_short icmp_id;  
    u_short icmp_seq;  
    char icmp_data[1];  
};  

需要注意的是,struct icmp的结构因type而有一些差别,这里列出的是type=8的icmp的结构体。

(4)收包线程

        本次设计的ping程序,采用多线程处理,报文收发包两个线程,发包程序代码如下图所示:

        

/**
 *
 * func: ping发包线程函数,设为分离线程,每秒发一个
 **/
void* ping_send(void *arg)
{
    int size;
    char send_buf[MAX_PACKET_LEN];

    memset(send_buf, 0, MAX_PACKET_LEN);
    gettimeofday(&start_time, NULL);
	
    (void)prctl(PR_SET_NAME, "ping_send_pthread");
    pthread_detach(pthread_self());
    while (alive) {
        icmp_pack((struct icmp *)send_buf, send_count, DEFAULT_LEN);
        size = sendto(ping_socket, send_buf, DEFAULT_LEN, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
        send_count++;
        if (size < 0) {
            printf("send icmp packet fail.\n");
            continue;
        }

        sleep(1);
    }
}

        发包函数主要就是关注一下sendto函数以及对应的函数参数,函数详细信息如下:

头文件:#include <sys/types.h>   #include <sys/socket.h>

定义函数:int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

       函数说明:sendto() 用来将数据由指定的socket 传给对方主机. 参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作. 参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send(). 参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数tolen 为sockaddr 的结果长度.

       返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.

(5)发包线程

/**
 *
 * func: ping收包线程函数,设为分离线程
 **/
void* ping_recv(void *arg)
{
    int ret;
    int state;
    int size;
    struct timeval tv;
    fd_set read_fd;
    char recv_buf[MAX_PACKET_LEN];

    ret = 0;
    state = 0;
    size = 0;
    tv.tv_sec = 0;
    tv.tv_usec = 200;
    memset(recv_buf, 0, sizeof(MAX_PACKET_LEN));
    (void)prctl(PR_SET_NAME, "ping_recv_pthread");
    pthread_detach(pthread_self());
    while (alive) {
        FD_ZERO(&read_fd);
        FD_SET(ping_socket, &read_fd);  	
        size = recvfrom(ping_socket, recv_buf, MAX_PACKET_LEN, 0, NULL, NULL);
        if (size < 0) {
            printf("recv date fail!\n");
            continue;
        }
        ret = icmp_unpack(recv_buf, size);
        if (ret == -1) {
            continue;
        }
        recv_count++;
    }	
}

        收包函数用的是recvfrom函数,其它也没啥好介绍,就是常规接收流程,稍微琢磨一下应该就可以很容易理解了,就不详细赘述。但是有一点提一下,就是看到不少人收包用的select配合recv函数使用,并且将收发包线程没设置为分离线程,例如上述参看文章链接中就是这种做法,select函数可以设置超时时间,没有收到报文时,不用一直阻塞,相比较而言效率更高,我在本地环境也用过select版本,写法模式文章头链接里面的实现,也可以达到效果。

(6)中断响应函数

/**
 *
 * func: 中断处理函数,当输入crtl+c时,该函数被调用
 **/
void icmp_sigint(int signo)
{
    alive = 0;
    gettimeofday(&end_time, NULL);
    interval_time = get_tv_sub(&end_time, &start_time);
}

这个函数的功能已经在函数头上进行了说明,就不再赘述了。

(7)接下来就是主函数了,如下图所示:

int main(int argc, char *argv[])
{
    int size = 128*1024;
    pthread_t send_id, recv_id;
    struct hostent *host;
    unsigned int inaddr;

    if (argc < 2) {
        printf("cmd is not availble.\r\n");
        return -1;
    }

    g_var_init();
    inaddr = 0;
    memset(dest_addr_str, 0, MAX_PACKET_LEN);
    memcpy(dest_addr_str, argv[1], strlen(argv[1]) + 1);	

    ping_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (ping_socket < 0) {
        printf("Fail to create socket!.\r\n");
        return -1;		
    }

    pid = getpid();
    /*设置接收报文的缓冲区的大小*/
    setsockopt(ping_socket, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));

    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    /*inet_addr函数将字符串转化为整型的ipv4地址
     * 如果返回INADDR_NONE表示转换失败,可能是因为这个地址是域名,需要转化
     */
    inaddr = inet_addr(argv[1]);
    if (inaddr == INADDR_NONE) {
        host = gethostbyname(argv[1]);
        if (host == NULL) {
            printf("Fail to gethostbyname!\r\n");
            return -1;
        }
        memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
    } else {
        memcpy((char *)&dest_addr.sin_addr, &inaddr, sizeof(inaddr));
    }

    inaddr = dest_addr.sin_addr.s_addr;
    printf("PING %s, (%d.%d.%d.%d) 56(84) bytes of data.\n", dest_addr_str,
        (inaddr & 0x000000ff), (inaddr & 0x0000ff00) >> 8,
        (inaddr & 0x00ff0000) >> 16, (inaddr & 0xff000000) >> 24);

    signal(SIGINT, icmp_sigint);

    if (pthread_create(&send_id, NULL, ping_send, NULL)) {
        printf("Fail to create ping send thread!\n");
        return -1;
    }
    if (pthread_create(&recv_id, NULL, ping_recv, NULL)) {
        printf("Fail to create ping recv thread!\n");
        return -1;
    }

    while(alive);
    ping_statistics(dest_addr_str);
    ping_over_show();
    close(ping_socket);

    return 0;
}

        主函数中首先对输入的参数进行了处理,包括对域名的考虑,然后注册了SIGINT中断信号函数,之后创建了收发包的两个线程,最后主线程一直循环,直到中断信号触发,结束整个进程。

运行结果如下图所示:

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

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

    8102 人正在学习 去看看 深博

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

2009-04-01 00:42:00 zhsp1029 阅读数 1877
  • 第07章-网络通信协议(OSI、TCP、UDP、IP、ARP、ICMP...

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

    8102 人正在学习 去看看 深博

 一 原理

    ping命令工作在ip层,在程序中通过raw scket进行数据的收发,发数据时不需要填充ip头部,但是在接收数据时需要过滤掉ip头部信息。icmp头部重要的字段有三个,type,code,checksum,其中type表示命令的类型,对于ping命令来说,type的值为8表示发送icmp,type值为0表示是icmp的回包,code表示type下的子命令,对于ping命令来说,code的值为0。checksum字段表示icmp包的校验字段,校验方法为对icmp的所有字段取和再取反码。

 

二 实现代码:

   /*ping.h*/

 

#ifndef __SAM_PING_
#define __SAM_PING_

#include    "stdlib.h"
#include    "string.h"
#include    "stdio.h"
#include    "fcntl.h"
#include    "errno.h"
#include    "signal.h"
#include    "sys/types.h"
#include    "sys/socket.h"
#include    "sys/time.h"
#include    "netinet/in.h"
#include    "arpa/inet.h"
#include    "netdb.h"

#define MAXBUFFLEN 200

#define ICMP_ECHO 8 /* icmp echo requir */
#define ICMP_ECHOREPLY 0 /* icmp echo reply */
#define ICMP_HEADSIZE 8 /* icmp packet header size */
#define IP_HEADSIZE 20 /* ip packet header size */
#pragma  pack(1)
typedef struct tagIpHead /* icmp packet header */
{
    u_char ip_verlen; /* ip version and ip header lenth*/
    u_char ip_tos; /* ip type of service */
    u_short ip_len; /* ip packet lenghth */
    u_short ip_id; /* ip packet identification */
    u_short ip_fragoff; /* ip packet fragment and offset */
    u_char ip_ttl; /* ip packet time to live */
    u_char ip_proto; /* ip packet protocol type */
    u_short ip_chksum; /* ip packet header checksum */
    u_long ip_src_addr; /* ip source ip adress */
    u_long ip_dst_addr; /* ip destination ip adress */
} IPHEAD;

typedef struct tagIcmpHead /* icmp header */
{
    u_char icmp_type; /* icmp service type */
    /* 8 echo require, 0 echo reply */
    u_char icmp_code; /* icmp header code */
    u_short icmp_chksum; /* icmp header chksum */
    u_short icmp_id; /* icmp packet identification */
    u_short icmp_seq; /* icmp packet sequent */
    u_char icmp_data[1]; /* icmp data, use as pointer */
} ICMPHEAD;
#pragma pack()
class CICMP
{
private:
    int m_iSocket;
    int m_iPkgSize;
    long m_iSendTime;             /*发送ping包的时间戳*/
    long m_iRecvTime;             /*接收ping包的时间戳*/
    long m_iDelay;
    IPHEAD m_stIpHead;
    ICMPHEAD m_stIcmpHead;
    char m_szIcmpData[MAXBUFFLEN];
protected:
    long TimeNow();
    bool validChkSum(ushort* buffer,int size);
    u_short ChkSum(u_short* pIcmpData,int iDataLen);
    int ParseRecv(IPHEAD* pHeader,int size);
public:
    CICMP(int pkgSize = 0):m_iPkgSize(pkgSize){memset(m_szIcmpData,0,MAXBUFFLEN);}
    int CreateRawSocket();
    int Ping(const char* szIp,int pkgSize);
    int Stat();
    int Receive();
};

#endif

 

 

/*ping.cpp*/

 

 

#include "ping.h"
#include <iostream>
#include <string>

using namespace std;

u_short CICMP::ChkSum( u_short * buffer, int size )
/* for check sum of icmp header */
{
    unsigned long cksum=0;
    while(size >1)
    {
        cksum+=*buffer++;
        size-=sizeof(unsigned short);
    }
    if(size) cksum+=*(unsigned short*)buffer;
    cksum=(cksum >> 16)+(cksum&0xffff);
    cksum+=(cksum >>16);
    return (unsigned short)(~cksum);

}

bool CICMP::validChkSum(unsigned short *buffer, int size)
{
    unsigned long cksum=0;
    while(size >1)
    {
        cksum+=*buffer++;
        size-=sizeof(unsigned short);
    }
    if(size) cksum+=*(unsigned short*)buffer;
    cksum=(cksum >> 16)+(cksum&0xffff);
    cksum+=(cksum >>16);
    return ((unsigned short)cksum == 0xFFFF);
}


int CICMP::CreateRawSocket()
{
    this->m_iSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if(this->m_iSocket < 0)
    {
        perror("socket");
        return -1;
    }
    return 0;
}

long CICMP::TimeNow()
{
    struct timeval now;
    long lPassed;
    gettimeofday(&now, 0);
    lPassed = now.tv_sec * 1000000 + now.tv_usec;
    /* now.tv_sec in second */
    /* now.tv_usec in 1/1000000 second */
    return lPassed;
}

int CICMP::Ping(const char* szIp,int pkgSize)
{
    struct sockaddr_in descAddr;
    bzero(&descAddr, sizeof(descAddr));
    descAddr.sin_family = AF_INET;
    u_long lHostIp;
    struct hostent* h = NULL;
    /* check host format */
    if ( ( lHostIp = inet_addr(szIp) ) != INADDR_NONE )
    {
        /* is available ip adress */
        //cout << "is available ip address" << endl;
        descAddr.sin_addr.s_addr = lHostIp;
    }
    else if ( h = gethostbyname(szIp))
    {
        /* is available host name */
        /* from hosts file of local host */
        /* or from DNS */
        //cout << "is available host name" << endl;
        bcopy(h->h_addr, &descAddr.sin_addr, h->h_length);
    }
    else
    {
        /* bad ip adress or host name */
        /* exit */
        fprintf( stderr, "bad IP or host/n" );
        return -1;
    }
    cout << "ping " << szIp<< endl;
    //begin Ping
    int iPacketSize = 0;
    char* buf = new char[MAXBUFFLEN];
    memset(buf,0,MAXBUFFLEN);
    /* make the icmp header information */
    ICMPHEAD *pIcmpHead = (ICMPHEAD *)buf;
    pIcmpHead->icmp_type = ICMP_ECHO;
    pIcmpHead->icmp_code = 0;
    pIcmpHead->icmp_id = 1;
    pIcmpHead->icmp_seq = 1;
    pIcmpHead->icmp_chksum = 0;

    /* store time information as icmp packet content, 4 bytes */
    /* u may store other information instead */
    *((long *)pIcmpHead->icmp_data) = TimeNow();
    this->m_iSendTime = TimeNow();
    //iPacketSize = ICMP_HEADSIZE + 4; /* icmp packet length */
    iPacketSize = sizeof(ICMPHEAD) + 3;
    /* icmp header check sum */
    pIcmpHead->icmp_chksum = this->ChkSum((u_short *)pIcmpHead, iPacketSize );

    /* remember the time when send for calculate round trip time */
    //lSendTime = time_now();

    /* send the icmp packet to des host */
    //cout << "send pkg size " << iPacketSize << endl;
    if (sendto(m_iSocket, buf, iPacketSize, 0, (struct sockaddr *)&descAddr, sizeof(descAddr) ) < 0)
    {
        perror("send failed");
        delete [] buf;
        return -1;
    }
    delete [] buf;
    //usleep(0);
    return 0;
}

int CICMP::Receive()
{
    struct sockaddr_in fromAddr;
    int size = 0;
    char* buf = new char[MAXBUFFLEN];
    memset(buf,0,MAXBUFFLEN);
    int namelen = sizeof(fromAddr);
    //cout << "before recvfrom" << endl;
    size = recvfrom(this->m_iSocket,buf,MAXBUFFLEN - 1,0,(struct sockaddr *)&fromAddr,(socklen_t*)&namelen);
    if(size == -1)
    {
        cout << "recv ping back failed:" << strerror(errno) << endl;
        return -1;
    }
    IPHEAD* pIpHead = (IPHEAD *)buf;
    //cout << "after recvfrom:" << size << endl;
    /* get the ip packet lenth */
    /* if too small, not the icmp echoreply packet */
    /* give it up */

    int iIpHeadLen = (int)((pIpHead->ip_verlen & 0x0f) << 2);
    if (size < iIpHeadLen + ICMP_HEADSIZE)
    {
        cout << "recv header is too short,give it up" << endl;
        delete buf;
        return -1;
    }
    //int ttl = pIpHead->ip_ttl; /* time to live param */

    /* get the icmp header information */
    ICMPHEAD *pIcmpHead = (ICMPHEAD *)(buf + iIpHeadLen);

    /* not icmp echo reply packet, give it up */
    if (pIcmpHead->icmp_type != ICMP_ECHOREPLY)
    {
        cout << "recv pkg not ICMP reply,give it up" << endl;
        delete buf;
        return -1;
    }
    /* not proper icmp sequent number, give it up */
    if (pIcmpHead->icmp_id != 1 || pIcmpHead->icmp_seq != 1)
    {
        cout << "icmp header's id and seq error" << endl;
        delete buf;
        return -1;
    }
    long iCurTime = this->TimeNow();
    this->m_iRecvTime = iCurTime;
    ParseRecv((IPHEAD *)buf,size);
    delete buf;
    return 0;
}

int CICMP::ParseRecv(IPHEAD * pHeader,int size)
{
    memcpy(&m_stIpHead,pHeader,sizeof(IPHEAD));
    char* szTmp = (char*)pHeader;
    char* ptrIcmp = szTmp + sizeof(IPHEAD);
    memcpy(&m_stIcmpHead,ptrIcmp,sizeof(ICMPHEAD));

    /* get the data of icmp*/
    szTmp += sizeof(ICMPHEAD) - 1;
    int iDataLen = size - sizeof(IPHEAD) - sizeof(ICMPHEAD) + 1;
    memcpy(m_szIcmpData,szTmp,iDataLen);
    return 0;
}

int CICMP::Stat()
{
    struct in_addr recvFrom;
    memcpy(&recvFrom,&m_stIpHead.ip_src_addr,4);
    u_char ipTTL = this->m_stIpHead.ip_ttl;
    //int iSendTime = atoi(this->m_szIcmpData);
    int iDelayTime = (int)((m_iRecvTime - m_iSendTime)/1000);
    char szBuff[100] = {0};
    char* szIp = inet_ntoa(recvFrom);
    if(szIp == NULL)
    {
        cout << "get src ip failed" << endl;
        return -1;
    }
    sprintf(szBuff,"recv from %s,delay=%ds,TTL=%d",szIp,iDelayTime,ipTTL);
    cout << szBuff << endl;
    return 0;
}

 

测试程序

 

int main(int argc,char** argv)
{
    if(argc != 2)
    {
        cout << "argument: host" << endl;
        return 0;
    }
    char* szIp = argv[1];
    CICMP icmp;
    if(icmp.CreateRawSocket() != 0)
    {
        cout << "create socket failed" << endl;
        return 0;
    }
    if(icmp.Ping(szIp,4) != 0)
    {
        cout << "ping failed" << endl;
        return 0;
    }
    //cout << "send success ,begin to receive" << endl;
    if(icmp.Receive() != 0)
    {
        return 0;
    }
    icmp.Stat();
    return 0;
}

 

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