Дескриптор файла не работает с dispatch_read () и dispatch_write () в Objective-C (создание почтового клиента)

Я хочу создать почтовый клиент, работающий по протоколу IMAP. Я использую сетевую инфраструктуру Apple для создания TCP-соединения с imap.gmail.com.

Спойлер! Я использовал официальное руководство(Реализация netcat с помощью Network Framework).

В моем приложении очень простой интерфейс:

  • Заполнитель электронной почты
  • Заполнитель пароля
  • Кнопка входа

Все методы написаны в ViewController.m

Когда я нажимаю кнопку входа в систему:

- (IBAction)loginButtonPressed:(id)sender { 
    const char *hostname = "imap.gmail.com";
    const char *port = "imaps";
    NSString *tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"myapptempfile.XXXXXX"];
    const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
    char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
    strcpy(tempFileNameCString, tempFileTemplateCString);
    dispatch_fd_t fd = mkstemp(tempFileNameCString); // Creating temp file descriptor
    free(tempFileNameCString);
    NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]; // Creating tempFile to use this file descriptor
    nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
    nw_endpoint_t endpoint = nw_endpoint_create_host(hostname, port);
    nw_connection_t connection = nw_connection_create(endpoint, parameters);
    [self start_connection:connection: tempFileHandle: fd]; // Start connection
}

Кто-то подумает, что в моем методе подключения нет необходимости передавать переменную "fd". Думаю, вы совершенно правы, но у меня были проблемы с файловыми дескрипторами, и это была моя последняя попытка понять, что происходит.

Затем пытаюсь запустить соединение, и все сразу.

- (void)start_connection:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
    nw_connection_set_queue(connection, dispatch_get_main_queue());
    nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) {
        switch (state) {
            case nw_connection_state_waiting:
                NSLog(@"waiting");
                break;
            case nw_connection_state_failed:
                NSLog(@"failed");
                break;
            case nw_connection_state_ready:
                NSLog(@"connection is ready");
                [self receive_loop:connection: file :fd];
                break;
            case nw_connection_state_cancelled:
                NSLog(@"connection is cancelled");
                break;
            default:
                break;
        }
    });
    nw_connection_start(connection);
}

Моя консоль в Xcode просто отображает: "соединение готово".

Как видите, мы вызываем "метод receive_loop с 3 аргументами", давайте посмотрим на этот метод:

- (void)receive_loop:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
    nw_connection_receive(connection, 1, UINT32_MAX, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t receive_error) {

        dispatch_block_t schedule_next_receive = ^{
            if (is_complete &&
                context != NULL && nw_content_context_get_is_final(context)) {
                exit(0);
            }
            if (receive_error == NULL) {
                [self receive_loop:connection: file :fd];
            } else {
                NSLog(@"Error");
            }
        };

        if (content != NULL) {
            dispatch_write(fd, content, dispatch_get_main_queue(), ^(__unused dispatch_data_t _Nullable data, int stdout_error) {
                if (stdout_error != 0) {
                    NSLog(@"Error in receive loop");
                } else {
                    NSString* str = @"00000001 LOGIN zurgsrushmber@gmail.com mypasswordhere";
                    NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
                    [file writeData:data];
                    [self send_loop:connection: file :fd];
                    schedule_next_receive();
                }
            });
        } else {
            // No content, so directly schedule the next receive
            schedule_next_receive();
        }
    });
}

А теперь самое интересное: сначала я использовал STDOUT_FILENO вместо "fd" или (очень важно) "file.fileDescriptor" в моей функции dispath_write(). Ну, когда я использовал STDOUT_FILENO, моя консоль просто печатала что-то вроде этого "* OK Gimap готов к запросам от 109.252.29.37 m5mb106667441ljg" Как видите, я пытаюсь записать это в свои переменные "fd" или "file" (оба не работают, у меня "NULL" после попытки прочитать fd или файл).

Итак, вот как я пытаюсь отправить информацию, но это не имеет значения, потому что я не могу писать на свой fd, поэтому я не могу отправить данные на imap.gmail.com...

Мой метод send_loop:

- (void)send_loop:(nw_connection_t)connection :(NSFileHandle *)file :(dispatch_fd_t)fd {
    dispatch_read(fd, 8192, dispatch_get_main_queue(), ^(dispatch_data_t _Nonnull read_data, int stdin_error) {
        if (stdin_error != 0) {
//            … error logging …
        } else if (read_data == NULL) {
//            … handle end of file …
        } else {
            nw_connection_send(connection, read_data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t  _Nullable error) {
                if (error != NULL) {
//                    … error logging …
                } else {
                    NSLog(@"send loop %@", (NSData *)read_data);
                    [self send_loop:connection: file :fd];
                }
            });
        }
    });
}

Кто-нибудь, помогите, пожалуйста!

ОБНОВЛЕНИЕ: метод receive_loop работает правильно, я понял это, когда открыл папку tmp и увидел мой файл с содержимым: "* OK Gimap готов к запросам от 109.252.29.37 m5mb106667441ljg", но я не могу прочитать этот файл с помощью метода send_loop...

1 ответ

ИЗМЕНИТЬ Я создал пакет, чтобы помочь с сокетами TLS. Вы можете легко подключиться к серверу NodeJS TLS, и он учитывает все недавние ограничения iOS 13 для завершения рукопожатия TLS. Множество ссылок на полезные статьи - https://github.com/eamonwhiter73/IOSObjCWebSockets - если вы разработчик, не стесняйтесь тянуть и менять, я не эксперт в этом вопросе, но я хотел создать это, потому что не похоже, что дляiOS/Objective-C/NodeJS комбинация в сети.

Думаю, вы не понимаете, как сформировать send loop, эта часть вашего кода:

} else if (read_data == NULL) {
    //… handle end of file …
} else {

Приведенная выше часть вашего кода очень важна для работы, я считаю, что ваш send_loop должно выглядеть примерно так:

- (void)send_loop:(nw_connection_t)connection {
    dispatch_read(STDIN_FILENO, 8192, dispatch_get_main_queue(), ^(dispatch_data_t _Nonnull read_data, int stdin_error) {
        if (stdin_error != 0) {
            //… error logging …
            NSLog(@"error int: %i", stdin_error);
        } else if (read_data == NULL) {
            nw_connection_send(connection, read_data, NW_CONNECTION_FINAL_MESSAGE_CONTEXT, true, ^(nw_error_t  _Nullable error) {
                if (error != NULL) {
                    NSLog(@"error: %@", error);
                }

                NSLog(@"read_data: %@", [read_data description]);
            });
        } else {
            nw_connection_send(connection, read_data, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t  _Nullable error) {
                if (error != NULL) {
                    NSLog(@"error: %@", error);
                } else {
                    [self send_loop:connection];
                }
            });
        }
    });
}

Та часть кода, о которой я говорю, важна из-за NW_CONNECTION_FINAL_MESSAGE_CONTEXT - когда читать нечего, он посылает сигнал, что это последнее сообщение.

Я все еще работаю над пакетом, который создаю, чтобы упростить работу WebSockets с IOS и основными Network пакет - я обновлю, если мне нужно, когда я закончу.

Другие вопросы по тегам