IOS开发之socket网络编程(基于SimpleNetworkStreams的c/s程序)
SimpleNetworkStreams展示了如何基于Socket网络编程,实现了一个很典型的局域网内网络数据传输的场景,一个是client向server端发送本地的图片文件,另一个是client向server端下载图片到本地文件。抽取出来的一般流程:
server开启socket监听
此处IOS的一般做法是三步走:
port = 0; fd = socket(AF_INET, SOCK_STREAM, 0); success = (fd != -1); if (success) { memset(&addr, 0, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = 0; addr.sin_addr.s_addr = INADDR_ANY; err = bind(fd, (const struct sockaddr *) &addr, sizeof(addr)); success = (err == 0); } if (success) { err = listen(fd, 5); success = (err == 0); } if (success) { socklen_t addrLen; addrLen = sizeof(addr); err = getsockname(fd, (struct sockaddr *) &addr, &addrLen); success = (err == 0); if (success) { assert(addrLen == sizeof(addr)); port = ntohs(addr.sin_port); } }这里用port=0是让系统自动随机找一个空闲端口。其他都是基于c风格对系统函数的直接调用。
第二步:用IOS的socket(CFSocket)包装系统socket
CFSocketContext context = { 0, (__bridge void *) self, NULL, NULL, NULL }; assert(self->_listeningSocket == NULL); self->_listeningSocket = CFSocketCreateWithNative( NULL, fd, kCFSocketAcceptCallBack, AcceptCallback, &context ); success = (self->_listeningSocket != NULL); if (success) { CFRunLoopSourceRef rls; fd = -1; // listeningSocket is now responsible for closing fd rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0); assert(rls != NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); }这里包装socket的目的是便于后面的事件侦听和处理,把基于原生态socket的开发转到IOS的层面上来,这里accept事件侦听函数是AcceptCallback,并在单独thread中执行。
第三步:通过NSNetService发布socket
if (success) { self.netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:@"Test" port:port]; success = (self.netService != nil); } if (success) { self.netService.delegate = self; [self.netService publishWithOptions:NSNetServiceNoAutoRename]; // continues in -netServiceDidPublish: or -netService:didNotPublish: ... }这里是基于NSNetService把先前创建的socket发布出去,便于clienti连接和请求。
这里是client通过前面server发布出来了netservice,发起对socket的连接:
netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:@"Test"];
server监听并处理数据请求
server会在accept的事件侦听的回调函数里对socket打开stream,并侦听stream上的各种IO事件:
CFStreamCreatePairWithSocket(NULL, fd, &readStream, NULL); assert(readStream != NULL); self.networkStream = (__bridge NSInputStream *) readStream; CFRelease(readStream); [self.networkStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket]; self.networkStream.delegate = self; [self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [self.networkStream open]; - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode // An NSStream delegate callback that's called when events happen on our // network stream. { assert(aStream == self.networkStream); #pragma unused(aStream) switch (eventCode) { case NSStreamEventOpenCompleted: { [self updateStatus:@"Opened connection"]; } break; case NSStreamEventHasBytesAvailable: { NSInteger bytesRead; uint8_t buffer[32768]; [self updateStatus:@"Receiving"]; // Pull some data off the network. bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)]; if (bytesRead == -1) { [self stopReceiveWithStatus:@"Network read error"]; } else if (bytesRead == 0) { [self stopReceiveWithStatus:nil]; } else { NSInteger bytesWritten; NSInteger bytesWrittenSoFar; // Write to the file. bytesWrittenSoFar = 0; do { bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar]; assert(bytesWritten != 0); if (bytesWritten == -1) { [self stopReceiveWithStatus:@"File write error"]; break; } else { bytesWrittenSoFar += bytesWritten; } } while (bytesWrittenSoFar != bytesRead); } } break; case NSStreamEventHasSpaceAvailable: { assert(NO); // should never happen for the output stream } break; case NSStreamEventErrorOccurred: { [self stopReceiveWithStatus:@"Stream open error"]; } break; case NSStreamEventEndEncountered: { // ignore } break; default: { assert(NO); } break; } }以上代码是server监听到有数据发过来时,把数据写入本地文件中,这里实际上就是把网络inputstream写入File的outputstream。这里handleevent方法是当设置了self.networkStream.delegate = self后IO事件的回调函数,handleevent里就需要根据不同的事件类型进行不同的处理。
client发送和接受数据流
client的数据处理与server类似也是通过对网络stream的事件监听来完成:
self.networkStream = output;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
assert(aStream == self.networkStream);
#pragma unused(aStream)
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:@"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:@"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:@"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:@"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:@"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}这里的过程与server端正好相反,是从file的Inputstream中读入数据,并写入网络的outputsteam中。
客户端:
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
1. 创建连接
CFSocketContext sockContext = {0, // 结构体的版本,必须为0
self,
// 一个任意指针的数据,可以用在创建时CFSocket对象相关联。这个指针被传递给所有的上下文中定义的回调。
NULL, // 一个定义在上面指针中的retain的回调, 可以为NULL
NULL, NULL};
CFSocketRef _socket = (kCFAllocatorDefault, // 为新对象分配内存,可以为nil
PF_INET, // 协议族,如果为0或者负数,则默认为PF_INET
SOCK_STREAM, // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM
IPPROTO_TCP, // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
kCFSocketConnectCallBack, // 触发回调函数的socket消息类型,具体见Callback Types
TCPServerConnectCallBack, // 上面情况下触发的回调函数
&sockContext // 一个持有CFSocket结构信息的对象,可以为nil
);
if (_socket != nil) {
struct sockaddr_in addr4; // IPV4
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(8888);
addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]); // 把字符串的地址转换为机器可识别的网络地址
// 把sockaddr_in结构体中的地址转换为Data
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
CFSocketConnectToAddress(_socket, // 连接的socket
address, // CFDataRef类型的包含上面socket的远程地址的对象
-1 // 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为
kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent(); // 获取当前线程的循环
// 创建一个循环,但并没有真正加如到循环中,需要调用CFRunLoopAddSource
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
CFRunLoopAddSource(cRunRef, // 运行循环
sourceRef, // 增加的运行循环源, 它会被retain一次
kCFRunLoopCommonModes // 增加的运行循环源的模式
);
CFRelease(courceRef);
}2. 设置回调函数
1 // socket回调函数的格式:
2 static void TCPServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef
address, const void *data, void *info) {
3 if (data != NULL) {
4 // 当socket为kCFSocketConnectCallBack时,失败时回调失败会返回一个错误代码指针,其他情况返
回NULL
5 NSLog(@"连接失败");
6 return;
7 }
8 TCPClient *client = (TCPClient *)info;
9 // 读取接收的数据
10 [info performSlectorInBackground:@selector(readStream) withObject:nil];
11 }
12 3. 接收发送数据
13 // 读取接收的数据
14 - (void)readStream {
15 char buffer[1024];
16 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
17 while (recv(CFSocketGetNative(_socket), //与本机关联的Socket 如果已经失效返回-
1:INVALID_SOCKET
18 buffer, sizeof(buffer), 0)) {
19 NSLog(@"%@", [NSString stringWithUTF8String:buffer]);
20 }
21 }
22 // 发送数据
23 - (void)sendMessage {
24 NSString *stringTosend = @"你好";
25 char *data = [stringTosend UTF8String];
26 send(SFSocketGetNative(_socket), data, strlen(data) + 1, 0);
27 }
28 服务器端:
29 CFSockteRef _socket;
30 CFWriteStreamRef outputStream = NULL;
31 int setupSocket() {
32 _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
33 if (NULL == _socket) {
34 NSLog(@"Cannot create socket!");
35 return 0;
36 }
37
38 int optval = 1;
39 setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, // 允许重用本地地址和端口
40 (void *)&optval, sizeof(optval));
41
42 struct sockaddr_in addr4;
43 memset(&addr4, 0, sizeof(addr4));
44 addr4.sin_len = sizeof(addr4);
45 addr4.sin_family = AF_INET;
46 addr4.sin_port = htons(port);
47 addr4.sin_addr.s_addr = htonl(INADDR_ANY);
48 CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
49
50 if (kCFSocketSuccess != CFSocketSetAddress(_socket, address)) {
51 NSLog(@"Bind to address failed!");
52 if (_socket)
53 CFRelease(_socket);
54 _socket = NULL;
55 return 0;
56 }
57
58 CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
59 CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
60 CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
61 CFRelease(source);
62
63 return 1;
64 }
65 // socket回调函数,同客户端
66 void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address,
const void *data, void *info) {
67 if (kCFSocketAcceptCallBack == type) {
68 // 本地套接字句柄
69 CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
70 uint8_t name[SOCK_MAXADDRLEN];
71 socklen_t nameLen = sizeof(name);
72 if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen)) {
73 NSLog(@"error");
74 exit(1);
75 }
76 NSLog(@"%@ connected.", inet_ntoa( ((struct sockaddr_in *)name)->sin_addr )):
77 CFReadStreamRef iStream;
78 CFWriteStreamRef oStream;
79 // 创建一个可读写的socket连接
80 CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream,
&oStream);
81 if (iStream && oStream) {
82 CFStreamClientContext streamContext = {0, NULL, NULL, NULL};
83 if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvaiable,
84 readStream, // 回调函数,当有可读的数据时调用
85 &streamContext)){
86 exit(1);
87 }
88 if (!CFReadStreamSetClient(iStream, kCFStreamEventCanAcceptBytes, writeStream,
&streamContext)){
89 exit(1);
90 }
91 CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(),
kCFRunLoopCommomModes);
92 CFWriteStreamScheduleWithRunLoop(wStream, CFRunLoopGetCurrent(),
kCFRunLoopCommomModes);
93 CFReadStreamOpen(iStream);
94 CFWriteStreamOpen(wStream);
95 } else {
96 close(nativeSocketHandle);
97 }
98 }
99 }
100 // 读取数据
101 void readStream(CFReadStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo) {
102 UInt8 buff[255];
103 CFReadStreamRead(stream, buff, 255);
104 printf("received: %s", buff);
105 }
106 void writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
107 outputStream = stream;
108 }
109 main {
110 char *str = "nihao";
111
112 if (outputStream != NULL) {
113 CFWriteStreamWrite(outputStream, str, strlen(line) + 1);
114 } else {
115 NSLog(@"Cannot send data!");
116 }
117 }
118 // 开辟一个线程线程函数中
119 void runLoopInThread() {
120 int res = setupSocket();
121 if (!res) {
122 exit(1);
123 }
124 CFRunLoopRun(); // 运行当前线程的CFRunLoop对象
125 }您可能感兴趣的文章:
-
本站(WWW.)旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。
本站(WWW.)站内文章除注明原创外,均为转载,整理或搜集自网络.欢迎任何形式的转载,转载请注明出处.
转载请注明:文章转载自:[169IT-IT技术资讯]
本文标题:IOS开发之socket网络编程(基于SimpleNetworkStreams的c/s程序)