码迷,mamicode.com
首页 > 编程语言 > 详细

Swift和C混合Socket编程实现简单的ping命令

时间:2016-01-14 23:45:10      阅读:1962      评论:0      收藏:0      [点我收藏+]

标签:

这个是用Mac下的Network Utility工具实现ping命令,用Wireshark抓取的ICMP数据包:

发送ICMP数据包内容
技术分享接受ICMP数据包内容
技术分享

一.icmp结构

要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。
ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的 一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。
ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报

各种ICMP报文的前32bits都是三个长度固定的字段:type类型字段(8位)、code代码字段(8位)、checksum校验和字段(16位)

8bits类型和8bits代码字段:一起决定了ICMP报文的类型。常见的有:
  
  类型8、代码0:回射请求。
  
  类型0、代码0:回射应答。
  
  类型11、代码0:超时。
  
  16bits校验和字段:包括数据在内的整个ICMP数据包的校验和,其计算方法和IP头部校验和的计算方法是一样的。

对于ICMP回射请求和应答报文来说,接下来是16bits标识符字段:用于标识本ICMP进程。
  
最后是16bits序列号字段:用于判断回射应答数据报。

ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面

一个ICMP报文包括IP头部(20字节)、ICMP头部(8字节)和ICMP报文数据部分

技术分享

ICMP报文格式,在Mac(Unix)下结构包含在ip_icmp.h中:
引入头文件#include //icmp数据包结构

struct icmp {
    u_char    icmp_type;        /* type of message, see below */
    u_char    icmp_code;        /* type sub code */
    u_short    icmp_cksum;        /* ones complement cksum of struct */
    union {
        u_char ih_pptr;            /* ICMP_PARAMPROB */
        struct in_addr ih_gwaddr;    /* ICMP_REDIRECT */
        struct ih_idseq {
            n_short    icd_id;
            n_short    icd_seq;
        } ih_idseq;
        int ih_void;

        /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
        struct ih_pmtu {
            n_short ipm_void;
            n_short ipm_nextmtu;
        } ih_pmtu;

        struct ih_rtradv {
            u_char irt_num_addrs;
            u_char irt_wpa;
            u_int16_t irt_lifetime;
        } ih_rtradv;
    } icmp_hun;
#define    icmp_pptr    icmp_hun.ih_pptr
#define    icmp_gwaddr    icmp_hun.ih_gwaddr
#define    icmp_id        icmp_hun.ih_idseq.icd_id
#define    icmp_seq    icmp_hun.ih_idseq.icd_seq
#define    icmp_void    icmp_hun.ih_void
#define    icmp_pmvoid    icmp_hun.ih_pmtu.ipm_void
#define    icmp_nextmtu    icmp_hun.ih_pmtu.ipm_nextmtu
#define    icmp_num_addrs    icmp_hun.ih_rtradv.irt_num_addrs
#define    icmp_wpa    icmp_hun.ih_rtradv.irt_wpa
#define    icmp_lifetime    icmp_hun.ih_rtradv.irt_lifetime
    union {
        struct id_ts {
            n_time its_otime;
            n_time its_rtime;
            n_time its_ttime;
        } id_ts;
        struct id_ip  {
            struct ip idi_ip;
            /* options and then 64 bits of data */
        } id_ip;
        struct icmp_ra_addr id_radv;
        u_int32_t id_mask;
        char    id_data[1];
    } icmp_dun;
#define    icmp_otime    icmp_dun.id_ts.its_otime
#define    icmp_rtime    icmp_dun.id_ts.its_rtime
#define    icmp_ttime    icmp_dun.id_ts.its_ttime
#define    icmp_ip        icmp_dun.id_ip.idi_ip
#define    icmp_radv    icmp_dun.id_radv
#define    icmp_mask    icmp_dun.id_mask
#define    icmp_data    icmp_dun.id_data
};

 

IP报头格式如下图:
技术分享
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
IP报文格式,在Mac(Unix)下结构包含在ip.h中:
引入头文件#include //ip数据包结构
struct ip {
#ifdef _IP_VHL
    u_char    ip_vhl;            /* version << 4 | header length >> 2 */
#else
#if BYTE_ORDER == LITTLE_ENDIAN
    u_int    ip_hl:4,        /* header length */
        ip_v:4;            /* version */
#endif
#if BYTE_ORDER == BIG_ENDIAN
    u_int    ip_v:4,            /* version */
        ip_hl:4;        /* header length */
#endif
#endif /* not _IP_VHL */
    u_char    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_char    ip_ttl;            /* time to live */
    u_char    ip_p;            /* protocol */
    u_short    ip_sum;            /* checksum */
    struct    in_addr ip_src,ip_dst;    /* source and dest address */
};

 

