首页 > 系统 > iOS > 正文

IOS底层网络之Socket

2019-11-06 09:55:01
字体:
来源:转载
供稿:网友

BSD Socket

创建Socket

调用socket(int addressFamily, int type, int PRotocol),返回值类型int

参数: - addressFamily:Socket的网络域,ipV4(AF_INET )或者 IPV6(AF_INET6); - type:Socket类型,流式Socket(SOCK_STREAM)、数据包Socket(SOCK_DGRAM) - protocol:协议枚举值,根据Socket类型自动选择,流式选择IPPROTO_TCP,数据包则选择IPPROTO_UDP。

返回值:如果创建成功,返回值为新文件说明符的号码;如果创建失败,返回-1。

注意:创建完成后,通信尚未开始,Socket也没有被指定为输入或输出Socket(直到首次使用Socket时才会指定)。

建立连接

配置Socket服务器 (1)先调用bind(int socketFileDescriptor, sockaddr *addressToBind, int addressStructLength),与具有唯一地址的Socket关联。接收一个Socket并将其分配或绑定到某个特定的地址与端口。成功则返回0,否则返回-1. (2)如果在socket(int, int, int)中的连接类型如果为UDP:可以开始向外界传输数据了,因为UDP是个无连接的协议,不需要再另一端监听; (3)若为TCP:要调用listen(int socketFileDescriptor, int backlogSize)来建立好缓冲区队列的数据结构。socketFileDescriptor会成为只读socket,不能用于发送消息;backlogSize表示有多少个挂起的连接在排队的同时等待服务器代码的使用。在监听时,服务器会等待进来的连接请求并调用accept(int socketFileDescriptor, sockaddr *clientAddress, int clientAddressStructLength)来接收请求。这会将挂起的请求从缓冲区中移除,并使用客户端的地址信息(主要是IP和port)来装配clientAddress结构体。接受了挂起的请求后,服务器就可以从客户端接收消息了。

Socket客户端连接 (1)TCP Socket:客户端首先通过connect(int socketFileDescriptor, sockaddr *serverAddress, int serverAddressStructLength)协商一个到服务器的连接。在TCP握手时该调用会阻塞,成功返回0,否则-1. (2)UDP Socket:connect方法是可选的。如果调用它则会为所有的UDP传输Socket设定默认地址,这样会方便UDP数据包的发送和接收。如果设备通过主机名而不是IP地址进行连接,它可能不清楚如何继续,因为socketaddr结构体只包含一个IP地址。可以通过DNS(Domain Name System)将主机名转为IP地址。

发送/接收消息

TCP

发送消息:int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags), socketFileDescriptor:其描述的Socket会将缓存中介于0与bufferLength之间的字节发送出去。成功则返回成功发送出去的字节数量,失败返回-1。

接收消息:int receive(int socketFileDescriptor, char *buffer, int bufferLength, int flags),缓存会通过从Socket读取的第一个bufferLength长度的字节副本来装配。成功则返回成功读取的字节数量,失败返回-1。

UDP: (1)如果调用connect()来设定默认地址的UDP,则可以同上TCP一样调用send和receive; (2)没有调用connect():

发送消息:int sendto(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)使用相同的Socket连接发送给多个地址,和send类似,只不过它为目标地址提供额外的参数;

接收消息:int recvfrom(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength),最后一个参数是指向整数的指针,值是fromAddress结构体的最终长度。

代码举例:创建socket来接收信息

