OC下UDP發(fā)送搭幻、接收數(shù)據(jù)實(shí)現(xiàn)

目前大部分iOS應(yīng)用都是做應(yīng)用層的網(wǎng)絡(luò)交互開發(fā)已艰,并且開發(fā)人員也都傾向于用第三方庫(kù)(AFNetworking,ASIHttpRequest等)實(shí)現(xiàn)唁盏,很少真正接觸到傳輸層或者網(wǎng)絡(luò)層的開發(fā)内狸。

這里分享一下我實(shí)現(xiàn)的一個(gè)簡(jiǎn)單的UDP發(fā)送检眯、接收數(shù)據(jù)例子。

先看一下UDP的定義:
用戶數(shù)據(jù)報(bào)協(xié)議(英語(yǔ):User Datagram Protocol昆淡,縮寫為UDP)锰瘸,又稱用戶數(shù)據(jù)報(bào)文協(xié)議,是一個(gè)簡(jiǎn)單的面向數(shù)據(jù)報(bào)的傳輸層協(xié)議昂灵,正式規(guī)范為RFC 768避凝。

由名字和定義可以看出,UDP是面向數(shù)據(jù)的協(xié)議眨补,它不保留數(shù)據(jù)備份管削,所以被認(rèn)為是不可靠的數(shù)據(jù)報(bào)協(xié)議。

UDP

接下來(lái)撑螺,要完整的實(shí)現(xiàn)UDP傳輸例子含思,得有一個(gè)UDP的Server。我在Eclipse下用java實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Server程序甘晤,綁定了本機(jī)的8080端口含潘,然后循環(huán)檢測(cè)是接收到消息,如果收到消息打印出來(lái)线婚,并且給收到消息一個(gè)回執(zhí)遏弱。代碼如下:

public class UDPServer {
 private static DatagramSocket serverSocket;

 public static void main(String[] args) throws IOException {
  serverSocket = new DatagramSocket(8080);
  byte[] receiveData = new byte[1024];
  byte[] sendData = new byte[1024];
  while(true)
  {
   DatagramPacket recievePacket = new DatagramPacket(receiveData, receiveData.length);
   serverSocket.receive(recievePacket);
   String recvStr = new String(recievePacket.getData());
   System.out.println(recvStr);
   sendData = "Hello from server".getBytes();
   DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, recievePacket.getAddress(), recievePacket.getPort());
      serverSocket.send(sendPacket);
  }
 }
}

然后就是OC下的UDP發(fā)送和接收實(shí)現(xiàn)了,這里用到了Socket的API酌伊,需要先導(dǎo)入#import <sys/socket.h> #import <netinet/in.h> #import <arpa/inet.h>這三個(gè)頭文件腾窝。

我用了兩種方式來(lái)實(shí)現(xiàn):

主要實(shí)現(xiàn)代碼:

        CFSocketContext context = {0,
            NULL,
            NULL,
            NULL,
            NULL};
        
        CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, UDPDataCallBack, &context);
        
        struct sockaddr_in addr4;
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(8080);
        addr4.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));

        
        const char *data = [@"UDPTest" UTF8String];
        CFSocketSendData(socket, address, CFDataCreate(kCFAllocatorDefault, (const UInt8*)data, strlen(data)), 1);
        CFRunLoopRef cfrl = CFRunLoopGetCurrent();
        CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
        CFRunLoopAddSource(cfrl, source, kCFRunLoopDefaultMode);
        CFRelease(source);
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3, false);

回調(diào)代碼:

