IOS底层网络之Socket

来源:互联网 发布:淘宝买hiv试纸准确吗 编辑:程序博客网 时间:2024/06/06 15:38

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;    }}
0 0