Ocaml Lwl_mvar.take не блокирует поток

Я все еще продолжаю писать простой игровой сервер. Благодаря совету, приведенному здесь, я реализовал поддержку mvar в надежде, что он заблокирует потоки, хотя в нем нет как минимум 2 игроков. Но это не ждет, пока я не положу туда какие-либо данные. Это всегда возвращение сна Lwt.t. Прежде всего, здесь мы принимаем подключения и предлагаем игроку войти в START, чтобы начать поиск партнеров:

let waiting_players = 
    Lwt_mvar.create_empty();;

let rec make_ready player = 
    player >>= fun cli ->
        send_to_client player "Type in START";
        let answer = read_from_client player in 
            answer >>= fun str ->
            match str with
                |"START" -> 
                    let ready_client =  cli in  
                    send_to_client player "Waiting for opponent";
                    Lwt_mvar.put waiting_players ready_client;
                | _ -> 
                    send_to_client player "Unknown command. try again";
                    make_ready player

let handle_income () =
    let in_conection = Lwt_unix.accept sock in 
    in_conection >>= fun (cli, addr) ->
    let player = Lwt.return cli in
    send_to_client player "Welcome to the server. To start game type in START and press Enter";
    make_ready player;;

    val make_ready : Lwt_unix.file_descr Lwt.t -> unit Lwt.t = <fun>
    val handle_income : unit -> unit Lwt.t = <fun>

Кажется, все в порядке, но когда я вызываю Lwt_mvar.take wait_players, он всегда возвращает некоторые значения, даже если до этого ничего не было помещено, и поток не блокируется. Такое странное (для меня) поведение лучше видно на примере:

# let bucket = Lwt_mvar.create_empty ();;
val bucket : '_a Lwt_mvar.t = <abstr>

# let apple = Lwt_mvar.take bucket;;
val apple : '_a Lwt.t = <abstr>

# Lwt.state apple;;
- : '_a Lwt.state = Sleep

Если "блокировка" означает возвращение именно таких спящих объектов, сообщите, пожалуйста. А как сделать цикл, возвращающий только "готовые" объекты наилучшим образом? Это хорошая идея использовать Lwt.is_sleeping? Большое спасибо.

2 ответа

Решение

Есть несколько проблем с вашим подходом и некоторые ошибки в вашем коде. Итак, я сначала выделю последнее, а затем предложу и обосновать другой подход.

вопросы

Выпуск 1

Похоже, что ваш send_to_client возвращает значение типа unit Lwt.t, Если вы просто игнорируете это, заканчивая свое выражение с ;, тогда это означает, что "не ждите, пока сообщение отправлено, и двигайтесь вперед". Обычно это не то, что вы хотите. Итак, вам нужно подождать, пока unit Lwt.t Завершение потока путем привязки к его возвращаемому значению.

Выпуск 2

