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