iOS网络编程笔记——Socket编程
一、什么是Socket通信:
Socket是网络上的两个程序,通过一个双向的通信连接,实现数据的交换。这个双向连路的一端称为socket。socket通常用来实现客户方和服务方的连接。socket是TCP/IP协议的一个十分流行的编程接口。一个socket由一个IP地址和一个端口号唯一确定。TCP/IP协议的传输层又有两种协议:TCP(传输控制协议)和UDP(用户数据报协议)。TCP是基于连接的,而UDP是无连接的;TCP对系统资源的要求较多,而UDP少;TCP保证数据的正确性而UDP可能丢包;TCP保证数据顺序而UDP不保证。
二、Socket编程:
2.1 服务器端监听某个端口是否有连接请求。服务器端程序处于堵塞状态,直到客户端像服务器端发出连接请求,服务器端接受请求才能向下运行。一旦连接建立起来,通过socket可以获得输入输出流对象。借助于输入输出流对象就可以实现与客户端的通信,最后不要忘记关闭socket和释放一些资源(包括关闭输入/输出流)。
2.2 客户端流程是先指定要通信的服务器IP地址、端口和采用的传输协议(TCP/UDP),向服务器发出连接请求,服务器有应答请求之后,就会建立连接。之后与服务器端一样。
三、 iOS网络编程的层次结构:
(1)Foundation:提供了 NSStream,Bonjour,GameKit,基于Core Foundation框架实现。
(2)Core Foundation:提供了CFStream,CFNetServices,面向C语言实现。
(3)BSD Socket :面向C实现,完全使用C编写。
四、实例:NSStream&CFStream实现TCP Socket服务器端
#import <CoreFoundation/CoreFoundation.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 void AcceptCallBack(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void *); void ReadStreamClientCallBack (CFReadStreamRef stream, CFStreamEventType eventType,void *); int main(int argc, const char * argv[]) { /* 定义一个Server Socket引用 */ CFSocketRef sserver; /* 创建socket context */ CFSocketContext CTX = { 0, NULL, NULL, NULL, NULL }; /* 创建server socket TCP IPv4 设置回调函数 */ sserver = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX); if (sserver == NULL) return -1; /* 设置是否重新绑定标志 */ int yes = 1; /* 设置socket属性 SOL_SOCKET是设置tcp SO_REUSEADDR是重新绑定,yes 是否重新绑定*/ setsockopt(CFSocketGetNative(sserver), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); /* 设置端口和地址 */ struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); //memset函数对指定的地址进行内存拷贝 addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; //AF_INET是设置 IPv4 addr.sin_port = htons(PORT); //htons函数 无符号短整型数转换成“网络字节序” addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY有内核分配,htonl函数 无符号长整型数转换成“网络字节序” /* 从指定字节缓冲区复制,一个不可变的CFData对象*/ CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8*)&addr, sizeof(addr)); /* 设置Socket*/ if (CFSocketSetAddress(sserver, (CFDataRef)address) != kCFSocketSuccess) { fprintf(stderr, "Socket绑定失败\n"); CFRelease(sserver); return -1; } /* 创建一个Run Loop Socket源 */ CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sserver, 0); /* Socket源添加到Run Loop中 */ CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes); CFRelease(sourceRef); printf("Socket listening on port %d\n", PORT); /* 运行Loop */ CFRunLoopRun(); } /* 接收客户端请求后,回调函数 */ void AcceptCallBack( CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { CFReadStreamRef readStream = NULL; CFWriteStreamRef writeStream = NULL; /* data 参数涵义是,如果是kCFSocketAcceptCallBack类型,data是CFSocketNativeHandle类型的指针 */ CFSocketNativeHandle sock = *(CFSocketNativeHandle *) data; /* 创建读写Socket流 */ CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock, &readStream, &writeStream); if (!readStream || !writeStream) { close(sock); fprintf(stderr, "CFStreamCreatePairWithSocket() 失败\n"); return; } CFStreamClientContext streamCtxt = {0, NULL, NULL, NULL, NULL}; // 注册两种回调函数 CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack, &streamCtxt); CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack, &streamCtxt); //加入到循环当中 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); CFReadStreamOpen(readStream); CFWriteStreamOpen(writeStream); } /* 读取流操作 客户端有数据过来时候调用 */ void ReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo){ UInt8 buff[255]; CFReadStreamRef inputStream = stream; if(NULL != inputStream) { CFReadStreamRead(stream, buff, 255); printf("接受到数据:%s\n",buff); CFReadStreamClose(inputStream); CFReadStreamUnscheduleFromRunLoop(inputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); inputStream = NULL; } } /* 写入流操作 客户端在读取数据时候调用 */ void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo) { CFWriteStreamRef outputStream = stream; //输出 UInt8 buff[] = "Hello Client!"; if(NULL != outputStream) { CFWriteStreamWrite(outputStream, buff, strlen((const char*)buff)+1); //关闭输出流 CFWriteStreamClose(outputStream); CFWriteStreamUnscheduleFromRunLoop(outputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); outputStream = NULL; } }
NSStream&CFSteam实现TCP Socket客户端
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void)initNetworkCommunication { CFReadStreamRef readStream; CFWriteStreamRef writeStream; CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"192.168.1.112", PORT, &readStream, &writeStream); _inputStream = (__bridge_transfer NSInputStream *)readStream; _outputStream = (__bridge_transfer NSOutputStream *)writeStream; [_inputStream setDelegate:self]; [_outputStream setDelegate:self]; [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream open]; [_outputStream open]; } -(void)close { [_outputStream close]; [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_outputStream setDelegate:nil]; [_inputStream close]; [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [_inputStream setDelegate:nil]; } - (IBAction)sendData:(id)sender { flag = 0; [self initNetworkCommunication]; } - (IBAction)receiveData:(id)sender { flag = 1; [self initNetworkCommunication]; } -(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { NSString *event; switch (streamEvent) { case NSStreamEventNone: event = @"NSStreamEventNone"; break; case NSStreamEventOpenCompleted: event = @"NSStreamEventOpenCompleted"; break; case NSStreamEventHasBytesAvailable: event = @"NSStreamEventHasBytesAvailable"; if (flag ==1 && theStream == _inputStream) { NSMutableData *input = [[NSMutableData alloc] init]; uint8_t buffer[1024]; NSInteger len; while([_inputStream hasBytesAvailable]) { len = [_inputStream read:buffer maxLength:sizeof(buffer)]; if (len > 0) { [input appendBytes:buffer length:len]; } } NSString *resultstring = [[NSString alloc] initWithData:input encoding:NSUTF8StringEncoding]; NSLog(@"接收:%@",resultstring); _message.text = resultstring; } break; case NSStreamEventHasSpaceAvailable: event = @"NSStreamEventHasSpaceAvailable"; if (flag ==0 && theStream == _outputStream) { //输出 UInt8 buff[] = "Hello Server!"; [_outputStream write:buff maxLength: strlen((const char*)buff)+1]; //必须关闭输出流否则,服务器端一直读取不会停止, _tipMessage.text = @"发送成功!"; [_outputStream close]; } break; case NSStreamEventErrorOccurred: event = @"NSStreamEventErrorOccurred"; [self close]; break; case NSStreamEventEndEncountered: event = @"NSStreamEventEndEncountered"; NSLog(@"Error:%ld:%@",[[theStream streamError] code], [[theStream streamError] localizedDescription]); break; default: [self close]; event = @"Unknown"; break; } NSLog(@"event------%@",event); }
实际上,socket编程是一种网络编程的标准,客户端和服务器端可以不受编程语言的限制完全自由的通信。客户端可以是oc编写的iOS程序,服务端可以是java编写的程序,通信双方定义好数据交互格式就可以了。
五、最后说一下Socket连接与http连接
由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。
注:源码参考自《iOS网络编程与云端应用最佳实践》