程式 - 與國際象棋引擎stockfish / FIFO / Bash重定向通信
我正在嘗試編寫一個小國際象棋程序 - 實際上更像是一個國際象棋 GUI。當玩家與電腦對戰時,國際象棋 GUI 應在後台使用 stockfish 國際象棋引擎。
我安裝了 stockfish 並可以在終端中執行它並通過 STDIN 和 STDOUT 與它通信,例如我可以輸入“isready”,stockfish 以“readyok”響應。
現在我試圖能夠通過 linux 上的一些 IPC 方法從國際象棋 GUI 與 stockfish 進行通信。首先研究了管道,但由於管道單向通信而放棄了這一點。然後我閱讀了 FIFO 和 Bash 重定向,現在就嘗試一下。它有點工作,因為我可以從stockfish 讀取一行輸出。但這僅適用於第一行。然後,當我通過 FIFO 向 stockfish 發送“isready”並嘗試從 stockfish 讀取下一個輸出時,沒有響應。我使用 Bash 重定向將 stockfish 的 STDIN 和 STDOUT 重定向到 FIFO。
我執行此腳本以在一個終端中啟動 stockfish:
#!/bin/bash rm /tmp/to_stockchess -f mkfifo /tmp/to_stockchess rm /tmp/from_stockchess -f mkfifo /tmp/from_stockchess stockfish < /tmp/to_stockchess > /tmp/from_stockchess
我用 ./stockfish.sh 呼叫這個腳本
例如,我有這個 c 程序(我是 C 新手)
#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fpi; fpi = fopen("/tmp/to_stockchess", "w"); FILE *fpo; fpo = fopen ("/tmp/from_stockchess", "r"); char * line = NULL; size_t len = 0; ssize_t read; read = getline(&line, &len, fpo); printf("Retrieved line of length %zu:\n", read); printf("%s", line); fprintf(fpi, "isready\n"); read = getline(&line, &len, fpo); printf("Retrieved line of length %zu:\n", read); printf("%s", line); fclose (fpi); fclose (fpo); return 0; }
終端中程序的輸出(但程序不會停止,它會等待):
Retrieved line of length 74: Stockfish 11 64 POPCNT by T. Romstad, M. Costalba, J. Kiiski, G. Linscott
唉,不能連續工作(我現在沒有循環,只是試圖在沒有循環的情況下讀取兩次或更多次),例如,stockfish 腳本在一個終端中終止(而不是連續執行),在從鱈魚輸出先進先出。或者我可以從stockfish 輸出FIFO 中讀取一行輸出。如果有更簡單的方法通過 STDIN 和 STDOUT 對stockfish 進行 IPC,我也可以嘗試。謝謝你。
Since you're already working with C, then I'd suggest you manage
stockchessin C as well. There is a library function,
popen()` that will give you a unidirectional pipe to a process – that doesn’t suite your use case. You can, however, set it up yourself.Consider the following example program:
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> /** * Creates two pipes, forks, and runns the given command. One pipe is * connected between the given *out and the standard input stream of the child; * the other pipe is connected between the given *in and the standard output * stream of the child. * * Returns the pid of the child on success, -1 otherwise. On error, errno * will be set accordingly. */ int bi_popen(const char* const command, FILE** const in, FILE** const out) { const int READ_END = 0; const int WRITE_END = 1; const int INVALID_FD = -1; int to_child[2] = { INVALID_FD, INVALID_FD }; int to_parent[2] = { INVALID_FD, INVALID_FD }; *in = NULL; *out = NULL; if (command == NULL || in == NULL || out == NULL) { errno = EINVAL; goto bail; } if (pipe(to_child) < 0) { goto bail; } if (pipe(to_parent) < 0) { goto bail; } const pid_t pid = fork(); if (pid < 0) { goto bail; } if (pid == 0) { // Child if (dup2(to_child[READ_END], STDIN_FILENO) < 0) { perror("dup2"); exit(1); } close(to_child[READ_END]); close(to_child[WRITE_END]); if (dup2(to_parent[WRITE_END], STDOUT_FILENO) < 0) { perror("dup2"); exit(1); } close(to_parent[READ_END]); close(to_parent[WRITE_END]); execlp(command, command, NULL); perror("execlp"); exit(1); } // Parent close(to_child[READ_END]); to_child[READ_END] = INVALID_FD; close(to_parent[WRITE_END]); to_parent[WRITE_END] = INVALID_FD; *in = fdopen(to_parent[READ_END], "r"); if (*in == NULL) { goto bail; } to_parent[READ_END] = INVALID_FD; *out = fdopen(to_child[WRITE_END], "w"); if (*out == NULL) { goto bail; } to_child[WRITE_END] = INVALID_FD; setvbuf(*out, NULL, _IONBF, BUFSIZ); return pid; bail: ; // Goto label must be a statement, this is an empty statement const int old_errno = errno; if (*in != NULL) { fclose(*in); } if (*out != NULL) { fclose(*out); } for (int i = 0; i < 2; ++i) { if (to_child[i] != INVALID_FD) { close(to_child[i]); } if (to_parent[i] != INVALID_FD) { close(to_parent[i]); } } errno = old_errno; return -1; } int main(void) { FILE* in = NULL; FILE* out = NULL; char* line = NULL; size_t size = 0; const int pid = bi_popen("/bin/bash", &in, &out); if (pid < 0) { perror("bi_popen"); return 1; } fprintf(out, "ls -l a.out\n"); getline(&line, &size, in); printf("-> %s", line); fprintf(out, "pwd\n"); getline(&line, &size, in); printf("-> %s", line); fprintf(out, "date\n"); getline(&line, &size, in); printf("-> %s", line); // Since in this case we can tell the child to terminate, we'll do so // and wait for it to terminate before we close down. fprintf(out, "exit\n"); waitpid(pid, NULL, 0); fclose(in); fclose(out); return 0; }
In the program, I have defined a function
bi_popen
. The function take as input the path of a program to run as well as twoFILE*
:in
for input from the command andout
for output to the command.
bi_popen
sets up two pipes, one for communicating from the parent process to the child process, and another for communicating from the child process to the parent process.Next,
bi_popen
fork
s, creating a new process. The child process connects its standard output to the write-end of the pipe to the parent, and connects its standard input to the read-end of the pipe from the parent. It then cleans up the old pipe file descriptors and usesexeclp
to replace the running process with the given command. That new program inherits the standard input/output configuration with the pipes. On success,execlp
never returns.In the case of the parent — when
fork
returns a non-zero value —, the parent process closes the unnecessary ends of the pipe, and usesfdopen
to createFILE*
associated with the relevant pipe file descriptors. It updates thein
andout
output parameters with those values. Finally, it usesexeclp
on the outputFILE*
to make it unbuffered (so that you don’t have to explicitly flush content you send to the child process).The
main
function is an example of how to use thebi_popen
function. It callsbi_popen
with the command as/bin/bash
. Anything written to theout
stream is sent to bash to execute. Anything bash prints to standard output is available for reading fromin
.Here’s an example run of the program:
$ ./a.out -> -rwxr-xr-x 1 user group 20400 Aug 29 17:09 a.out -> /home/user/src/bidirecitonal_popen -> Sat Aug 29 05:10:52 PM EDT 2020
Note that
main
writes a series of commands to the child process (here,bash
), and the command responded with the expected output.In your case, you could replace “/bin/bash” with “stockfish”, then use
out
to write commands tostockfish
andin
to read the responses.`