Обычно в Lwt-программировании функции принимают значения непосредственных типов (т. Е. Тех, которые не включены в Lwt.t) и возвращает отложенные потоки (т. е. значения типа 'some Lwt.t). Обычно это, конечно, никто не мешает вам делать что-то другое. Но попробуйте придерживаться паттерна "немедленный ввод, задержанный вывод".

Выпуск 3

Используйте инструменты. использование ocp-indent отступ для вашего кода, это поможет в удобочитаемости. Кроме того, похоже, что вы не используете компилятор и играете на высшем уровне. Обычно это плохая идея, особенно с системным программированием. использование ocamlbuild скомпилировать и запустить ваш код с:

ocamlbuild game.native --

Игра

Программирование в OCaml имеет другую философию по сравнению с программированием на Python или других языках со слабой системой типов. В OCaml нужно начинать с разработки типов и подписей, а затем заполнять реализации. Конечно, это идеализация, и в реальной жизни это будет процесс итеративного уточнения, но общий подход все тот же. Начните с типов.

Итак, сначала давайте определим player тип. Это тривиально, но есть место для улучшения.

open Lwt

type player = {
  fd : Lwt_unix.file_descr
}

Далее, давайте использовать систему типов, чтобы помочь нам понять нашу проблему инициализации игры. Вам нужно подготовить двух игроков, которые хотят играть в вашу игру. Это означает, что у вас есть три последовательных состояния:

  • Nobody готов
  • One player готов
  • Both (player1, player2) готовы

На самом деле, поскольку, как только вы достигнете третьего состояния, вы готовы к игре, вам не нужно это состояние, поэтому мы в итоге имеем только два варианта:

type stage =
  | Nobody
  | One of player

Мы можем использовать player option введите здесь, поскольку это изоморфно нашему выбору. Но давайте будем более явными и использовать наши собственные stage тип. Это будет держать нашу модель более ограниченной и подходящей.

Следующим шагом будет определение протокола взаимодействия клиента и сервера. Мы будем использовать имя request для сообщения от сервера к клиенту, и response для сообщений, движущихся в противоположном направлении.

type request =
  | Init
  | Wait
  | Unknown_command
  | Bye 

type response =
  | Start
  | Quit

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

Но давайте смоделируем простейшую конкретную реализацию, которая использует текстовые команды:

let response_of_string msg =
  match String.trim (String.uppercase msg) with
  | "START" -> Some Start
  | "QUIT" -> Some Quit
  | _ -> None

И в обратном направлении (примечание: лучше выводить эти сообщения на стороне клиента и отправлять значения типов request а также response на проводе это будет поддерживать низкий профиль вашего трафика и, что более важно, позволит прозрачно подключать разных клиентов).

let string_of_request = function
  | Init -> "Welcome to a game server.
    Please, type 
    - `start' to start game;
    - `quit' to finish session"
  | Wait -> "Please wait for another player to join the game"
  | Unknown_command -> "Don't understand this"
  | Bye -> "Thank you, see you later!"

Следующим шагом является определение интерфейса для Io, Этот модуль отвечает за взаимодействие между клиентом и сервером. Обратите внимание, как мы скрываем с абстракцией все детали, например, используя сокеты или строки.

module Io : sig
  val send : player -> request -> unit Lwt.t
  val recv : player -> response option Lwt.t 
end = struct
  let send dst msg = return_unit
  let recv dst = return None
end 

Теперь мы можем определить наши Game модуль. Сначала у него будет два разных автомата:

  • init инициализировать игру между двумя игроками;
  • play чтобы сыграть в игру один раз, мы получаем двух жертв.

Давайте прямо скажем это в OCaml:

module Game : sig

  (** [play a b] play a game between player [a] and player [b] *) 
  val play : player -> player -> unit Lwt.t

  (** [init next_player] waits until two players are ready to play.
      TODO: Describe a grammar that is recognized by this automaton. *)
  val init : (unit -> player Lwt.t) -> (player * player) Lwt.t
end = struct
  let play a b = return_unit

  let init next_player =
    let rec process stage player = 
      Io.send player Init >>= fun () -> 
      Io.recv player >>= function
      | None ->
        Io.send player Unknown_command >>= fun () ->
        process stage player
      | Some Quit ->
        Io.send player Bye >>= fun () -> 
        next_player () >>= process stage
      | Some Start -> match stage with
        | One a -> return (a,player)
        | Nobody -> 
          Io.send player Wait >>= fun () ->
          next_player () >>= process (One player) in
    next_player () >>= process Nobody
end

Теперь мы можем написать основную функцию, которая склеивает все вместе:

let main server_sock = 
  let next_player () =
    Lwt_unix.accept server_sock >>=
    fun (fd,_) -> return {fd} in
  Game.init next_player >>= fun (a,b) -> 
  Game.play a b

Когда вы продолжите этот подход, вы можете позже заметить, что разные конечные автоматы вашей игры определяют разные языки (например, протоколы). Таким образом, вместо одного протокола, вы можете использовать специальный протокол для каждого FSM, например: init_protocol, play_protocolи т. д. Но вы также можете заметить, что этот протокол имеет некоторые пересечения. Чтобы справиться с этим, вы можете использовать подтипы и полиморфные варианты.

Нашел способ.

let rec form_pairs () = 
let player1 = Lwt_mvar.take waiting_players in 
    player1 >>= fun descriptor1 ->
let player2 = Lwt_mvar.take waiting_players in 
    player2 >>= fun descriptor2->
Lwt_io.printl "Pairs formed";
Lwt.return (descriptor1, descriptor2);
form_pairs ();;
Другие вопросы по тегам