void UDPDataCallBack(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
    NSLog(@"%@",[[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(data) encoding:NSUTF8StringEncoding]);
}

這里對(duì)實(shí)現(xiàn)代碼做一些解釋:
首先CFSocketContext,是CFSocket綁定的上下文居砖,在調(diào)用CFSocketCreate最后一個(gè)參數(shù)就是填它的地址虹脯。其實(shí),在這個(gè)例子代碼中并沒有用到復(fù)雜的多線程或者多頁(yè)面調(diào)用奏候,所以完全可以不用創(chuàng)建Context循集,直接在Socket創(chuàng)建的時(shí)候傳NULL也是可以的。

CFSocketContext這個(gè)結(jié)構(gòu)體里的一共5個(gè)參數(shù)蔗草。

  1. version
    結(jié)構(gòu)體的版本號(hào)咒彤,必須是0;
  2. info
    一個(gè)指針包含程序定義的數(shù)據(jù)咒精,他會(huì)跟socket對(duì)象綁定镶柱,并且在后面所有的回調(diào)中傳遞。(例子中如果我們綁定了"Hello"模叙,那么在UDPDataCallBack回調(diào)的時(shí)候info就會(huì)是"Hello"這個(gè)字符串指針)
  3. retain
    一個(gè)上面info指針的retain回調(diào)歇拆,可以為NULL。
  4. release
    一個(gè)上面info指針的release回調(diào),可以為NULL故觅。
  5. copyDescription
    一個(gè)上面info指針的copy description回調(diào)厂庇,可以為NULL。

然后就是CFSocketCreate输吏,這個(gè)方法目的就是創(chuàng)建一個(gè)CFSocketRef結(jié)構(gòu)體的指針权旷。這個(gè)方法有7個(gè)參數(shù),但相對(duì)于CFSocketContext比較容易理解贯溅。由于我們是做UDP通信拄氯,所以第2~4參數(shù)就填的PF_INET,SOCK_DGRAM盗迟,IPPROTO_UDP坤邪,然后5、6參數(shù)是綁定的回調(diào)方法(回調(diào)類型有很多種罚缕,我們這里是為了響應(yīng)接收數(shù)據(jù)艇纺,所以用的kCFSocketDataCallBack)。

接下來(lái)是構(gòu)建服務(wù)器地址數(shù)據(jù)結(jié)構(gòu)體sockaddr邮弹,參數(shù)通過(guò)參數(shù)名就能理解黔衡。由于后面發(fā)送的時(shí)候用的地址是CFDataRef類型,所以最后有一個(gè)轉(zhuǎn)換腌乡。

最后就是發(fā)送盟劫、接收數(shù)據(jù),發(fā)送數(shù)據(jù)用的CFSocketSendData方法与纽,參數(shù)就是前面創(chuàng)建的socket侣签、地址、數(shù)據(jù)等急迂,最后一個(gè)參數(shù)是超時(shí)時(shí)間影所。接收數(shù)據(jù)由于用到了回調(diào),就必須把socket丟到RunLoop里去監(jiān)聽僚碎,這里創(chuàng)建的Runloop會(huì)運(yùn)行3秒就停止(因?yàn)榉?wù)器是接收到數(shù)據(jù)就返回猴娩,并且就是本機(jī)測(cè)試,所以3秒足夠了)勺阐。

看一下兩邊終端的打泳碇小:

Eclipse
Xcode
  • Linux下C語(yǔ)言的socket API

