Asynchronous socket I/O using NSFileHandle
Asynchronous socket I/O using NSFileHandle
- Subject: Asynchronous socket I/O using NSFileHandle
- From: "Erik M. Buck" <email@hidden>
- Date: Sat, 10 Nov 2001 14:48:01 -0600
>
> 1. Establish an asynchronous telnet channel to some telnetish
>
> service? NSFileHandle combined with SimpleSockets (to get a Unix
>
> file descriptor for NSFileHandle) and suitable notifications seems
>
> to be an answer, but an example would be nice.
>
>
You can always do this using C and BSD API.
>
If I was at the office I could mail sample code using NSFileHandle to you.
>
I am at the office now. Here is some sample code with references changed to
protect the innocent.
This code comes from Openstep for Windows NT, so details of socket creation
may differ with BSD. I don't recall the differences right now.
- (id)initWithPort:(unsigned short)aPort
/*" Designated initializer: Initialize with the specified port number "*/
{
self = [super init];
_clientConnections = [[NSMutableArray alloc] init];
_serverPort = aPort;
// Windows needs socket initialization.
#ifdef _WIN32
{
DWORD vR = MAKEWORD(1, 1);
WSADATA wsaData;
(void)WSAStartup(vR, &wsaData);
}
#endif
// Setup server socket.
_serverSocket = [self openServerSocket:_serverPort];
// Configure for any number of client connections
if (_serverSocket != INVALID_SOCKET) {
_serverHandle = [[NSFileHandle alloc]
initWithNativeHandle:(void*)_serverSocket];
if (_serverHandle != nil) {
// Register for notification when connect happens
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(acceptNewClient:)
name:NSFileHandleConnectionAcceptedNotification
object:_serverHandle];
// Tell file handle to notify when connect happens
[_serverHandle acceptConnectionInBackgroundAndNotify];
}
} else {
fprintf(stderr, "ERROR: Failed to set up server socket on port %i.",
_serverPort);
}
return self;
}
- (SOCKET)openServerSocket:(unsigned short)aPort
/*" Open a socket, bind it to aPort, and listen for client connections "*/
{
SOCKET socketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in socketAddress;
socketAddress.sin_family = AF_INET;
socketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
socketAddress.sin_port = htons(aPort);
if (socketFD != INVALID_SOCKET) {
// Initialize
//
int bindResult;
bindResult = bind(socketFD, (struct sockaddr *)&socketAddress,
sizeof(socketAddress));
if (bindResult < 0) {
closesocket(socketFD);
socketFD = INVALID_SOCKET;
fprintf(stderr, "ERROR: bind() failed.\n");
} else {
int listenResult;
listenResult = listen(socketFD, 5);
if(listenResult < 0) {
closesocket(socketFD);
socketFD = INVALID_SOCKET;
fprintf(stderr, "ERROR: listen() failed.\n");
}
}
}
return socketFD;
}
- (void)acceptNewClient:(NSNotification*)aNotification
/*" Called automatically whenever a new client connects to the server (As
long as an instance of NSApplication exists!). "*/
{
NSFileHandle *clientHandle = [[aNotification userInfo]
objectForKey:NSFileHandleNotificationFileHandleItem];
if ([clientHandle isKindOfClass:[NSFileHandle class]]) {
MYServerConnection *newClientConnection;
fprintf(stderr, "New client connected.\n");
newClientConnection = [[MYServerConnection alloc]
initWithFileHandle:clientHandle];
[_clientConnections addObject:newClientConnection];
[newClientConnection release];
}
// Have to call this again to enable additional connections
[[aNotification object] acceptConnectionInBackgroundAndNotify];
}
The MYServerConnection class has code like the following to read and write
data asynchronously:
- (id)initWithFileHandle:(NSFileHandle *)aFileHandle
/*" Designated initializer "*/
{
self = [super init];
// retain reference to file handle for client communication
_vHandle = [aFileHandle retain];
// Register for notification when new data is availble
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDataAvailable:)
name:NSFileHandleDataAvailableNotification object:_vHandle];
// Tell handle to notify when new data is availble
[_vHandle waitForDataInBackgroundAndNotify];
return self;
}
- (void)handleDataAvailable:(NSNotification *)aNotification
/*" Called automatically when new data is available "*/
{
SOCKET clientSocket = -1;
char error[_MYConnectionErrorBufferSize + 1];
int errorLen = _MYConnectionErrorBufferSize;
char data[_MYConnectionReadBufferSize + 1];
int bytesRead;
int indexOfLastByteUsed = 0;
// Get the socket descriptor
clientSocket = (SOCKET)[_vHandle nativeHandle];
// Read up to _MYConnectionReadBufferSize bytes
bytesRead = recv(clientSocket, data, _MYConnectionReadBufferSize , 0);
//fprintf(stderr, "Read %d bytes\n", bytesRead);
if (bytesRead > 0) {
// Read successful. Check if we have data.
// Check for error
if (0 == getsockopt(clientSocket, SOL_SOCKET, SO_ERROR, error,
&errorLen)) {
if (error[0] != 0) {
fprintf(stderr, "ERROR: <s>\n", error);
}
} else {
fprintf(stderr, "ERROR: Failed getsockopt()\n");
}
}
// Tell handle to notify when new data is availble (Must do this every
time we read !)
[_vHandle waitForDataInBackgroundAndNotify];
}
elsewhere there is code like the following to write to the socket
{
SOCKET aSocket = (SOCKET)[_vHandle nativeHandle];
char error[17] = "";
int len = 16;
int numBytesWritten = send(aSocket, (char *)[sharedTempData bytes],
[sharedTempData length], 0);
if(numBytesWritten != [sharedTempData length]) {
fprintf(stderr, "ERROR: Failed to complete write to socket\n");
[_vHandle closeFile];
[_vHandle release];
_vHandle = nil;
}
// Check for error
if (getsockopt(aSocket, SOL_SOCKET, SO_ERROR, error, &len) == 0) {
if (error[0] != 0) {
fprintf(stderr, "ERROR: Could not write to socket\n");
}
} else {
fprintf(stderr, "ERROR: getsockopt() failed\n");
}
}