目前大部分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é)議。
接下來(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):
- OC封裝的CFSocket類
主要實(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ù)蔗草。
-
version
結(jié)構(gòu)體的版本號(hào)咒彤,必須是0; -
info
一個(gè)指針包含程序定義的數(shù)據(jù)咒精,他會(huì)跟socket對(duì)象綁定镶柱,并且在后面所有的回調(diào)中傳遞。(例子中如果我們綁定了"Hello"模叙,那么在UDPDataCallBack回調(diào)的時(shí)候info就會(huì)是"Hello"這個(gè)字符串指針) -
retain
一個(gè)上面info指針的retain回調(diào)歇拆,可以為NULL。 -
release
一個(gè)上面info指針的release回調(diào),可以為NULL故觅。 -
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秒足夠了)勺阐。
看一下兩邊終端的打泳碇小:
- 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è)recv
與recvfrom
,也是一個(gè)含有地址參數(shù)一個(gè)不含有震捣。在本例子中都能成功接收消息荔棉。
最后介紹一下bind
,主要用來(lái)給socket綁定發(fā)送地址的蒿赢,如果不使用润樱,那么每次發(fā)送時(shí)候會(huì)隨機(jī)分配地址。
看一下兩邊終端的打酉劭谩:
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店展。