- (void)loadCurrentStatus:(NSURL *)url { // 创建流式Socket int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0); if (socketFileDescriptor == -1) { // 创建失败 return; } // 将主机名转为IP struct hostent *remoteHostEnt = gethostbyname([[url host] UTF8String]); if (remoteHostEnt == NULL) { // 转换失败 return; } struct in_addr *remoteAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0]; // 设置socket参数来打开IP地址 struct sockaddr_in socketParameters; socketParameters.sin_family = AF_INET; socketParameters.sin_addr = *remoteAddr; socketParameters.sin_port = htons([[url port] intValue]); // 整数转为网络字节序 // 连接socket // 当sin_family为AF_INET时,sockaddr_in和sockaddr这两个结构体布局一样,所以sockaddr_in可以转为sockaddr if (connect(socketFileDescriptor, (struct sockaddr * )&socketParameters, sizeof(socketParameters)) == -1) { // 连接失败 return; } // 连接成功 NSMutableData *data = [[NSMutableData alloc] init]; BOOL waitingForData = YES; // 接收数据 while(waitingForData) { const char *buffer[1024]; int length = sizeof(buffer); // read a buffer's amount of data from the socket, the number of bytes read is returned. int result = recv(socketFileDescriptor, &buffer, length, 0); if (result > 0) { // 接收成功 [data appendBytes:buffer length:result]; } else { waitingForData = NO; // 退出接收数据 } } // 读取完成后关闭socket close(socketFileDescriptor); NSString *resultsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"received string: %@", resultsString);}

CFNetwork

对BSD Socket的一层轻量级封装,主要优势在于被集成到系统级的设置与主运行循环中。如必要时开启无效以及通过系统范围的VPN进行路由等,并且没有什么严重的缺陷。

创建socket:

CFStreamCreatePairWithSocketToHost(),可以针对给定的主机名和端口创建一对socket,一个用于读,一个用于写。其中框架会负责将主机名转换为IP地址,将端口号转换为网络字节序。如果不需要其中一个Socket,只需将NULL作为读或写流参数,就不会创建它了。

注意:使用前,必须通过CFReadStreamOpen()或CFWriteStreamOpen()打开流。这两个调用都是异步的,在成功打开后会通过kCFStreamEventOpenCompleted调用回调函数。

代码举例:创建与打开流

- (void)loadCurrentStatus:(NSURL *)url { // keep a reference to self to use for controller callbacks CFStreamClientContext ctx = {0, (__bridge void *)self, NULL, NULL, NULL}; // get callbacks for stream data, stream end, and any errors CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | // socket有可以读取的字节 kCFStreamEventEndEncountered | // socket到达字节流的末尾 kCFStreamEventErrorOccurred); // 操作出现错误 // 创建一个只读socket CFReadStreamRef readStream; CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], [[url port] intValue], &readStream, NULL); //schedule the stream ton the run loop to enable callbacks // 如果设置了kCFStreamEventOpenComplete,打开成功后会调用回调函数 // 注册socket回调函数 if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) { // 根据给定的运行循环来调度流 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); } else { //调用回调失败 return; } // 打开readStream if (CFReadStreamOpen(readStream) == NO) { // 打开失败 return; } CFErrorRef error = CFReadStreamCopyError(readStream); if (error != NULL) { if (CFErrorGetCode(error) != 0) { // 连接失败 } CFRelease(error); return; } //连接成功,去开始进程 CFRunLoopRun();}// 一个回调函数,当registeredEvents发生时,该函数就会被调用void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) { switch (event) { case kCFStreamEventHasBytesAvailable: // 读取bytes while(CFReadStreamHasBytesAvailable(stream)) { UInt8 buffer[1024]; int numBytesRead = CFReadStreamRead(stream, buffer, 1024); NSData *data = [NSData dataWithBytes:buffer length:numBytesRead]; NSLog(@"接收到的数据 = %@", data); } break; case kCFStreamEventErrorOccurred: { CFErrorRef error = CFReadStreamCopyError(stream); if (error != NULL) { if (CFErrorGetCode(error) != 0) { // 获取错误信息 } CFRelease(error); } } break; case kCFStreamEventEndEncountered: { // 关闭stream CFReadStreamClose(stream); // stop processing callback methods CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); // 结束当前线程的主运行 CFRunLoopStop(CFRunLoopGetCurrent()); } break; default: break; }}
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表