I have been trying to get a basic TCP client up and running for my iOS application but have run into a block which i cannot seem to work my head around.
So far i can connect, send a message which is received on the server side but then my app crashes.
Client.h
#import <Foundation/Foundation.h>
#interface Client : NSObject <NSStreamDelegate>
{
NSInputStream *inputStream;
NSOutputStream *outputStream;
}
-(void)initNetworkCommunication;
-(void)send:(NSString*)message;
#end
Client.m
#import "Client.h"
#implementation Client
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"10.0.1.51", 7769, &readStream, &writeStream);
inputStream = ( NSInputStream *)CFBridgingRelease(readStream);
outputStream = ( NSOutputStream *)CFBridgingRelease(writeStream);
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)send:(NSString*)message
{
NSData *data = [[NSData alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(#"stream event %i", streamEvent);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
}
}
}
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"End Encountered!");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
theStream = nil;
break;
default:
NSLog(#"Unknown event");
}
}
#end
The output in the console is
2013-10-03 17:01:38.542 MMORPG[6076:70b] stream event 1
2013-10-03 17:01:38.543 MMORPG[6076:70b] Stream opened
2013-10-03 17:01:43.495 MMORPG[6076:70b] stream event 4
2013-10-03 17:01:43.495 MMORPG[6076:70b] Unknown event
It seems like, my message is sent, i receive stream event #4 and then i get a bad access crash. The problem is i have no idea what its having trouble accessing?
Any help would be greatly appreciated!
The problem is that NSStream keeps an assign/unsafe_unretained reference to its delegate. If the delegate is released before the stream is closed and released, the stream will try to call methods on its now deallocated delegate, causing a crash. The solution is to either make sure some other object has a strong reference to the client, preventing its early deallocation, or else make sure you close and release the stream before its delegate is deallocated.
Related
Though AFMultipartBodyStream of AFNetworking lib is a subclass of NSStream conforming to NSStreamDelegate protocol, it could not be processed as with standard way of a regular NSStream. Namely, AFMultipartBodyStream could not be handled with stream event. I looked into the code of AFMultipartBodyStream, and found that it intentionally disabled the scheduleInRunLoop method of NSInputStream abstract class:
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
Any specific reason? Is it a way to make it support the standard stream event mechanism so that the stream data handling can be done asynchronously with stream:handleEvent: event handler?
After studying the implementation of AFMultipartBodyStream, I noticed that the way of current implementation could not support the asynchronous way of regular stream IO handling. I then enhanced AFMultipartBodyStream to provide a stream which was connected with the internal multipart data structure, and thus the holder of this AFMultipartBodyStream can handle the multipartbody data as the regular stream which can be scheduled in the runloop. below code snippet shows the main idea:
-(NSInputStream *)inputStream {
// If _inputStream has not been connected with HTTPBodyParts data, establish the connection
if (!_inputStream) {
NSParameterAssert([self.HTTPBodyParts count] != 0);
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFIndex bufferSize = self.contentLength;
CFStreamCreateBoundPair(NULL, &readStream, &writeStream, bufferSize);
_inputStream = (__bridge_transfer NSInputStream *)readStream;
_outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[_outputStream setDelegate:self];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"\n====in async block of inputStream....====\n");
[self->_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self open];
[self->_outputStream open];
NSInteger totalBytesSent = self.contentLength;
while (totalBytesSent > 0 && [self->_outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
NSInteger bytesRead = [self read:buffer maxLength:1024];
totalBytesSent -= bytesRead;
NSLog(#"\n====buffer read (%ld): [%s]====\n", (long)bytesRead, buffer);
if (self.streamError || bytesRead < 0) {
break;
}
NSInteger bytesWritten = [self->_outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (self->_outputStream.streamError || bytesWritten < 0) {
NSLog(#"\n====Socket write failed[%#]====\n", self->_outputStream.streamError);
break;
}
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
[self->_outputStream close];
[self->_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
});
}
return _inputStream;
}
// Added
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{
// Setup the input stream for body stream data consuming
NSInputStream *inputStream = [self inputStream];
NSParameterAssert(inputStream == self.inputStream);
[inputStream setDelegate:self.delegate_];
[inputStream scheduleInRunLoop:aRunLoop forMode:mode];
[inputStream open];
}
// Added
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{
if (_inputStream) {
[_inputStream setDelegate:[self delegate]];
[_inputStream removeFromRunLoop:aRunLoop forMode:mode];
[_inputStream close];
}
}
I'd like to create global variable that will contain answers from socket connection.
This is my code:
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"192.168.0.10", 35000, &readStream, &writeStream);
self.inputStream = objc_unretainedObject(readStream);
self.outputStream = objc_unretainedObject(writeStream);
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
}
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(#"Połączono\n");
break;
case NSStreamEventHasBytesAvailable:
if (aStream == self.inputStream) {
uint8_t buffer[1024];
long len;
while ([self.inputStream hasBytesAvailable]) {
len = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"%#",output);
}
}
}
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Nie można połączyć\n");
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}
I want to make output variable as global so I can use it in other functions. I want to do this because I'd like to create conditions - everytime when I send request, controller answers me and sends ">" symbol so that means it is ready to take another request. I want to create condition that will send only when ">" appears. I have already done RegExp for this but now I have problem with access to output variable outside stream function.
When you declaring something as extern you are telling the compiler the type AND that you will define it somewhere else. In your case you never define your variable.
First, ensure it is defined:
// in .h you would have:
extern NSString* const output; // << declaration
// in .m you would have:
NSString * const output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding]; // << definition
currently I am wanting to program a chat server. I am having trouble really understanding the documentations though.
Currently, this is my code, basically extracted from the developer library:
#import "ServerSide.h"
#implementation ServerSide
#synthesize socket;
#synthesize socketAddress;
#synthesize handleConnect;
#synthesize portNumber;
- (id)init{
self = [super init];
if (self) {
self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL);
}
return self;
}
- (void) bind {
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin_len = sizeof(socketAddress);
socketAddress.sin_family = AF_INET; /* Address family */
socketAddress.sin_port = htons(self.portNumber); /* Or a specific port */
socketAddress.sin_addr.s_addr= INADDR_ANY;
CFDataRef sincfd = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&socketAddress, sizeof(socketAddress));
CFSocketSetAddress(socket, sincfd);
CFRelease(sincfd);
}
- (void) listen {
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), socketsource, kCFRunLoopDefaultMode);
CFSocketGetNative(socket);
}
Now, 'handleconnect' is a CFSocketCallBack ivar with no initialization.
Now I have seen others use different implementations to creating a socket server but this was the one from the docs and I would like to build on top of that.
I am currently attempting to connect to the server but it looks like this doesn't even open a socket. I can't seem to connect to it through terminal using telnet localhost 'port#' either.
Heres the client implementation:
#import "Client.h"
#implementation Client
#synthesize host;
#synthesize port;
#synthesize readStream;
#synthesize writeStream;
#synthesize inputStream;
#synthesize outputStream;
- (void)setup {
NSLog(#"Setting up connection to %# : %i", [[NSURL URLWithString:host] absoluteString], port);
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[[NSURL URLWithString:host] host], port, &readStream, &writeStream);
if(!CFWriteStreamOpen(writeStream)) {
NSLog(#"Error, writeStream not open");
return;
}
}
- (void)open {
NSLog(#"Opening streams.");
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)close {
NSLog(#"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
NSLog(#"Stream triggered.");
switch(event) {
case NSStreamEventHasSpaceAvailable: {
if(stream == outputStream) {
NSLog(#"outputStream is ready.");
}
break;
}
case NSStreamEventHasBytesAvailable: {
if(stream == inputStream) {
NSLog(#"inputStream is ready.");
uint8_t buf[1024];
unsigned int len = 0;
len = (int)[inputStream read:buf maxLength:1024];
if(len > 0) {
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes:(const void *)buf length:len];
[self readIn:[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]];
data = nil;
}
}
break;
}
default: {
NSLog(#"Stream is sending an Event: %lu", event);
break;
}
}
}
- (void)readIn:(NSString *)s {
NSLog(#"Reading in the following:");
NSLog(#"%#", s);
}
- (void)writeOut:(NSString *)s {
uint8_t *buf = (uint8_t *)[s UTF8String];
[outputStream write:buf maxLength:strlen((char *)buf)];
NSLog(#"Writing out the following:");
NSLog(#"%#", s);
}
#end
I run the server on a specified port, then tell the client to connect to the specified host and port number.
But now how do I pass data in the socket I have opened...
I don't expect some large explanation, but if someone could give me a better understanding of what needs to be done to further this implementation, I'd greatly appreciate it.
I'm trying to send json object to server. But I received an error and I can't fix it.
-(void) connectToHost{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 9123, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open]; }
This is my connection to host.
NSDictionary *setUser = [NSDictionary
dictionaryWithObjectsAndKeys:[#"u" stringByAppendingString:my.id],#"id",
#"GET_USER_INFO",#"command",
#"",#"value",
nil];
NSArray *array = [NSArray arrayWithObject:setUser];
jsonDataToSendTheServer = [array JSONRepresentation];
NSLog(#" %# ", jsonDataToSendTheServer);
// array = [NSArray arrayWithObject:jsonDataToSendTheServer];
NSLog(#" %# ", array);
NSLog(#"true or false %c",[NSJSONSerialization
isValidJSONObject: array]);
bytesWritten = [NSJSONSerialization writeJSONObject:array toStream:outputStream options:NSJSONWritingPrettyPrinted error:nil];
and this part is NSJSONSerialization part.
However I got an error.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '***+[NSJSONSerialization writeJSONObject:toStream:options:error:]: stream is not open for writing'
I'm new at objective-c. I can't fix the problem for 3 hours.
=======================================
edit:
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(#"stream event %i", streamEvent); //this doesn't post in the log when stream opened...
NSLog(#"bytes %i",bytesWritten);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
//[self messageReceived:output];
}
}
}
}
break;
case NSStreamEventEndEncountered:
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//[theStream release];
theStream = nil;
break;
case NSStreamEventHasSpaceAvailable:
{
uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
readBytes += bytesWritten; // instance variable to move pointer
int data_len = [_data length];
unsigned int len = ((data_len - bytesWritten >= 1024) ?
1024 : (data_len-bytesWritten));
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
//len = [theStream write:(const uint8_t *)buf maxLength:len];
NSLog(#"written %s", buf );
bytesWritten += len;
}
break;
case NSStreamEventErrorOccurred:
{
NSLog(#"no connection");
}
case NSStreamEventNone:
{
//printf("EVENT: None.\n");
break;
}
default:
NSLog(#"Unknown event");
}}
this is my stream: handleEvent: function.
Now after I did add the connectionToHost to delegate I get this kind of error.
012-08-10 16:14:27.302 TaraftarlikOyunu[2274:c07] written P∏◊P‡ˇø
2012-08-10 16:14:28.399 TaraftarlikOyunu[2274:c07] benim bu id 587127341
2012-08-10 16:14:28.399 TaraftarlikOyunu[2274:c07] benim ad Ahmet
2012-08-10 16:14:28.400 TaraftarlikOyunu[2274:c07]
[{"id":"u581277341","command":"GET_USER_INFO","value":""}]
2012-08-10 16:14:28.404 TaraftarlikOyunu[2274:c07] stream event 4
2012-08-10 16:14:28.404 TaraftarlikOyunu[2274:c07] bytes 86
(lldb)
now I don't have an idea that if it buffered or not
=======================EDIT2===============
sorry about this.
the problem probably appears because of this
(void)memcpy(buf, readBytes, len);
I just copy and paste this part of code.
what may be the problem!
The connection method you are using is a little much.
You need to make sure the connection has happened in the delegate
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventHasSpaceAvailable:
NSLog(#\"None!\");
break;
case NSStreamEventOpenCompleted:
NSLog(#\"Stream opened\");
//NOW you can write to the stream
I was trying on a TCP connection app, and I am getting a NSStreamEvent "4" on handleEvent. What am I doing wrong?
My code is like,
-(void) initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 80, &readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (IBAction)didTapButton:(id)sender {
NSString *response = inputTextField.text;
NSLog(#"%#", response);
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
NSLog(#"Stream has bytes available");
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"Stream closed");
break;
default:
NSLog(#"Unknown event: %# : %d", theStream, streamEvent);
}
}
The console gives,
2012-05-29 13:37:07.132 GestureTrial[24289:f803] Stream opened
2012-05-29 13:37:07.133 GestureTrial[24289:f803] Stream opened
2012-05-29 13:37:07.133 GestureTrial[24289:f803] Unknown event: <__NSCFOutputStream: 0x6b85c70> : 4
when tried to send a message to server. I tried it with a tcp tester app for Mac, and it's working fine, so might not be a firewall issue. The output is same for device as well as simulator. Any help would be much appreciated.
Actually you're not doing anything wrong.
This event (it is NSStreamEventHasSpaceAvailable) usually occours after writing to the stream telling you that stream is ready for writing again and after opening a writable stream. Please refer to NSStream Class Reference or, to be exact: Stream Event Constants.
If you're not familliar to << operator, it means shift bits to left for n places (each shift equals to multiplying by 2). Translation would be:
typedef enum {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1,
NSStreamEventHasBytesAvailable = 2,
NSStreamEventHasSpaceAvailable = 4,
NSStreamEventErrorOccurred = 8,
NSStreamEventEndEncountered = 16
};
In many applications you will se this event simply ignored (not handled) because it usually occours very soon after writing to the stream. If something goes wrong you get NSStreamEventErrorOccurred or NSStreamEventEndEncountered and these are the ones you need to handle. You could use NSStreamEventHasSpaceAvailable as a flag that it is o.k. to send some more data.
You should also know that both streams (inputStream and outputStream) are calling the same delegate method. That's why you get two NSStreamEventOpenCompleted events to begin with. But again in many cases this shouldnt be a problem. You can always check which stream is the originator of the event if needed.