Как я могу убить / завершить / отменить / присоединиться к потоку блокировки сокетов? - PullRequest
4 голосов
/ 09 апреля 2020

Я создал тему, которая может читать сообщения Bluetooth, отправленные с телефона. Моя проблема возникла, когда я понял, что не могу безопасно убить эту нить. До сих пор методом был пользовательский ввод, который вызывал exit() из этого потока, все странные вещи начали происходить, когда я переместил это из этого потока в основной (обнаружен разрыв стека и ошибка сегментации). Очевидно, что этот подход также не очень хорошо масштабировался бы, если бы у меня был другой поток для вызовов Wi-Fi или я просто не хотел выходить из системы вообще.

Пока это мой код.

Структура для Связанные с Bluetooth вещи для передачи в поток:

typedef struct {
    struct sockaddr_rc loc_addr;
    struct sockaddr_rc rem_addr;
    socklen_t opt;
    char blu_buffer[1024];
    int blu_sock;    
} Bluetooth_stuff;

Поток прослушивания новых соединений:

void * BluetoothListiner(void * argv){

    Bluetooth_stuff * bt_set = (Bluetooth_stuff * ) argv;

    int bytes_read;
    int local_client;   
    int option = 0; 

    listen(bt_set->blu_sock, 1);
    memset(bt_set->blu_buffer, 0, sizeof(bt_set->blu_buffer));

    while(true){

        local_client = accept(bt_set->blu_sock, (struct sockaddr *)&((*bt_set).rem_addr), &((*bt_set).opt));        

        // read data from the client
        bytes_read = read(local_client, bt_set->blu_buffer, sizeof(bt_set->blu_buffer));
        if( bytes_read > 0 ) {
            // process data
            }   
        }
        else{
            // accept again?
        }       

        // clear the buffer
        memset(bt_set->blu_buffer, 0, sizeof(bt_set->blu_buffer));

        // close connection
        close(local_client);

        usleep(10);
    }
}

Конфигурация сокета:

void BluetoothSocketConfig(Bluetooth_stuff * bt_set){   

    // allocate socket
    bt_set->blu_sock = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);        
    bt_set->opt= sizeof(bt_set->rem_addr);

    (*bt_set).loc_addr.rc_family = AF_BLUETOOTH;
    (*bt_set).loc_addr.rc_bdaddr = *BDADDR_ANY;
    (*bt_set).loc_addr.rc_channel = (uint8_t) 1;        

    // bind socket to port 1 of the first available 
    bind(bt_set->blu_sock, (struct sockaddr *)&(bt_set->loc_addr), sizeof(bt_set->loc_addr));

}

Тестирование основного:

int main(int argc, char *argv[]){       
    Bluetooth_stuff bt_set;     
    BluetoothSocketConfig(&bt_set);     
    pthread_create(&blu_listiner, NULL, BluetoothListiner, bt_set);
    while(1);  // I modify that for testing to exit, having a global flag also gave me segmentation errors somehow
}

Вызов exit(1) не очищает другие потоки, а pthread_kill или pthread_cancel из другого потока часто приводят к тому, что процессы, все еще работающие в фоновом режиме, не позволяют мне снова запустить приложение, не убивая процессы вручную с помощью ps -A и kill -9 PROCESS.

Исходя из этой статьи Я решил, что лучший способ сделать это - ввести в поток оператор if и поместить его pthread_exit в забвение. Например:

if(external_exit){
    ret2  = 200;
    pthread_exit(&ret2);
}

Теперь, когда read() является блокирующим вызовом, я никогда не смогу прийти к выражению if без каких-либо внешних событий. Этот ответ предлагает использовать тайм-аут для достижения этого, и я следовал синтаксису этого ответа , чтобы выйти из read() через определенный период. Таким образом, я добавил это перед основным временем l oop потока (я думаю, что это должно быть определено только один раз, а не для каждого l oop, хотя и не уверен, поскольку не смог найти конкретный пример c).

Теперь моя проблема заключалась в том, что он заблокировал бы read() на первом l oop, но затем пропустил бы его, как будто ничего, и просто l oop на неопределенный срок. Я попытался «включить» read(), повторно вызвав accept() listen() и bind() безуспешно. Кажется, использование select() является предпочтительным вариантом для первой проверки, если есть что читать, но как указано в руководстве

" В Linux select () может сообщить дескриптор файла сокета как "готовый к чтению", хотя, тем не менее, последующие блоки чтения читаются. Это может, например, произойти, когда данные поступили, но при проверке имеет неправильную контрольную сумму и отбрасывается"

Так что даже при ее использовании (который я я не уверен, что будет работать с Bluetooth-сокетами, как это было бы с TCP-соединениями) всегда есть вероятность, что он все равно застрянет на read() без возможности убить его безопасно.

1) Как я могу безопасно уничтожить этот поток (или любой блокирующий поток)?
2) Действительно ли select() действительно необходим для подхода с блокировкой, поскольку у меня нет нескольких соединений?
3) Как выполнить процедуру для правильного использования read() в режим блокировки после начального тайм-аута?
4) Кроме различной инициализации все сокеты в основном эквивалентны unix / can / tcp / bluetooth?
5) Любые другие способы получить Что я хочу или серьезно fl aws в логи c с моей стороны?

Спасибо за любую помощь, если у кого-то есть ссылка на реализацию потока на сервере Bluetooth, я был бы рад видеть это (или любой другой сокет unix / can / tcp).

1 Ответ

0 голосов
/ 10 апреля 2020
  1. Откройте сокет с помощью SOCK_NONBLOCK и оставьте его в неблокирующем режиме на весь срок его жизни.
  2. На этапе инициализации вызовите eventfd(0,0), чтобы создать другой дескриптор файла.
  3. Вместо блокировки на read, настройте и вызовите select для ожидания на обоих дескриптора файла eventfd и сокета (передайте NULL для тайм-аута). Это может навсегда заблокировать.
  4. Когда select возвращает:
    • Если сокет доступен для чтения, вызовите read. Вы правы, что select мог сигнализировать о вещах неправильно, но в этом случае вы получите EAGAIN и сможете l oop вернуться назад и снова позвонить select. Если read возвращает любую другую ошибку, обработайте ее, закрыв соединение, и т. Д. c.
    • Если дескриптор файла eventfd доступен для чтения, закройте соединение и выполните корректное завершение работы
  5. В другом потоке, где вы ранее пытались убить блокировку thead, теперь вы можете write в дескрипторе файла eventfd "прервать" операцию выбора.
...