I'm trying to add a posix socket server to my iOS app that will allow a TCP connection and writes the buffer to a UILabel object as a test.
I can get it to work...once. Then it finishes and closes the connection. Ok, easy fix, I'll just put it in a loop. Now whenever I put the exact same code in a loop, it won't update the UILabel for some reason. I don't actually need it to be able to update the UILabel, it was just a test to make sure the server was working.......but it's making me nervous. I take it out of the while loop, it works, I put it back in, and everything but the UILabel setText call work.
Also, two other small questions: I'm having trouble figuring out how to exit the loop after a client disconnects, and I'm not sure how to correctly close the ports when I exit, I have to keep changing the port number because it can't bind.
-(void)viewDidLoad
NSThread *listenThread = [[NSThread alloc] initWithTarget:self selector:#selector(createPosixServer) object:nil];
[listenThread start];
-(void)createPosixServer
//declarations
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
NSString *nsbuffer;
//bind and listen on socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
NSLog(#"Error while calling socket()");
}
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 1818;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
NSLog(#"ERROR on binding");
}
listen(sockfd, 5);
NSLog(#"Begin listen loop");
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) {
NSLog(#"ERROR on accept");
}
while(true) {
bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) {
NSLog(#"ERROR reading from socket");
}
if (n > 0) {
nsbuffer = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
[_lblStatus setText:nsbuffer];
NSLog(#"You sent %#", nsbuffer);
}
NSLog(#"Finished listen loop");
sleep(1);
}
close(newsockfd);
close(sockfd);
NSLog(#"Socket closed");
createPosixServer is running on a background thread. It is never safe to update the UI from a background thread. UIKit will sometimes work, sometimes just ignore you. You need to dispatch the call to update the label onto the main thread, something like this:
typeof(self) __weak weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.lblStatus.text = nsbuffer;
});
To answer the side question: when a client disconnects you want to close the socket that you accepted for that connection (your newsockfd) but you don't want to close your listener sockfd until you are tearing down the whole service.
To exit the loop, simply do this:
if (n < 0) {
NSLog(#"ERROR reading from socket");
break;
}
Though you probably want to check errno in that block too because you're probably going to want different behavior depending on the error.
Remove the sleep(1), that is doing nothing good for you. The read call will block, there's no need to sleep.
Related
I am working with a package called DSBridge to connect a mobile application (iOS code in this case) to javascript code containing the main logic of my application. This involves running the JavaScript on an invisible WKWebView.
My JavaScript code needs to call a method in the native iOS application which needs to execute and return a value to the JavaScript.
My Objective C function reads:
- (NSString* )read:(NSDictionary *) args{
NSLog(#"In Read");
while(self->connection != NULL) {
if([self->connection isMessageAvailable]) {
[self->connection messageRetrieved];
NSLog(#"Message Received in ViewController");
return [self->connection getMessage];
}
}
return NULL;
}
Which is called from my JavaScript, I need this to return on the main thread so that the return value is sent into my WebView.
The issue is that my the message from IO I am waiting for will never appear as my NSStream is blocked by the while loop.
My connection methods and NSStream look like this:
-(void)readData:(NSData*)receivedData{
NSData *decoded = [self btXOR:receivedData withMask:0x26];
message = [[NSString alloc] initWithData:decoded encoding:NSUTF8StringEncoding];
NSLog(#"message: %#", message);
messageAvailable = true;
}
- (BOOL) isMessageAvailable {
return messageAvailable;
}
- (void) messageRetrieved {
messageAvailable = false;
}
- (NSString*) getMessage {
NSLog(#"Message: %#", message);
return message;
}
#pragma mark StreamDelegate
-(void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode)
{
//Reading in from host done here
case NSStreamEventHasBytesAvailable:
DDLogInfo(#"NSStreamEventHasBytesAvailable");
DDLogInfo(#"stream is input: %i " , (theStream == inputStream));
NSLog(#"NSStreamEventHasBytesAvailable");
NSLog(#"stream is input: %i " , (theStream == inputStream));
if (theStream == inputStream)
{
uint8_t buffer[1024];
long len;
while ([inputStream hasBytesAvailable])
{
len = [inputStream read:buffer maxLength:sizeof(buffer)];
DDLogDebug(#"InputStream still has bytes");
NSLog(#"InputStream still has bytes");
if (len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buffer length:(int)len];
[self readData:data];
}
}
}
break;
The 'NSStreamEventHasBytesAvailable' case should occur once the message is ready, and should be picked up by my read function so it can be returned to my WebViews JavaScript.
How can I take the while loop off of the main thread and still return once I have the message? Is it possible to take the NSStream off the main runloop in a way that it will not be blocked by my read function?
I tried making read async and using a callback to the JavaScript as seen here, but this makes my JavaScript codebase rather unwieldy with callbacks.
I've some data which is accumulated in a buffer and I need to read the data when buffer is having data. This i need to do with thread synchronisation. I've worked little with GCD, which I'm failing to do. please help how to do a circular buffer with read and write threads in synchronization.
My Code:
- (void)viewDidLoad {
[super viewDidLoad];
readPtr = 0;
writePtr = 0;
currentPtr = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
while(YES){
[self writeToBuffer:buffer[0] withBufferSize:bufferSize];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
while(YES){
float* newBuffer;
if(currentPtr>512){
newBuffer = [self readBuffer];
}else{
continue;
}
[self UseBuffer: newBuffer];
}
});
}
-(void)writeToBuffer:(float*)Values withBufferSize:(int)bSize{
[_lock lock];
for(int i=0;i<bSize;i++){
if(writePtr>1859){
writePtr = 0;
}
globalBuffer[writePtr] = Values[i];
writePtr++;
currentPtr++;
}
NSLog(#"Writing");
[_lock unlock];
}
-(float*)readBuffer{
[_lock lock];
float rBuffer[512];
for(int i=0;i<512;i++){
if(readPtr>1859){
readPtr = 0;
}
rBuffer[i] = globalBuffer[readPtr];
readPtr++;
currentPtr--;
}
NSLog(#"Reading");
[_lock unlock]
return rBuffer;
}
One of the key points of GCD is that it completely replaces the need for locks. So, if you are mixing GCD and mutex locks it is typically a sign that you're doing things wrong or sub-optimally.
A serial queue is, effectively, an exclusive lock on whatever is associated with the serial queue.
There a bunch of problems in your code.
while (YES) {...} is going to spin, burning CPU cycles ad infinitum.
The readBuffer method is returning a pointer to a stack based buffer. That won't work.
It isn't really clear what the goal of the code is, but those are some specific issues.
Psuedocode of my current solution:
if (disconnected):
while (disconnected):
check for connection
if (connected):
fetch results
Is there a more idiomatic way to tell when the device goes from being disconnected to establishing an internet connection?
Take a look at Apple's Reachability sample code
You don't have to investigate detailed implementation of Reachability.m. You can just use it as a library.
Absolutely! This is what SCNetworkReachability is for!
Unfortunately, people tend to use it incorrectly. What you are describing would be a correct use case for it.
Attempt a connection normally.
If it fails with NSURLErrorNotConnectedToInternet:
Use SCNetworkReachability to monitor the device network configuration for changes
When the network configuration moves to a state that indicates packets can again leave the device, your application will be notified and you can make connections again.
What you should NOT do is try to use SCNetworkReachability to see if the device is connected before connecting. This is not recommended for many reasons. The SCNetworkReachability API can tell you when it is, or is not, possible for packets to leave the device. It can't tell you if the thing you're connecting to is down, DNS isn't working, etc.
The Apple sample projects MVCNetworking and Reachability demonstrate use of the SCNetworkReachability API.
In practice, this looks like....
In your connection error handling:
if ([[error domain] isEqualToString:NSURLErrorDomain]){
NSURL *failingURL = [[error userInfo] valueForKey:NSURLErrorFailingURLErrorKey];
switch ([error code]){
case NSURLErrorNotConnectedToInternet:
[self beginObservingReachabilityStatusForHost:[failingURL host]];
break;
default:
break;
}
}
The beginObservingReachabilityStatusForHost: and endObsvervingReachabilityStatusForHost: methods:
- (void) beginObservingReachabilityStatusForHost:(NSString *)host {
SCNetworkReachabilityRef reachabilityRef = NULL;
void (^callbackBlock)(SCNetworkReachabilityFlags) = ^(SCNetworkReachabilityFlags flags) {
BOOL reachable = (flags & kSCNetworkReachabilityFlagsReachable) != 0;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self host:host didBecomeReachable:reachable];
}];
};
SCNetworkReachabilityContext context = {
.version = 0,
.info = (void *)CFBridgingRetain(callbackBlock),
.release = CFRelease
};
if ([host length] > 0){
reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [host UTF8String]);
if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)){
if (!SCNetworkReachabilitySetDispatchQueue(reachabilityRef, [self scNetworkQueue]) ){
// Remove our callback if we can't use the queue
SCNetworkReachabilitySetCallback(reachabilityRef, NULL, NULL);
}
[self setCurrentReachability:reachabilityRef];
}
}
}
- (void) endObsvervingReachabilityStatusForHost:(NSString *)host {
// Un-set the dispatch queue
if (!SCNetworkReachabilitySetDispatchQueue([self currentReachability], NULL) ){
}
SCNetworkReachabilitySetCallback([self currentReachability], NULL, NULL);
}
The C callback that wraps our block:
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void* info) {
void (^callbackBlock)(SCNetworkReachabilityFlags) = (__bridge id)info;
callbackBlock(flags);
}
And the method that the block calls to do something when reachability changes:
- (void) host:(NSString *)host didBecomeReachable:(BOOL)reachable {
// Update your UI to indicate reachability status here
if (reachable){
[self endObsvervingReachabilityStatusForHost:nil];
}
}
Obviously to use the above you need a serial dispatch queue and a way to hold on to the SCNetworkReachabilityRef for later cancellation when you're done.
I have a lot of switches in my network which I try to check if they are online. I do this with a small UDP packet to which they respond with an UDP packet of their own to tell me that they are there. In fact it is only one switch simulating 200 hundred for testing but this is not important.
Since I do not like to work low level if I don't have to I use https://github.com/robbiehanson/CocoaAsyncSocket for the UDP thing.
It works... almost. When I ping for example 10 switches repeatedly (I ping them at the same time and then wait for all the responses) it seems to work for the first cycles but then after a few it gets out of hand and almost all seem to not be responding. It is completely random which are and which not. It gets worse when I add more switches (meaning it does not work from the first cycle). With cycles I man: I send pings for all switches an then wait for all the answers (or timeouts) and then send the pings again.
When I check my network traffic with a packet sniffer I see that for the "wrong" switches (meaning they are shown as offline == timeout, but are actually online) there are three possible cases for each switch:
The packet from the iPhone is never send so no answer is received.
The iPhone send the packet but the switch did not respond.
The answer packet is received but the iPhone didn't read it.
It's completely random and they are mixed in one cycle.
When all the switches are actually online the problem is minimized (takes more cycles to appear) but still there .
Since the code is asynchronous there is a high potential for bugs and I think there must be some kind of race condition or something.
I will post my ping class here. It is not very complicated. I stared at this for hours but could not find a bug.
The usage is the following:
You create an instance with:
- (id)initWithTimeout:(NSTimeInterval)timeout delegate:(id<NMASwitchPingerDelegate>)delegate;
Than you just call the following method for every ipAddress you want to ping. The method returns immediately. This means the pings are processed concurrently:
- (void)sendPingToAddress:(NSString*)address;
I added it to a paste bin for your convenience: http://pastebin.com/0LuiXsXY
#import "NMASwitchPinger.h"
/**
* Private interface.
*/
#interface NMASwitchPinger () {
/**
* The delegate of this class.
*/
__weak id<NMASwitchPingerDelegate> delegate;
/**
* The timeout after which the pinger stops waiting for a response.
*/
NSTimeInterval timeout;
/**
* The socket which is used to send the ping.
*/
GCDAsyncUdpSocket *socket;
/**
* List of pings which are awaiting a response.
*/
NSMutableDictionary *pendingPings;
/**
* Dispatch queue which serializes access to the pendingPings dictionary.
*/
dispatch_queue_t pendingPingsAccessQueue;
/**
* The queue on which the delegate methods of the socket are executed.
*/
dispatch_queue_t socketDelegateQueue;
/**
* Is set to true when the SwitchPinger started receiving responses (after first send)
*/
bool receiving;
}
#end
#implementation NMASwitchPinger
#pragma mark - Initialization
- (id)initWithTimeout:(NSTimeInterval)newTimeout delegate:(id<NMASwitchPingerDelegate>)newDelegate {
self = [super init];
if (self) {
// setting passed values
timeout = newTimeout;
delegate = newDelegate;
// init data structures
pendingPings = [[NSMutableDictionary alloc] init];
pendingPingsAccessQueue = dispatch_queue_create("de.nexans-ans.pingerPendingAccess", DISPATCH_QUEUE_SERIAL);
// create the socket for udp sending
socketDelegateQueue = dispatch_queue_create("de.nexans-ans.pingerDelegate", DISPATCH_QUEUE_CONCURRENT);
socket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:socketDelegateQueue];
}
return self;
}
- (id)init {
NSAssert(NO, #"Use the designated initializer");
return nil;
}
#pragma mark - Sending a ping
- (void)sendPingToAddress:(NSString *)address {
// we allow only one ping at a time to the same ip
__block BOOL alreadyInList = NO;
dispatch_sync(pendingPingsAccessQueue, ^{
if (pendingPings[address]) {
alreadyInList = YES;
} else {
pendingPings[address] = [[NSDate alloc] init];
}
});
// don't send a second ping to the same address
if (alreadyInList) {
NSLog(#"SimplePinger: did not send ping because already a ping pending to this addres: %#", address);
return;
}
// create a minimal packet (3 bytes)
NSMutableData *packet = [[NSMutableData alloc] initWithCapacity:3];
uint16_t vendor_value = CFSwapInt16HostToBig(266);
uint8_t request_type = 1;
[packet appendBytes:&vendor_value length:sizeof(vendor_value)];
[packet appendBytes:&request_type length:sizeof(request_type)];
// send over the wire
[socket sendData:packet toHost:address port:50266 withTimeout:timeout tag:0];
// schedule timeout handler
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_after(popTime, pendingPingsAccessQueue, ^(void){
[self removeTimedOutPingWithAddress:address];
});
// start receiving when not already receiving
if (!receiving) {
bool recvGood = [socket beginReceiving:nil];
NSAssert(recvGood, #"SimplePinger: could not start receiving");
receiving = YES;
}
}
#pragma mark - GCDAsyncSocket delegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
NSString *ipAddress = [GCDAsyncUdpSocket hostFromAddress:address];
__block BOOL pingStillPending = NO;
dispatch_sync(pendingPingsAccessQueue, ^{
NSDate *sendDate = pendingPings[ipAddress];
if (sendDate) {
[pendingPings removeObjectForKey:ipAddress];
pingStillPending = YES;
}
});
if (pingStillPending) {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate switchPinger:self didReceiveResponse:data fromAddress:ipAddress];
});
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
NSLog(#"didnt send data");
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
NSLog(#"did send");
}
#pragma mark - Private methods
/**
* Removes a timed out ping. A call of this function gets scheduled when a ping is send.
*
* #param address The address of the ping which should be removed.
*/
- (void)removeTimedOutPingWithAddress:(NSString*)address {
NSDate *sendDate = pendingPings[address];
if (sendDate) {
NSLog(#"timeout: %#", address);
NSAssert(fabs([sendDate timeIntervalSinceNow]) >= timeout, #"SimplePing: removed ping before timout");
[pendingPings removeObjectForKey:address];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate switchPinger:self didReceiveTimeoutFromAddress:address];
});
}
}
#end
ViewController.h
#interface ViewController : UIViewController{
CFSocketRef s;
int connectFlag;
}
ViewController.m
void receiveDataCilent(CFSocketRef cs,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void *info)
{
CFDataRef df = (CFDataRef) data;
int len = CFDataGetLength(df);
if(len <= 0) return;
CFRange range = CFRangeMake(0,len);
UInt8 buffer[len];
NSLog(#"Received %d bytes from socket %d\n",
len, CFSocketGetNative(cs));
CFDataGetBytes(df, range, buffer);
NSLog(#"Client received: %s\n", buffer);
NSLog(#"As UInt8 coding: %#", df);
NSLog(#"len value: %d", len);
}
-(void) clientConnect:(int)sender;{
s = CFSocketCreate(NULL, PF_INET,
SOCK_STREAM, IPPROTO_TCP,
kCFSocketDataCallBack,
receiveDataCilent,
NULL);
struct sockaddr_in sin;
struct hostent *host;
memset(&sin, 0, sizeof(sin));
host = gethostbyname("localhost");
memcpy(&(sin.sin_addr), host->h_addr,host->h_length);
sin.sin_family = AF_INET;
sin.sin_port = htons(6666);
CFDataRef address;
CFRunLoopSourceRef source;
address = CFDataCreate(NULL, (UInt8 *)&sin, sizeof(sin));
CFSocketConnectToAddress(s, address, 0);
CFRelease(address);
source = CFSocketCreateRunLoopSource(NULL, s, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
source,
kCFRunLoopDefaultMode);
CFRelease(source);
CFRunLoopRun();
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(connectFlag == 0 ){
[self clientConnect:1];
}
}
First I'm sorry for messing the concept of xcode programming and threading programming I'm very new to xcode and I need to finish my game project soon
This code is suppose to connect to server and waiting for message from server. I try and test it and it work fine. In view I have IBAction that can press and change view.
But after I implement it to my main project. It can run but I cannot press any button or do anything. After log it out. It seem it stuck around CFRunLoopRun(); I think it stuck in loop but why when I test it. it not stuck ?
In my project this code is run from another view not from ViewController like I just test.
I don't know why it stuck in my project.
Any help would be appreciated.
Don't run the run loop yourself on the main thread. The main event loop will run it.
Edit: I should say, "within the context of a GUI app". In a command-line tool, you do need to run the run loop yourself.