2

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?
Manelion
  • 167
  • 9
  • This may not be true, but if you are adding to run loop of main thread, try to create another thread, then run run-loop in the thread and see if you still have occasional delay. – beshio Jan 25 '18 at 03:42
  • One more comment.Probably the least overhead "callback" scheme should be to wait w/ posix select(). if above doesn't work, I would try select() at another thread. – beshio Jan 25 '18 at 03:58
  • Thanks, behsio. I was looking at creating another thread. My call back function is as follows: `void getSocketDataCallBack (CFSocketRef cfSocketRef, CFSocketCallBackType cbType, CFDataRef address, const void *data, void *info)`. How would I do that using NSThread? – Manelion Jan 29 '18 at 19:49
  • In a related Q: in the context above, would a call to CFRunLoopGetCurrent() return the same as CFRunLoopGetMain() in the CFRunLoopAddSource() call? I'm thinking in this case they would be return the same thing, but wanted to understand it a bit more. Thanks! – Manelion Jan 29 '18 at 23:28
  • Since it's too long to put in comment, I wrote in answer below. – beshio Jan 30 '18 at 01:40

1 Answers1

3

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.

beshio
  • 794
  • 2
  • 7
  • 17
  • Thanks so much, beshio! As I am programming this whole setup on the client side, I was able to remove the intermediate step (your `socketDataCallback`) and set up my version of the client callback from `workerThread` – Manelion Jan 30 '18 at 18:08
  • I have more testing to do, but it appears the callback timing variability is less. The system still delays the socket read callbacks on occasion, but I may not be able to totally alleviate that problem as I'm trying to do real-time audio playback inside a non-real-time iOS – Manelion Jan 30 '18 at 18:18
  • So after testing, I'm still getting more variability than I would like in the timing of the callbacks. I've tried setting the policy and priority of the new thread as follows, but to no avail: `pthread_attr_setinheritsched(&at, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&at, SCHED_FIFO);` Then: `int policy; struct sched_param param; pthread_getschedparam(th, &policy, &param); param.sched_priority = 32; //default=31 pthread_setschedparam(th, policy, &param);` Any other ideas? – Manelion Feb 02 '18 at 22:43
  • How about waiting with POSIX select() call instead of socket call back in your worker thread ? If you increase scheduling priority of the worker thread and wait with select() call, I think that's the best we can do. I believe waiting with select() is less overhead than socket call back. – beshio Feb 03 '18 at 01:46