Network socket - Implementation client (iOS)/server (MacOS)
This post hasn't been updated for 3 years
1. What is network socket
- Is an endpoint of an inter-process communication across a computer network
- A socket that has been connected to another socket.
- Within the operation system and the applicatio that created a socket, a socket is referred to by a unique integer value called socket description. The operating system forwards the payload of incoming IP packets to the corresponding application by extracting the socket address information from the IP and transport protocol headers and stripping the headers from the application data.
2. Types
Serveral types of internet socket are available :
- Datagram sockets, also known as connectionless sockets, which use User Datagram Protocol (UDP)
- Stream sockets, also know as connection-oriented sockets, which use Transmission control Protocol (TCP)
- Raw sockets (or Raw IP Sockets). Typically available in routers and other network equipment. Here the transport layer is bypassed, and the packet headers are made accessible to the application.
3. How sockets work
- Sockets are commonly used for client and server interaction. Typical system configuration places the server on one machine, with the clients on other machines. The clients connect to the server, exchange information, and then disconnect.
- A socket has a typical flow of events. In a connection-oriented client-to-server model, the socket on the server process wait for request from client. To to this, the server first binds an address that clients can use to find the server. When the address is established, the server waits for clients to request a service. The client-to-server ddata exchange takes place when a client connects to the server through a socket. The server performs the client's request and send the reply back to client.
This is a typical flow of events for a connection-oriented socket:
-
The socket() API creates an endpoint for communications and returns a socket descriptor that represents the endpoint.
-
When an application has a socket descriptor, it can bind a unique name to the socket. Servers must bind a name to be accessible from the network.
-
The listen() API indicates a willingness to accept client connection requests. When a listen() API is issued for a socket, that socket cannot actively initiate connection requests. The listen() API is issued after a socket is allocated with a socket() API and the bind() API binds a name to the socket. A listen() API must be issued before an accept() API is issued.
-
The client application uses a connect() API on a stream socket to establish a connection to the server.
-
The server application uses the accept() API to accept a client connection request. The server must issue the bind() and listen() APIs successfully before it can issue an accept() API.
-
When a connection is established between stream sockets (between client and server), you can use any of the socket API data transfer APIs. Clients and servers have many data transfer APIs from which to choose, such as send(), recv(), read(), write(), and others.
-
When a server or client wants to stop operations, it issues a close() API to release any system resources acquired by the socket.
_ The socket APIs are located in the communications model between the application layer and the transport layer. The socket APIs are not a layer in the communication model. Socket APIs allow applications to interact with the transport or networking layers of the typical communications model. The arrows in the following figure show the position of a socket, and the communication layer that the socket provides._
4. Socket characteristics
Sockets share some common characteristics.
- A socket is represented by an integer. That integer is called a socket descriptor.
- A socket exists as long as the process maintains an open link to the socket.
- You can name a socket and use it to communicate with other sockets in a communication domain.
- Sockets perform the communication when the server accepts connections from them, or when it exchanges messages with them.
- You can create sockets in pairs (only for sockets in the AF_UNIX address family).
How socket characteristics are determined
When an application creates a socket with the socket() API, it must identify the socket by specifying these parameters:
- The socket address family determines the format of the address structure for the socket. This topic contains examples of each address family's address structure.
- The socket type determines the form of communication for the socket.
- The socket protocol determines the supported protocols that the socket uses.
These parameters or characteristics define the socket application and how it interoperates with other socket applications. Depending on the address family of a socket, you have different choices for the socket type and protocol. The following table shows the corresponding address family and its associated socket type and protocols:
5. Creating a connection-oriented socket
These server and client examples illustrate the socket APIs written for a connection-oriented protocol such as Transmission Control Protocol (TCP).
The following figure illustrates the client/server relationship of the sockets API for a connection-oriented protocol.
Socket flow of events: Connection-oriented server
The following sequence of the socket calls provides a description of the figure. It also describes the relationship between the server and client application in a connection-oriented design. Each set of flows contains links to usage notes on specific APIs.
-
The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol version 6 address family (AF_INET6) with the TCP transport (SOCK_STREAM) is used for this socket
-
The setsockopt() API allows the local address to be reused when the server is restarted before the required wait time expires.
-
After the socket descriptor is created, the bind() API gets a unique name for the socket. In this example, the user sets the s6_addr to zero, which allows connections to be established from any IPv4 or IPv6 client that specifies port 3005.
-
The listen() API allows the server to accept incoming client connections. In this example, the backlog is set to 10. This means that the system queues 10 incoming connection requests before the system starts rejecting the incoming requests.
-
The server uses the accept() API to accept an incoming connection request. The accept() call blocks indefinitely, waiting for the incoming connection to arrive.
-
The select() API allows the process to wait for an event to occur and to wake up the process when the event occurs. In this example, the system notifies the process only when data is available to be read. A 30-second timeout is used on this select() call.
-
The recv() API receives data from the client application. In this example, the client sends 250 bytes of data. Thus, the SO_RCVLOWAT socket option can be used, which specifies that recv() does not wake up until all 250 bytes of data have arrived.
-
The send() API echoes the data back to the client.
-
The close() API closes any open socket descriptors.
Socket flow of events: Connection-oriented client
The following sequence of APIs calls describes the relationship between the server and client application in a connection-oriented design.
- The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol version 6 address family (AF_INET6) with the TCP transport (SOCK_STREAM) is used for this socket.
- In the client example program, if the server string that was passed into the inet_pton() API was not a valid IPv6 address string, then it is assumed to be the host name of the server. In that case, use the getaddrinfo() API to retrieve the IP address of the server.
- After the socket descriptor is received, the connect() API is used to establish a connection to the server.
- The send() API sends 250 bytes of data to the server.
- The recv() API waits for the server to echo the 250 bytes of data back. In this example, the server responds with the same 250 bytes that was just sent. In the client example, the 250 bytes of the data might arrive in separate packets, so the recv() API can be used over and over until all 250 bytes have arrived.
- The close() API closes any open socket descriptors.
6. Creating a connectionless socket
Connectionless sockets do not establish a connection over which data is transferred. Instead, the server application specifies its name where a client can send requests.
Connectionless sockets use User Datagram Protocol (UDP) instead of TCP/IP.
The following figure illustrates the client/server relationship of the socket APIs used in the examples for a connectionless socket design.
Socket flow of events: Connectionless server
The following sequence of the socket calls provides a description of the figure and the following example programs. It also describes the relationship between the server and client application in a connectionless design. Each set of flows contains links to usage notes on specific APIs. If you need more details on the use of a particular API, you can use these links. The first example of a connectionless server uses the following sequence of API calls:
- The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol version 6 address family (AF_INET6) with the UDP transport (SOCK_DGRAM) is used for this socket
- After the socket descriptor is created, a bind() API gets a unique name for the socket. In this example, the user sets the s6_addr to zero, which means that the UDP port of 3555 is bound to all IPv4 and IPv6 addresses on the system
- The server uses the recvfrom() API to receive that data. The recvfrom() API waits indefinitely for data to arrive.
- The sendto() API echoes the data back to the client .
- The close() API ends any open socket descriptors.
Socket flow of events: Connectionless client
The second example of a connectionless client uses the following sequence of API calls.
- The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol version 6 address family (AF_INET6) with the UDP transport (SOCK_DGRAM) is used for this socket.
- In the client example program, if the server string that was passed into the inet_pton() API was not a valid IPv6 address string, then it is assumed to be the host name of the server. In that case, use the getaddrinfo() API to retrieve the IP address of the server.
- Use the sendto() API to send the data to the server.
- Use the recvfrom() API to receive the data from the server.
- The close() API ends any open socket descriptors.
Example
NetworkManager.h
//
// NetworkManager.h
// SocketServer
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 nguyenvandung. All rights reserved.
//
import <Foundation/Foundation.h>
@interface NetworkManager : NSObject
+ (NetworkManager *)sharedInstance;
- (NSString *)pathForTestImage:(NSUInteger)imageNumber;
- (NSString *)pathForTemporaryFileWithPrefix:(NSString *)prefix;
@property (nonatomic, assign, readonly ) NSUInteger networkOperationCount; // observable
- (void)didStartNetworkOperation;
- (void)didStopNetworkOperation;
@end
NetworkManager.m
//
// NetworkManager.m
// SocketServer
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 nguyenvandung. All rights reserved.
//
import "NetworkManager.h"
@interface NetworkManager ()
// read/write redeclaration of public read-only property
@property (nonatomic, assign, readwrite) NSUInteger networkOperationCount;
@end
@implementation NetworkManager
+ (NetworkManager *)sharedInstance {
static dispatch_once_t onceToken;
static NetworkManager * sSharedInstance;
dispatch_once(&onceToken, ^{
sSharedInstance = [[NetworkManager alloc] init];
});
return sSharedInstance;
}
- (NSString *)pathForTestImage:(NSUInteger)imageNumber {
NSUInteger power;
NSUInteger expansionFactor;
NSString * originalFilePath;
NSString * bigFilePath;
NSFileManager * fileManager;
NSDictionary * attrs;
unsigned long long originalFileSize;
unsigned long long bigFileSize;
assert( (imageNumber >= 1) && (imageNumber <= 4) );
power = imageNumber - 1;
if TARGET_IPHONE_SIMULATOR
power += 1;
endif
expansionFactor = (NSUInteger) pow(10, power);
fileManager = [NSFileManager defaultManager];
assert(fileManager != nil);
// Calculate paths to both the original file and the expanded file.
originalFilePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"TestImage%zu", (size_t) imageNumber] ofType:@"png"];
assert(originalFilePath != nil);
bigFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"TestImage%zu.png", (size_t) imageNumber]];
assert(bigFilePath != nil);
// Get the sizes of each.
attrs = [fileManager attributesOfItemAtPath:originalFilePath error:NULL];
assert(attrs != nil);
originalFileSize = [[attrs objectForKey:NSFileSize] unsignedLongLongValue];
attrs = [fileManager attributesOfItemAtPath:bigFilePath error:NULL];
if (attrs == NULL) {
bigFileSize = 0;
} else {
bigFileSize = [[attrs objectForKey:NSFileSize] unsignedLongLongValue];
}
// If the expanded file is missing, or the wrong size, create it from scratch.
if (bigFileSize != (originalFileSize * expansionFactor)) {
NSOutputStream * bigFileStream;
NSData * data;
const uint8_t * dataBuffer;
NSUInteger dataLength;
NSUInteger dataOffset;
NSUInteger counter;
NSLog(@"%5zu - %@", (size_t) expansionFactor, bigFilePath);
data = [NSData dataWithContentsOfMappedFile:originalFilePath];
assert(data != nil);
dataBuffer = [data bytes];
dataLength = [data length];
bigFileStream = [NSOutputStream outputStreamToFileAtPath:bigFilePath append:NO];
assert(bigFileStream != NULL);
[bigFileStream open];
for (counter = 0; counter < expansionFactor; counter++) {
dataOffset = 0;
while (dataOffset != dataLength) {
NSInteger bytesWritten;
bytesWritten = [bigFileStream write:&dataBuffer[dataOffset] maxLength:dataLength - dataOffset];
assert(bytesWritten > 0);
dataOffset += bytesWritten;
}
}
[bigFileStream close];
}
return bigFilePath;
}
- (NSString *)pathForTemporaryFileWithPrefix:(NSString *)prefix {
NSString * result;
CFUUIDRef uuid;
NSString * uuidStr;
uuid = CFUUIDCreate(NULL);
assert(uuid != NULL);
uuidStr = CFBridgingRelease( CFUUIDCreateString(NULL, uuid) );
result = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@", prefix, uuidStr]];
assert(result != nil);
CFRelease(uuid);
return result;
}
- (void)didStartNetworkOperation {
assert([NSThread isMainThread]);
self.networkOperationCount += 1;
}
- (void)didStopNetworkOperation {
assert([NSThread isMainThread]);
self.networkOperationCount -= 1;
}
@end
1. Server (MacOS)
SocketListen.h
//
// SocketListen.h
// SocketServer
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 nguyenvandung. All rights reserved.
//
import <Foundation/Foundation.h>
import <Cocoa/Cocoa.h>
@interface SocketListen : NSObject
{
}
@property (nonatomic, copy) NSString *host;
- (BOOL) isStarted;
- (void)startServer;
- (void)stopServer:(NSString *)reason;
- (id)initWithHost:(NSString *)host;
@end
SocketListen.m
//
// SocketListen.m
// SocketServer
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 nguyenvandung. All rights reserved.
//
import "SocketListen.h"
include <sys/socket.h>
include <netinet/in.h>
include <unistd.h>
import "NetworkManager.h"
@interface SocketListen() <NSStreamDelegate, NSNetServiceDelegate> {
}
@property (nonatomic, assign, readonly ) BOOL isStarted;
@property (nonatomic, assign, readonly ) BOOL isReceiving;
@property (nonatomic, strong, readwrite) NSNetService * netService;
@property (nonatomic, assign, readwrite) CFSocketRef listeningSocket;
@property (nonatomic, strong, readwrite) NSInputStream * networkStream;
@property (nonatomic, strong, readwrite) NSOutputStream * fileStream;
@property (nonatomic, copy, readwrite) NSString * filePath;
// forward declarations
- (void)stopServer:(NSString *)reason;
@end
@implementation SocketListen
- (id)initWithHost:(NSString *)ahost
{
self =[super init];
if (self){
self.host =ahost;
}
return self;
}
// These methods are used by the core transfer code to update the UI.
- (void)serverDidStartOnPort:(NSUInteger)port
{
assert( (port != 0) && (port < 65536) );
}
- (void)serverDidStopWithReason:(NSString *)reason
{
if (reason == nil) {
reason = @"Stopped";
}
}
- (void)receiveDidStart
{
[[NetworkManager sharedInstance] didStartNetworkOperation];
}
- (void)updateStatus:(NSString *)statusString
{
assert(statusString != nil);
}
- (void)receiveDidStopWithStatus:(NSString *)statusString
{
if (statusString == nil) {
assert(self.filePath != nil);
statusString = [NSString stringWithFormat:@"Receive succeeded "];
}
[[NetworkManager sharedInstance] didStopNetworkOperation];
}
pragma mark * Core transfer code
// This is the code that actually does the networking.
- (BOOL)isStarted
{
return (self.netService != nil);
}
- (BOOL)isReceiving
{
return (self.networkStream != nil);
}
- (NSString *) getStorageFolder
{//NSPicturesDirectory
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES);
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"QuizServer"];
BOOL isDir = NO;
if (![fileManager fileExistsAtPath: path isDirectory:&isDir]){
if (!isDir){
[fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
}
return path;
}
- (void)startReceive:(int)fd
{
self.filePath = nil;
NSLog(@"start with host :%@",self.host);
CFReadStreamRef readStream;
assert(fd >= 0);
assert(self.networkStream == nil); // can't already be receiving
assert(self.fileStream == nil); // ditto
assert(self.filePath == nil); // ditto
// Open a stream for the file we're going to receive into.
//@"received.zip"
self.filePath = [[self getStorageFolder] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.zip",self.host]];
assert(self.filePath != nil);
self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:NO];
assert(self.fileStream != nil);
[self.fileStream open];
// Open a stream based on the existing socket file descriptor. Then configure
// the stream for async operation.
CFStreamCreatePairWithSocket(NULL, fd, &readStream, NULL);
assert(readStream != NULL);
self.networkStream = (__bridge NSInputStream *) readStream;
CFRelease(readStream);
[self.networkStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
// Tell the UI we're receiving.
[self receiveDidStart];
}
- (void)stopReceiveWithStatus:(NSString *)statusString
{
if (self.networkStream != nil) {
self.networkStream.delegate = nil;
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self receiveDidStopWithStatus:statusString];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
assert(aStream == self.networkStream);
pragma unused(aStream)
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:@"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
[self updateStatus:@"Receiving"];
// Pull some data off the network.
bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) {
[self stopReceiveWithStatus:@"Network read error"];
} else if (bytesRead == 0) {
[self stopReceiveWithStatus:nil];
} else {
NSInteger bytesWritten;
NSInteger bytesWrittenSoFar;
// Write to the file.
bytesWrittenSoFar = 0;
do {
bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopReceiveWithStatus:@"File write error"];
break;
} else {
bytesWrittenSoFar += bytesWritten;
}
} while (bytesWrittenSoFar != bytesRead);
}
} break;
case NSStreamEventHasSpaceAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventErrorOccurred: {
[self stopReceiveWithStatus:@"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
- (void)acceptConnection:(int)fd
{
int junk;
// If we already have a connection, reject this new one. This is one of the
// big simplifying assumptions in this code. A real server should handle
// multiple simultaneous connections.
if ( self.isReceiving ) {
junk = close(fd);
assert(junk == 0);
} else {
[self startReceive:fd];
}
}
static void AcceptCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
// Called by CFSocket when someone connects to our listening socket.
// This implementation just bounces the request up to Objective-C.
{
SocketListen * obj;
pragma unused(type)
assert(type == kCFSocketAcceptCallBack);
pragma unused(address)
// assert(address == NULL);
assert(data != NULL);
obj = (__bridge SocketListen *) info;
assert(obj != nil);
assert(s == obj->_listeningSocket);
pragma unused(s)
[obj acceptConnection:*(int *)data];
}
- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
// A NSNetService delegate callback that's called if our Bonjour registration
// fails. We respond by shutting down the server.
//
// This is another of the big simplifying assumptions in this sample.
// A real server would use the real name of the device for registrations,
// and handle automatically renaming the service on conflicts. A real
// client would allow the user to browse for services. To simplify things
// we just hard-wire the service name in the client and, in the server, fail
// if there's a service name conflict.
{
pragma unused(sender)
assert(sender == self.netService);
pragma unused(errorDict)
[self stopServer:@"Registration failed"];
}
- (void)startServer
{
BOOL success;
int err;
int fd;
int junk;
struct sockaddr_in addr;
NSUInteger port;
// Create a listening socket and use CFSocket to integrate it into our
// runloop. We bind to port 0, which causes the kernel to give us
// any free port, then use getsockname to find out what port number we
// actually got.
port = 0;
fd = socket(AF_INET, SOCK_STREAM, 0);
success = (fd != -1);
if (success) {
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = 0;
addr.sin_addr.s_addr = INADDR_ANY;
err = bind(fd, (const struct sockaddr *) &addr, sizeof(addr));
success = (err == 0);
}
if (success) {
err = listen(fd, 5);
success = (err == 0);
}
if (success) {
socklen_t addrLen;
addrLen = sizeof(addr);
err = getsockname(fd, (struct sockaddr *) &addr, &addrLen);
success = (err == 0);
if (success) {
assert(addrLen == sizeof(addr));
port = ntohs(addr.sin_port);
}
}
if (success) {
CFSocketContext context = { 0, (__bridge void *) self, NULL, NULL, NULL };
// assert(self->_listeningSocket == NULL);
self->_listeningSocket = CFSocketCreateWithNative(
NULL,
fd,
kCFSocketAcceptCallBack,
AcceptCallback,
&context
);
success = (self->_listeningSocket != NULL);
if (success) {
CFRunLoopSourceRef rls;
fd = -1; // listeningSocket is now responsible for closing fd
rls = CFSocketCreateRunLoopSource(NULL, self.listeningSocket, 0);
assert(rls != NULL);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
}
}
// Now register our service with Bonjour. See the comments in -netService:didNotPublish:
// for more info about this simplifying assumption.
if (success) {
self.netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:self.host port:port];
success = (self.netService != nil);
}
if (success) {
self.netService.delegate = self;
[self.netService publishWithOptions:NSNetServiceNoAutoRename];
// continues in -netServiceDidPublish: or -netService:didNotPublish: ...
}
// Clean up after failure.
if ( success ) {
assert(port != 0);
[self serverDidStartOnPort:port];
} else {
[self stopServer:@"Start failed"];
if (fd != -1) {
junk = close(fd);
assert(junk == 0);
}
}
}
- (void)stopServer:(NSString *)reason
{
if (self.isReceiving) {
[self stopReceiveWithStatus:@"Cancelled"];
}
if (self.netService != nil) {
[self.netService stop];
self.netService = nil;
}
if (self.listeningSocket != NULL) {
CFSocketInvalidate(self.listeningSocket);
CFRelease(self->_listeningSocket);
self->_listeningSocket = NULL;
}
[self serverDidStopWithReason:reason];
}
pragma mark * Actions
- (IBAction)startOrStopAction:(id)sender
{
pragma unused(sender)
if (self.isStarted) {
[self stopServer:nil];
} else {
[self startServer];
}
}
pragma mark * View controller boilerplate
- (void)dealloc
{
[self stopServer:nil];
}
@end
Start Server
SocketListen *socketListen = [[SocketListen alloc] initWithHost:self.address];
[socketListen startServer];
2. Client
SocketClient.h
//
// SocketClient.h
// SocketClient
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 DHT. All rights reserved.
//
import <Foundation/Foundation.h>
import <UIKit/UIKit.h>
@interface SocketClient : NSObject
- (void) sendData:(NSData *)data;
- (BOOL)isSending;
- (void)stopSendWithStatus:(NSString *)statusString;
- (void)startSend:(NSString *)filePath;
- (void) prepare;
@end
SocketClient.m
//
// SocketClient.m
// SocketClient
//
// Created by nguyenvandung on 11/29/15.
// Copyright © 2015 DHT. All rights reserved.
//
import "SocketClient.h"
import <ifaddrs.h>
import <arpa/inet.h>
import "NetworkManager.h"
@interface NSNetService (QNetworkAdditions)
- (BOOL)qNetworkAdditions_getInputStream:(out NSInputStream **)inputStreamPtr
outputStream:(out NSOutputStream **)outputStreamPtr;
@end
@implementation NSNetService (QNetworkAdditions)
- (BOOL)qNetworkAdditions_getInputStream:(out NSInputStream **)inputStreamPtr
outputStream:(out NSOutputStream **)outputStreamPtr {
BOOL result;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
result = NO;
readStream = NULL;
writeStream = NULL;
if ( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) ) {
CFNetServiceRef netService;
netService = CFNetServiceCreate(
NULL,
(__bridge CFStringRef) [self domain],
(__bridge CFStringRef) [self type],
(__bridge CFStringRef) [self name],
);
if (netService != NULL) {
CFStreamCreatePairWithSocketToNetService(
NULL,
netService,
((inputStreamPtr != nil) ? &readStream : NULL),
((outputStreamPtr != nil) ? &writeStream : NULL)
);
CFRelease(netService);
}
result = ! ((( inputStreamPtr != NULL) && ( readStream == NULL)) ||
((outputStreamPtr != NULL) && (writeStream == NULL)));
}
if (inputStreamPtr != NULL) {
*inputStreamPtr = CFBridgingRelease(readStream);
}
if (outputStreamPtr != NULL) {
*outputStreamPtr = CFBridgingRelease(writeStream);
}
return result;
}
@end
enum {
kSendBufferSize = 32768
};
@interface SocketClient()<NSStreamDelegate>
{
uint8_t _buffer[kSendBufferSize];
}
@property (nonatomic, strong, readwrite) IBOutlet UIActivityIndicatorView * activityIndicator;
- (IBAction)sendAction:(UIView *)sender;
- (IBAction)cancelAction:(id)sender;
// private properties
@property (nonatomic, assign, readonly ) BOOL isSending;
@property (nonatomic, strong, readwrite) NSOutputStream * networkStream;
@property (nonatomic, strong, readwrite) NSInputStream * fileStream;
@property (nonatomic, assign, readonly ) uint8_t * buffer;
@property (nonatomic, assign, readwrite) size_t bufferOffset;
@property (nonatomic, assign, readwrite) size_t bufferLimit;
@end
@implementation SocketClient
- (void)sendDidStart {
[self.activityIndicator startAnimating];
[[NetworkManager sharedInstance] didStartNetworkOperation];
}
- (void)updateStatus:(NSString *)statusString {
assert(statusString != nil);
}
- (void) cancelAction:(id)sender {
}
- (void) sendAction:(UIView *)sender {
}
- (void)sendDidStopWithStatus:(NSString *)statusString {
[self.activityIndicator stopAnimating];
[[NetworkManager sharedInstance] didStopNetworkOperation];
}
pragma mark * Core transfer code
- (uint8_t *)buffer {
return self->_buffer;
}
- (BOOL)isSending {
return (self.networkStream != nil);
}
- (NSString *)getIPAddress {
NSString *address = @"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
- (void) prepare{
[self.networkStream setDelegate:nil];
self.networkStream = nil;
[self.fileStream setDelegate:nil];
self.fileStream = nil;
}
- (void) sendData:(NSData *)data {
if (self.networkStream != nil){
return;
}
NSOutputStream * output;
BOOL success;
NSNetService * netService;
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// Open a stream for the file we're going to send.
self.fileStream = [NSInputStream inputStreamWithData:data];
assert(self.fileStream != nil);
[self.fileStream open];
NSString *ip = [self getIPAddress];
netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:ip];
assert(netService != nil);
success = [netService qNetworkAdditions_getInputStream:NULL outputStream:&output];
assert(success);
self.networkStream = output;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
// Tell the UI we're sending.
[self sendDidStart];
}
- (void)startSend:(NSString *)filePath {
if (self.networkStream != nil){
return;
}
NSOutputStream * output;
BOOL success;
NSNetService * netService;
assert(filePath != nil);
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// Open a stream for the file we're going to send.
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
assert(self.fileStream != nil);
[self.fileStream open];
NSString *ip = [self getIPAddress];
netService = [[NSNetService alloc] initWithDomain:@"local." type:@"_x-SNSUpload._tcp." name:ip];
assert(netService != nil);
success = [netService qNetworkAdditions_getInputStream:NULL outputStream:&output];
assert(success);
self.networkStream = output;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
// Tell the UI we're sending.
[self sendDidStart];
}
- (void)stopSendWithStatus:(NSString *)statusString {
if (self.networkStream != nil) {
self.networkStream.delegate = nil;
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
self.bufferOffset = 0;
self.bufferLimit = 0;
[self sendDidStopWithStatus:statusString];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
assert(aStream == self.networkStream);
pragma unused(aStream)
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:@"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:@"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:@"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:@"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:@"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
pragma mark * Actions
pragma mark * View controller boilerplate
- (void)dealloc {
[self stopSendWithStatus:@"Stopped"];
}
@end
Client send data
SocketClient *client = [[SocketClient alloc] init];
[client prepare];
UIImage *img = [UIImage imageNamed:@"Kara_iOS.jpg"];
[client sendData:UIImageJPEGRepresentation(img, 1)];
All Rights Reserved