實(shí)現(xiàn)代碼:

        int udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        struct sockaddr_in addr4;
        bzero(&addr4, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(8080);
        addr4.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
        socklen_t add_len = sizeof(struct sockaddr_in);
        ssize_t error;
        
//        struct sockaddr_in addr;
//        bzero(&addr, sizeof(addr));
//        addr.sin_len = sizeof(addr);
//        addr.sin_family = AF_INET;
//        addr.sin_port = htons(3030);
//        addr.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
//        error = bind(udpSocket, (struct sockaddr *)&addr, add_len);
        const char *sendMsg = "Hello from Client";
//        error = connect(udpSocket, (struct sockaddr *)&addr4, add_len);
//        error = send(udpSocket, sendMsg, strlen(sendMsg), 0);
        error = sendto(udpSocket, sendMsg, strlen(sendMsg), 0, (struct sockaddr *)&addr4, add_len);
        
        
        char rev[256];
        error = recvfrom(udpSocket, rev, sizeof(rev), 0, (struct sockaddr *)&addr4, &add_len);
        NSLog(@"%@",[NSString stringWithCString:rev encoding:NSUTF8StringEncoding]);

        close(udpSocket);

同樣也解釋一下上面的代碼,由于前面在介紹CFSocket類實(shí)現(xiàn)的時(shí)候某些知識(shí)已經(jīng)涵蓋了渊抽,所以這里就簡(jiǎn)單過(guò)一下實(shí)現(xiàn)蟆豫。

首先,socket和地址的創(chuàng)建和CFSocket的方法大同小異懒闷,參數(shù)也基本一樣十减,不多解釋徙瓶。

然后就是發(fā)送,C語(yǔ)言下有兩種方法send(不含地址參數(shù))和sendto(含有地址參數(shù))嫉称,兩種都能實(shí)現(xiàn),但是如果使用send灵疮,必須先調(diào)用connect去連接服務(wù)器织阅。

接收同樣有兩個(gè)recvrecvfrom,也是一個(gè)含有地址參數(shù)一個(gè)不含有震捣。在本例子中都能成功接收消息荔棉。

最后介紹一下bind,主要用來(lái)給socket綁定發(fā)送地址的蒿赢,如果不使用润樱,那么每次發(fā)送時(shí)候會(huì)隨機(jī)分配地址。

看一下兩邊終端的打酉劭谩:

Eclipse
Xcode

OC完整代碼:

//
//  main.m
//  UDPtest
//
//  Created by Jiao Liu on 8/24/16.
//  Copyright ? 2016 ChangHong. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

void UDPDataCallBack(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info)
{
    NSLog(@"%@",[[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(data) encoding:NSUTF8StringEncoding]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        
//        CFSocketContext context = {0,
//            NULL,
//            NULL,
//            NULL,
//            NULL};
//        
//        CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, UDPDataCallBack, &context);
//        
//        struct sockaddr_in addr4;
//        memset(&addr4, 0, sizeof(addr4));
//        addr4.sin_len = sizeof(addr4);
//        addr4.sin_family = AF_INET;
//        addr4.sin_port = htons(8080);
//        addr4.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
//        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
//
//        
//        const char *data = [@"UDPTest" UTF8String];
//        CFSocketSendData(socket, address, CFDataCreate(kCFAllocatorDefault, (const UInt8*)data, strlen(data)), 1);
//        CFRunLoopRef cfrl = CFRunLoopGetCurrent();
//        CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
//        CFRunLoopAddSource(cfrl, source, kCFRunLoopDefaultMode);
//        CFRelease(source);
//        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 3, false);
        
    
        int udpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        struct sockaddr_in addr4;
        bzero(&addr4, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(8080);
        addr4.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
        socklen_t add_len = sizeof(struct sockaddr_in);
        ssize_t error;
        
//        struct sockaddr_in addr;
//        bzero(&addr, sizeof(addr));
//        addr.sin_len = sizeof(addr);
//        addr.sin_family = AF_INET;
//        addr.sin_port = htons(3030);
//        addr.sin_addr.s_addr = inet_addr([@"10.9.36.24" UTF8String]);
//        error = bind(udpSocket, (struct sockaddr *)&addr, add_len);
        const char *sendMsg = "Hello from Client";
//        error = connect(udpSocket, (struct sockaddr *)&addr4, add_len);
//        error = send(udpSocket, sendMsg, strlen(sendMsg), 0);
        error = sendto(udpSocket, sendMsg, strlen(sendMsg), 0, (struct sockaddr *)&addr4, add_len);
        
        
        char rev[256];
        error = recvfrom(udpSocket, rev, sizeof(rev), 0, (struct sockaddr *)&addr4, &add_len);
        NSLog(@"%@",[NSString stringWithCString:rev encoding:NSUTF8StringEncoding]);

        close(udpSocket);
    }
    return 0;
}

至此壹若,就完成了簡(jiǎn)單的用OC實(shí)現(xiàn)UDP發(fā)送、接收數(shù)據(jù)皂冰。??rz店展。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秃流,隨后出現(xiàn)的幾起案子赂蕴,更是在濱河造成了極大的恐慌,老刑警劉巖舶胀,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件概说,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嚣伐,警方通過(guò)查閱死者的電腦和手機(jī)糖赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纤控,“玉大人挂捻,你說(shuō)我怎么就攤上這事〈颍” “怎么了刻撒?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)耿导。 經(jīng)常有香客問(wèn)我声怔,道長(zhǎng),這世上最難降的妖魔是什么舱呻? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任醋火,我火速辦了婚禮悠汽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芥驳。我一直安慰自己柿冲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布兆旬。 她就那樣靜靜地躺著假抄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丽猬。 梳的紋絲不亂的頭發(fā)上宿饱,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音脚祟,去河邊找鬼谬以。 笑死,一個(gè)胖子當(dāng)著我的面吹牛由桌,可吹牛的內(nèi)容都是我干的为黎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沥寥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碍舍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起邑雅,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤片橡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淮野,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捧书,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年骤星,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了经瓷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洞难,死狀恐怖舆吮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情队贱,我是刑警寧澤色冀,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站柱嫌,受9級(jí)特大地震影響锋恬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜编丘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一与学、第九天 我趴在偏房一處隱蔽的房頂上張望彤悔。 院中可真熱鬧,春花似錦索守、人聲如沸晕窑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幕屹。三九已至,卻和暖如春级遭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渺尘。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工挫鸽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸥跟。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓丢郊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親医咨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枫匾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 1,976評(píng)論 0 7
  • ———————————————回答好下面的足夠了---------------------------------...
    恒愛DE問(wèn)候閱讀 1,716評(píng)論 0 4
  • 多線程、特別是NSOperation 和 GCD 的內(nèi)部原理拟淮。運(yùn)行時(shí)機(jī)制的原理和運(yùn)用場(chǎng)景干茉。SDWebImage的原...
    LZM輪回閱讀 2,007評(píng)論 0 12
  • __block和__weak修飾符的區(qū)別其實(shí)是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,309評(píng)論 0 6
  • 寫這篇文章的靈感來(lái)源于今天看菜頭叔在得到專欄寫的文章《最完美的方案》,羅胖在一開始讓他每周至少寫3篇文章委造,一年52...
    楊穎__指數(shù)成長(zhǎng)閱讀 359評(píng)論 0 2