二.具体实现代码

//xSocketPing.c


#include "xSocketPing.h" //statistics void statistics(char* back){ double percent = ((double)sendPacketNumber - (double)recvPacketNumber) / (double)sendPacketNumber * 100; sprintf(back, "---%s ping statistics---\n%d packets trasmitted, %d packet received, %0.1f%% packet loss",inet_ntoa(dstAddr.sin_addr),sendPacketNumber,recvPacketNumber,percent); } //check sum unsigned short checkSum(unsigned short *buffer, int size){ unsigned long checkSum = 0; while (size > 1) { checkSum += *buffer++; size -= sizeof(unsigned short);//unsigned short is 2 bytes = 16 bits } //if size is odd number if (size == 1){ checkSum += *(unsigned short *)buffer; } checkSum = (checkSum >> 16) + (checkSum & 0xFFFF); checkSum += (checkSum >> 16); return ~checkSum; } //calculate time difference double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp){ //calculate seconds long timevalSec = recvTimeStamp->tv_sec - sendTimeStamp->tv_sec; //calculate microsends long timevalUsec = recvTimeStamp->tv_usec - sendTimeStamp->tv_usec; //if microsends less then zero if (timevalUsec < 0) { timevalSec -= 1; timevalUsec = - timevalUsec; } return (timevalSec * 1000.0 + timevalUsec) / 1000.0; } //fill icmp packet and return size of packet int fillPacket(int packetSequence){ int packetSize = 0; struct icmp *icmpHeader = (struct icmp *)sendBuffer; icmpHeader->icmp_type = ICMP_ECHO; icmpHeader->icmp_code = 0; icmpHeader->icmp_cksum = 0; icmpHeader->icmp_id = pid; icmpHeader->icmp_seq = packetSequence; packetSize = dataSize + 8; tvSend = (struct timeval *)icmpHeader->icmp_data; gettimeofday(tvSend, NULL);//get current of time icmpHeader->icmp_cksum = checkSum((unsigned short *)icmpHeader, packetSize); return packetSize; } //send icmp packet to dstAddr int sendPacket(int packetSequence){ int packSize = 0; packSize = fillPacket(packetSequence); if ((sendto(socketfd, sendBuffer, packSize, 0, (struct sockaddr *)&dstAddr, sizeof(dstAddr))) < 0) { printf("Send icmp packet Error\n"); sendPacketNumber--; recvPacketNumber--; return -1; } return 0; } //setting ip address void settingIP(){ //initialize bzero(&dstAddr,sizeof(dstAddr)); dstAddr.sin_family = AF_INET; dstAddr.sin_addr.s_addr = inet_addr(ipAddr); } //get current process id void getPid(){ pid = getpid(); } //create socket int createSocket(){ //原始套接字SOCK_RAW需要使用root权限,所以改用SOCK_DGRAM if ((socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0) { printf("Create Socket Error\n"); return -1; } return 0; } //setting socket void settingSocket(int timeout){ int size = 50 * 1024; //setting timeout seconds or you can set it by microseconds struct timeval timeOut; timeOut.tv_sec = timeout; //扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答 setsockopt(socketfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeOut, sizeof(timeOut)); } //destory socket void destorySocket(){ close(socketfd); } //unpacket void unPacket(char* packetBuffer,char* back, long size){ struct ip *ipHeader = NULL; struct icmp *icmpHeader = NULL; double rtt;//往返时间 int ipHeaderLength;//ip header length ipHeader = (struct ip *)packetBuffer; ipHeaderLength = ipHeader->ip_hl<<2;//求ip报头长度,即ip报头的长度标志乘4 icmpHeader = (struct icmp *)(packetBuffer + ipHeaderLength);//越过IP头,point to ICMP header size -= ipHeaderLength; if (size < 8){ back = "Unpacket Error:packet size minmum 8 bytes\n"; } if ((icmpHeader->icmp_type == ICMP_ECHOREPLY) && (icmpHeader->icmp_id == pid)) { tvSend = (struct timeval *)icmpHeader->icmp_data; gettimeofday(&tvRecv, NULL); //以毫秒为单位计算rtt rtt = timeSubtract(&tvRecv, tvSend); sprintf(back,"%ld bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n",size,inet_ntoa(recvAddr.sin_addr),icmpHeader->icmp_seq,ipHeader->ip_ttl,rtt); }else{ back = "Unpacket Error\n"; } } //receive packet void receivePacket(char* back){ //claculate packet size int packetSize = sizeof(recvAddr); long size; if ((size = recvfrom(socketfd, recvBuffer, sizeof(recvBuffer), 0, (struct sockaddr *)&recvAddr, (socklen_t *)&packetSize)) < 0) { sprintf(back,"Receive timeout\n"); recvPacketNumber--; }else{ gettimeofday(&tvRecv, NULL); //char temp[100] = {0}; unPacket(recvBuffer, back, size); //printf("%s\n",temp); } } void ping(char *ipAddress, int number, int timeout){ int packetnumber = 0; ipAddr = ipAddress; sendPacketNumber = number; recvPacketNumber = number; settingIP(); getPid(); if (createSocket() != -1){ settingSocket(timeout); printf("PING %s: %d bytes of data.\n",ipAddress,dataSize); while(packetnumber < number){ if (sendPacket(packetnumber) != -1){ char back[100] = {0}; receivePacket(back); printf("%s",back); } sleep(1); packetnumber++; } char back[100] = {0}; statistics(back); printf("%s\n",back); destorySocket(); } }


