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程序)