CFSocket data callbacks

放肆的年华 提交于 2019-12-13 16:21:02

问题


In an iPhone app, I create a CFSocket object from an existing native UDP socket and set up a data callback whenever the socket receives some data. I then add that to my main program loop:

    //Set socket descriptor field
    cbData.s = udpSocket.getSocketDescriptor();

    CFSocketContext udpSocketContext;
    memset(&udpSocketContext, 0, sizeof(udpSocketContext));
    udpSocketContext.info = &cbData;

    cbData.socketRef = CFSocketCreateWithNative(NULL, cbData.s, kCFSocketDataCallBack, &getSocketDataCallBack, &udpSocketContext);
    cbData.runLoopSourceRef = CFSocketCreateRunLoopSource( NULL, cbData.socketRef, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), cbData.runLoopSourceRef, kCFRunLoopCommonModes);

I send 1024-byte datagrams over WiFi from a separate Mac server app every 5 mS, and receive them on my iPhone in my getSocketDataCallBack routine.

I expect getSocketDataCallBack to be called every 5 mS (to match the period of the datagrams being sent from the Mac), which happens the majority of times. BUT, the calls often get delayed by 10s or 100s of mS. Thereafter, I get a rapid sequence of callbacks (fractions of a mS) to retrieve the multiple datagrams that have piled up over that delay.

As iOS obviously keeps the delayed datagrams around,

  • is there any way to grab all the delayed datagrams from the system at once instead of getSocketDataCallBack being called over and over in quick succession?

    [I do query how many bytes are available in the callback ala:

           CFDataRef dataRef = (CFDataRef)data;
           numBytesReceived = CFDataGetLength(dataRef);
    

    but 'numBytesReceived' is always reported as 1024.]

  • Alternatively, is there any way to improve/lessen the socket callback timing variability through other means?

回答1:


I'm using socket call back for Inter Process Communication (actually, inter thread communication) with UNIX socket. How we use socket is identical to the TCP/UDP.

The code below is written in c/obj-c and using posix thread. To translate it to Swift/NSThread should not be difficult.

Note the program below works as a server side, which means the program creates socket where the clients connect to. Once the client connected to the socket, the system automatically accepts the connection and allocates another file descriptor to read/write. The socket call back reflects this two stage operation. Initially we create the socket, we then add as run-loop source so the system can call the call back when the client attempted to connect. The system accepts, then allocates and tells the call back a file descriptor to read/write with the client. We then create another run-loop source from the read/write fd and add to run-loop. This second call back is called when rx/tx data is ready.

MAIN THREAD:

The main thread creates UNIX socket and worker thread. The socket fd is passed as argument of the worker thread.

#import <stdio.h>
#import <string.h>
#import <stdlib.h>
#import <unistd.h>
#import <pthread.h>
#import <sys/socket.h>
#import <sys/un.h>
#import <sys/stat.h>
#import <sys/types.h>
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
int setup(const char *ipcNode) {
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) {
        return -1;
    }
    struct sockaddr_un sa = {0};
    sa.sun_len = sizeof(sa);
    sa.sun_family = AF_UNIX;
    strcpy(sa.sun_path, ipcNode);
    remove(sa.sun_path);
    if (bind(sockfd, (struct sockaddr*)&sa, sizeof(struct sockaddr_un)) == -1) {
        close(sockfd);
        return -1;
    }
    // start up worker thread
    pthread_attr_t at;
    pthread_attr_init(&at);
    pthread_attr_setdetachstate(&at, PTHREAD_CREATE_DETACHED);
    pthread_t th;
    pthread_create(&th, &at, workerThread, (void *)(long)(sockfd));
    return 1;
}

WORKER THREAD:

The program works as a server. So, it waits to get connected by client (via connect()). Once it's connected, the system automatically calls accept() and allocates read/write fd to communicate with the client. This fd is passed to accept-call back routine socketDataCallback(). Then we create another call back clientDataCallback() with the read/write fd.

// worker thread
//
void *workerThread(void *tprm) {
    int sockfd = (int)tprm;
    int retval = listen(sockfd, 1);  // mark as "server" side. here, accept only 1 connection request at a time
    if (retval != 0) {
        return NULL;
    }
    // create CFSocket and register it as data source.
    CFSocketRef socket = CFSocketCreateWithNative(kCFAllocatorDefault, sockfd, kCFSocketAcceptCallBack, socketDataCallback, nil);
    // don't close native fd on CFSocketInvalidate
    CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
    // create run loop source
    CFRunLoopSourceRef socketRunLoop = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
    // add to run loop
    CFRunLoopAddSource(CFRunLoopGetCurrent(), socketRunLoop, kCFRunLoopCommonModes);
    CFRelease(socketRunLoop);
    CFRelease(socket);
    CFRunLoopRun();
    // not return here untill run loop stops
    close(sockfd);
    return NULL;
}

// socket connection w/ client side. create another data source and add to run-loop
//
void socketDataCallback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) {
    CFSocketContext socketContext;
    memset(&socketContext, 0, sizeof(CFSocketContext));
    int clientfd = *((int *)data);   // get file descriptor (fd)
    socketContext.info = (void *)((long)clientfd);  // set fd at info of socketContext
    // create CFSocket for tx/rx w/ connected client
    CFSocketRef socket = CFSocketCreateWithNative(kCFAllocatorDefault, clientfd, kCFSocketReadCallBack | kCFSocketWriteCallBack, clientDataCallback, &socketContext);
    CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
    CFRunLoopSourceRef socketRunLoop = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), socketRunLoop, kCFRunLoopCommonModes);
    CFRelease(socket);
    CFRelease(socketRunLoop);
}

// data to/from client
//
void clientDataCallback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) {
    if (callbackType & kCFSocketWriteCallBack) {
        // your own tx data prcess here
        // txDataCallback(s, callbackType, address, data, info);
    }
    if (!(callbackType & kCFSocketReadCallBack))  return;
    // extract fd
    int fd = (int)((long)info);
    // read data, and do some work
    uint8_t rxdata[1024];
    size_t nr = read(fd, rxdata, 1024);
    if (!nr) {
        // socket closed
        handleSocketClosed(s);
        return;
    }
    // your own rx process here
}

// socket closed
//
void handleSocketClosed(CFSocketRef s) {
    // any clean up process here, then
    CFSocketInvalidate(s);
    // stop run loop if necessary
    // CFRunLoopStop(CFRunLoopGetCurrent());
}

If you are working at client side, things get a bit easier. You get a read/write fd with connect() call. Then you create CFSockeRef and add to run-loop by using the fd.

Hope this helps.

EDIT: How to wait with POSIX select(). To wait with POSIX select() at worker thread is simpler than socket call back. If you are on client side, then:

int sockfd = socket(...);
bind(sockfd, ...)
connect(sockfd, ...);
while (1) {
    int nfds = sockfd+1;
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(sockfd, &rfds);
    int retval = select(nfds, &rfds, NULL, NULL, NULL);
    if (retval == -1)  break;
    if (retval > 0) {
        uint8_t rxdata[1024];
        size_t nr = read(sockfd, rxdata, 1024);
        if (!nr) {
            // socket closed.
            break;
        }
        // do your rx process here
    }
}

Run the code above at your worker thread.



来源:https://stackoverflow.com/questions/48434976/cfsocket-data-callbacks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!