//xSocketPing.h

#ifndef xSocketPing_h
#define xSocketPing_h #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h>//基本系统数据类型 #include <sys/socket.h> #include <netinet/in.h> //get current of time #include <sys/time.h> #include <arpa/inet.h>//inet_ntoa将一个IP转换成一个互联网标准点分格式的字符串 #include <unistd.h>//close(int) #include <netdb.h>//定义了与网络有关的结构、变量类型、宏、函数等 //ip packet structure #include <netinet/ip.h> //icmp packet structure #include <netinet/ip_icmp.h> //time to live int ttl = 64; //icmp data size ,icmp header 8bytes,data size 56bytes,the maximum of packet size 64bytes int dataSize = 56; //packet number int sendPacketNumber; int recvPacketNumber; //ip address char * ipAddr; //send packet of time struct timeval *tvSend; //receive packet of time struct timeval tvRecv; //Socket address, internet style. //the destination address struct sockaddr_in dstAddr; //the receive address struct sockaddr_in recvAddr; //send icmp buffer char sendBuffer[1024] = {0}; //receive icmp replay buffer char recvBuffer[1024] = {0}; //the current process of id int pid; //socket int socketfd = 0; void statistics(char* back); unsigned short checkSum(unsigned short *buffer, int size); double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp); int fillPacket(int packetSequence); int sendPacket(int packetSequence); void settingIP(); void getPid(); int createSocket(); void settingSocket(int timeout); void destorySocket(); void unPacket(char* packetBuffer,char* back, long size); void receivePacket(char* back); void ping(char *ipAddress, int number, int timeout); #endif /* xSocketPing_h */


//xSocketPing.swift

import Foundation

public class xSocketPing{
    
    private var ipAddress:String
    //default packet number 3 or you can setting it by yourself
    private var packetNumber:Int = 3
    private var timeout:Int = 1
    //refresh UI
    weak var delegate:refreshTextDelegate?

    init(ipAddress:String, delegate:refreshTextDelegate){
        self.ipAddress = ipAddress
        self.delegate = delegate
    }
    convenience init(ipAddress:String, packetNumber:Int, delegate:refreshTextDelegate){
        self.init(ipAddress: ipAddress,delegate: delegate)
        self.packetNumber = packetNumber
    }
    convenience init(ipAddress:String, packetNumber:Int, timeout:Int, delegate:refreshTextDelegate){
        self.init(ipAddress: ipAddress,packetNumber: packetNumber,delegate: delegate)
        self.timeout = timeout
    }
    
