jshort
jshort

Reputation: 1

Can't read response from Java server in iOS client using NSInputStream

All,

This is my first time posting here -- I've searched for several hours over the past few days. This isn't the first client/server application I've made, and I'm completely stumped as to what's going wrong.

I've got a Java server (and it's able to correctly read a request from my iOS client -- it even generates a response and appears to send it correctly, though no data is available to read on the iOS client):

public void run() {
    BufferedReader in;

    try {
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        OutputStream out_stream = this.socket.getOutputStream();
        StringBuilder request = new StringBuilder();
        String request_buffer;

        while ((request_buffer = in.readLine()) != null) {
            request.append(request_buffer);
        }
        out_stream.write(processRequest(request.toString()).getBytes());
        out_stream.close();

        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

The supplied Java function is called as the result of spawning an instance of the class it's a member of, and it's initialized with the result of the accept() method of a ServerSocket. Everything seems to work fine here -- the following Python client is able to send a request (and even read a response):

DEFAULT_HOST = ''
DEFAULT_PORT = 2012
RECEIVE_BUFFER_SIZE = 4096

if __name__ == "__main__":
    import sys, socket

    port = DEFAULT_PORT
    host = DEFAULT_HOST
    if len(sys.argv) > 2:
        host = sys.argv[1]
        del sys.argv[1]

    if len(sys.argv) == 2:
            request = sys.argv[1]
            print "Requesting: %s" % request
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((host, port))
            s.send(request)
            s.shutdown(socket.SHUT_WR)

            response = ""
            message = True
            while message:
                message = s.recv(RECEIVE_BUFFER_SIZE)
                response += message

    print "Response: %s" % response

Before posting the iOS client, I've tested it with the following Python server (and the iOS client can read/write as expected.. this also works with the Python test client):

import os, sys

DEFAULT_HOST = ''
DEFAULT_PORT = 4150

# Simple test server
DEFAULT_SIZE = 4096
import socket

class Server:

    def __init__(self, host, port, root, protocol, debug=True):
        self.debug = debug
        self.host = host
        self.port = port
        self.root = root
        self.protocol = protocol

    def __call__(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind((self.host, self.port))
        s.listen(5)

        while True:
            try:
                c = s.accept()
                print "Connection: %s" % str(c)

                request = c[0].recv(DEFAULT_SIZE)
                print "Request: %s" % request

                try:
                    response = "test"
                    if self.debug:
                        print "Response: %s" % response

                except Exception as ex:
                    print "Error generating response: %s" % ex

                if response:
                    c[0].send(response)

                else:
                    c[0].send("3rr0rZ")

                c[0].close()
                print


            except Exception as ex:
                print ex


if __name__ == "__main__":
    host = DEFAULT_HOST
    port = DEFAULT_PORT

    args = sys.argv

    # choose a port
    if len(args) > 1 and args[1] == "-p":
        if len(args) < 3:
            raise Exception("Missing Argument for %s" % "-p")
            port = int(args[2])
            del args[1:3]
        else:
            port = DEFAULT_PORT

    # check if root specified
    if len(args) > 1:
        root = os.path.realpath(args[1])
        del args[1]
    else:
        root = os.getcwd()

    print "Using:"
    print "\tHost: %s" % host
    print "\tPort: %s" % port
    print "\tRoot: %s" % root
    print
    server = Server(host, port, root)
    server()

Obviously this is a simplified server -- the problem isn't in how requests are generated. For a little more background, requests and responses are JSON strings, though that's not entirely relevant. As mentioned before, the Python client is able to successfully request and receive a response from both the Java and Python servers. The iOS client can successfully send requests to both the Python and Java servers, but it's only able to read a response from the Python server. Here's the relevant part of the iOS client:

- (NSData *)sendMessage:(NSData *)request
{
    // Create low-level read/write stream objects
    CFReadStreamRef readStream = nil;
    CFWriteStreamRef writeStream = nil;

    // Create high-level stream objects
    NSInputStream *inputStream = nil;
    NSOutputStream *outputStream = nil;

    // Connect the read/write streams to a socket
    CFStreamCreatePairWithSocketToHost(nil, (__bridge CFStringRef) self.address, self.port, &readStream, &writeStream);

    // Create input/output streams for the raw read/write streams
    if (readStream && writeStream) {
        CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
        CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);

        inputStream = (__bridge_transfer NSInputStream *)readStream;
        [inputStream open];

        outputStream = (__bridge_transfer NSOutputStream *)writeStream;
        [outputStream open];
    }

    NSLog(@"Sending message to server: %@", request);
    [outputStream write:[request bytes] maxLength:[request length]];
    [outputStream close];

    int size;
    int buffer_size = 1024;
    uint8_t buffer[buffer_size];
    NSMutableData *response = [NSMutableData dataWithLength:0];
    while (![inputStream hasBytesAvailable]);

    NSLog(@"About to read");
    while ([inputStream streamStatus] == NSStreamStatusOpen)
    {        
        if ([inputStream hasBytesAvailable] && (size = [inputStream read:buffer maxLength:buffer_size]) > 0)
        {
            NSLog(@"Reading response data");
            [response appendData:[NSData dataWithBytes:buffer length:size]];
        }
    }

    NSLog(@"\tResponse:%@", response);
    return response;
}

When reading from the Java server, the iOS client never gets past the line which reads:

while (![inputStream hasBytesAvailable]);

I've read all the documentation, forum posts, questions, etc. that I could find for a variety of search terms, but nothing has helped; I'm hoping someone here can shed some light on the issue! I've posted a slightly simplified/flattened version of the code I'm using, but, again, this should be sufficient for establishing context.. I'll happily post more code if it's necessary, and I appreciate any help or insight that you can share.

I'm purposefully not using a NSStreamDelegate, and I can't imagine that being an issue. If I were, I'd imagine that the problem would simply transform into the NSStreamEventHasBytesAvailable never happening.

Upvotes: 0

Views: 2011

Answers (2)

Alexiosdev
Alexiosdev

Reputation: 272

Try my code, it works for me.

    NSInputStream *inputStream;
    NSOutputStream *outputStream;
    -(void) init{
        NSURL *website = [NSURL URLWithString:@"http://YOUR HOST"];

        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
        CFStreamCreatePairWithSocketToHost(NULL,CFBridgingRetain([website host]),9876, &readStream, &writeStream);

        inputStream = (__bridge_transfer NSInputStream *)readStream;
        outputStream = (__bridge_transfer NSOutputStream *)writeStream;
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream open];
        [inputStream open];

        CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
        CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
    }

- (void) sendMessage {

    // it is important to add "\n" to the end of the line
    NSString *response  = @"Say hello to Ukraine\n";
    NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
    int sent = [outputStream write:[data bytes] maxLength:[data length]];
    NSLog(@"bytes sent: %d",sent);

    do{
        uint8_t buffer[1024];
        int bytes = [inputStream read:buffer maxLength:sizeof(buffer)];
        NSString *output = [[NSString alloc] initWithBytes:buffer length:bytes encoding:NSUTF8StringEncoding];
        NSLog(@"%@",output);
    } while ([inputStream hasBytesAvailable]);
}

    public class ServerTest {
        public static void main(String[] args) {
            Thread thr = new Thread(new SocketThread());
            thr.start();
        }
    }

    class SocketThread implements Runnable {

        @Override
        public void run() {
            try {
                ServerSocket server = new ServerSocket(9876);
                while (true) {
                    new SocketConnection(server.accept()).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class SocketConnection extends Thread {
        InputStream input;
        PrintWriter output;
        Socket socket;

        public SocketConnection(Socket socket) {
            super("Thread 1");
            this.socket = socket;
            try {
                input = socket.getInputStream();
                output = new PrintWriter(new OutputStreamWriter(
                        socket.getOutputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            try {
                byte array[] = new byte[1024];
                while (true) {
                    do {
                        int readed = input.read(array);
                        System.out.println("readed == " + readed + " "
                                + new String(array).trim());
                        String sendString = new String(
                                "Hello Ukraine!".getBytes(),
                                Charset.forName("UTF-8"));
                        output.write(sendString);
                        output.flush();
                    } while (input.available() != 0);
                }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Upvotes: 2

brettw
brettw

Reputation: 11114

Your python server and Java server are not equivalent. In python you read like this:

request = c[0].recv(DEFAULT_SIZE)

which is reading upto 4096 bytes in a block. Whereas in Java you are using:

while ((request_buffer = in.readLine()) != null)

in.readLine() will block until it gets a end of line, OR until it gets the end of file. Likely this will get stuck until the client shuts down the socket. Are you sure the client is shutting down the output stream correctly? I'm not familiar with Objective C, but closing the output stream may not be the same is shutting down the write-side of the socket.

If you're in charge of both sides of the wire, I would have the client write a length header first (two bytes) followed by exactly that much data. Then the server can read two bytes, compute the length of the remaining data, and read exactly that much.

Length (2-bytes) | Data (length bytes)
----------------------------------------------------------
0x000C           | Hello World!

By always sending length followed by data you can even send multiple messages without shutting down the socket very easily.

Upvotes: 0

Related Questions