    public func xPing(){
        let tempIpAddress:UnsafeMutablePointer = UnsafeMutablePointer<Int8>((ipAddress as NSString).UTF8String)
        ipAddr = tempIpAddress
        sendPacketNumber = Int32(packetNumber)
        recvPacketNumber = Int32(packetNumber)
        
        settingIP()
        getPid()
        if createSocket() != -1 {
            settingSocket(Int32(timeout))
            let message:String = "PING \(ipAddress): 56 bytes of data.\n"
            refresh(message, speed: 0.0)
            //将String转换为UnsafeMutablePointer<CChar>,相当于char tempmessage[100]
            let tempmessage:UnsafeMutablePointer = UnsafeMutablePointer<CChar>.alloc(100)
            var packetsequence:Int = 0
            var speed:Float = 0.0
            
            while packetsequence < packetNumber {
                if sendPacket(Int32(packetsequence)) != -1 {
                    receivePacket(tempmessage)
                    //Calculate percentage
                    speed = (Float(packetNumber) - (Float(packetNumber) - Float(packetsequence))) / Float(packetNumber)
                    //将UnsafeMutablePointer<CChar>转换为String
                    refresh(String.fromCString(tempmessage)!, speed: speed)
                }
                sleep(1);
                packetsequence++
            }
            statistics(tempmessage)
            refresh(String.fromCString(tempmessage)!, speed: 1)
            destorySocket()
        }
    }
    
    func refresh(text:String, speed:Float){
        delegate?.refresh(text, speed: speed)
    }
    deinit{
        print("xSocketPing destory")
    }
}

 

//ViewController.swift

import UIKit

protocol refreshTextDelegate:NSObjectProtocol{
    func refresh(text:String, speed:Float)
}

class ViewController: UIViewController,refreshTextDelegate {

    @IBOutlet weak var xprogress: UIProgressView!
    @IBOutlet weak var xip: UITextField!
    @IBOutlet weak var xnumber: UITextField!
    @IBOutlet weak var xtext: UITextView!
    
    var number:Int = 3
    var ip:String = "192.168.1.1"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        xtext.text = ""
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func xNumberChange(sender: UIStepper) {
        xnumber.text = "\(Int(sender.value))"
        self.number = Int(sender.value)
    }
    
    @IBAction func beginPing(sender: UIButton) {
        sender.enabled = false
        xip.enabled = false
        xnumber.enabled = false
        
        xprogress.progress = 0
        xtext.text = ""
        
        let ip = xip.text
        if ip == ""{
            let alert = UIAlertView(title: "Error", message: "No IP Address", delegate: self, cancelButtonTitle: "OK")
            alert.show()
        }else{
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
                let ping:xSocketPing = xSocketPing(ipAddress: "\(ip!)", packetNumber: self.number, delegate: self)
                ping.xPing()
            })
        }
        xnumber.enabled = true
        xip.enabled = true
        sender.enabled = true
    }
    
    
    func refresh(text:String, speed:Float) {
        dispatch_async(dispatch_get_main_queue(), {
            //更新进度条
            self.xprogress.progress = speed
            //更新UITextView,追加内容并滚动到最下面
            self.xtext.text.appendContentsOf(text)
            self.xtext.scrollRangeToVisible(NSMakeRange(self.xtext.text.characters.count, 0))
        })
    }
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        self.view.endEditing(true)
    }
}


运行结果图:
技术分享

技术分享
技术分享

技术分享

运行环境OS X10.10.5,Xcode7.0,Swift2.1,iOS9.0模拟器
真机测试的时候有可能找不到#include ,猜测是苹果不给在机子上使用icmp数据包,如果只是在自己机子上测试,可以将模拟器的
ip_icmp.h文件复制到真机环境,再进行编译运行.

部分出现的疑惑或者问题,可以看我之前的新浪博客
 Wireshark找不到网卡

Swift和C语言交互-String和UnsafeMutablePointer转换

Swift和c的类型转换

Swift与c的交互

GitHub项目地址:Swift和C混合Socket编程实现简单的ping命令
本程序还有很多不足之处,后续会持续更新,并更新GitHub项目,需要改进的,请在博客上留言
抓包的图片是不更新的,所以会和运行结果图不一样.

更新于2016-1-13
修复bug,优化部分显示,更好,更安全的处理指针类型.
更新于2016-1-12
新增功能:优化显示,将返回的结果显示到模拟器中.发送数据包在额外的线程中进行,不会阻塞主线程.由于程序使用的是Swift和C语言混合编程,用到了指针,交互的时候容易crash.后续会继续修复bug.
更新2016-1-7
新增功能:优化显示,将返回结果集中到主函数中,方便调用,修复部分bug,添加超时处理,防止程序一直处于阻塞状态.,

Swift和C混合Socket编程实现简单的ping命令

标签:

原文地址:http://www.cnblogs.com/xwjack1554239786/p/5131787.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!