[英]GNU C Multi-process handling with pipes
我在大學里被教給我一個在后台處理進程的最簡單方法,即通過管道read()
函數掛起子進程/父進程的運行。 老實說,我已經在做作業兩周了,無法解決異步流程處理問題。 我將代碼最小化,以編寫帶有2個管道的單個子處理,並使用read()
函數阻止父進程和子進程。 您可以在下面找到我的代碼的當前狀態:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char pipeMessage[100];
char* readFromPipe(int pipe)
{
pipeMessage[0] = '\0';
read(pipe, pipeMessage, sizeof(pipeMessage));
fflush(NULL);
return pipeMessage;
}
void writeToPipe(int pipe, char *input)
{
char text[strlen(input) + 1];
strncpy(text, input, (int)strlen(input));
strncat(text, "\0", 1);
printf("TEXT: %s\n", text);
write(pipe, text, sizeof(text));
fflush(NULL);
}
int main(void)
{
int pipes[2][2];
pid_t pid;
if(pipe(pipes[0]) < 0 || pipe(pipes[1]) < 0)
{
printf("[ERROR] create pipes\n");
return -1;
}
printf("[PARENT] Create child1\n");
if((pid = fork()) < 0)
{
printf("[ERROR] Fork error\n");
return -1;
}
if(pid == 0)
{
// Child code
close(pipes[0][0]);
writeToPipe(pipes[0][1], "TEST MESSAGE");
printf("[CHILD1] pipe message: %s\n", readFromPipe(pipes[1][0]));
writeToPipe(pipes[0][1], "-1");
}
else if(pid > 0)
{
// Parent code
close(pipes[0][1]);
close(pipes[1][0]);
char *message;
do
{
message = readFromPipe(pipes[0][0]);
printf("[PARENT] pipe message: %s\n", message);
writeToPipe(pipes[1][1], "-1");
}
while(atoi(message) != -1);
}
return 0;
}
錯誤如下:
[PARENT] Create child1
TEXT: TEST MESSAGE
[PARENT] pipe message: TEST MESSAGE
TEXT: -1
[CHILD1] pipe message: -1
TEXT: -1�po
[PARENT] pipe message: -1�T MESSAGE
TEXT: -1�po
我試圖用信號實現此過程處理,但是在最終應用程序中,我將需要3個不同的子進程,並且異步運行會導致信號處理問題。 我還嘗試在網上找到一個教程,但是每個多處理主題都涵蓋了一個簡單的單消息解決方案,從父級到子級,反之亦然。 但是我在父進程中需要一個基於字符的菜單,因此子進程應連續等待父信號/消息,並且父進程也需要等待子進程完成實際任務。 請幫幫我,因為我真的很困。 如果您對流程處理有任何正常的解決方案,請告訴我,因為我知道這段代碼很糟糕。 唯一的原因是缺少正確的教程。 提前致謝。
好的,我有一個模板,可以用於嵌入式解決方案。 使用此方法,我創建了一個有效的解決方案,其中包含客戶端循環,但僅聲明了服務器循環。 應該清楚如何解決。
應該清楚如何將多個讀取器/寫入器添加到select調用中。 注意有使用SOCK_DGRAM。 除了套接字對之外,還可以添加管道(這對於在流程之間流傳輸原始數據是首選的)。 我希望您可以從中學到一些東西。 我們應該刪除它(有時)。 它需要完成一些工作(服務器循環)
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <cstring> // strerror
#include <unistd.h> // pipe
#include <fcntl.h>
#include <stdint.h>
#include <algorithm>
#define CallErrExit(fun, arg, retval) { \
if ((fun arg)<0) { \
FailErr(#fun); \
return retval; \
} \
}
#define FailErr(msg) { \
(void)fprintf(stderr, "%s, %s(%d)\n", \
msg, strerror(errno), errno); \
(void)fflush(stderr);}
const int parentsocket = 0;
const int childsocket = 1;
int disk_main(int comm_server[]);
int server_main(int comm_disk[]);
int main(int argc, char* argv[]) {
int status; // Status parameter used waitpid
// Communication sockets
int comm_server_disk[2];
// Process Id's
pid_t proc_server;
pid_t proc_disk;
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
return EXIT_FAILURE;
}
proc_server = getpid();
int socket;
// SOCK_DGRAM are connectionless as opposed to SOCK_STREAM or SOCK_SEQPACKET
CallErrExit(socketpair, (AF_UNIX, SOCK_DGRAM, 0,
comm_server_disk), EXIT_FAILURE);
CallErrExit(proc_disk = fork,(),EXIT_FAILURE);
if (proc_disk == 0) {
// Disk process
proc_disk = getpid();
// TODO: Try the alternative, use the names '/proc/<process id>/fd/
printf("disk_main started with sockets:\n"
"\tserver->disk: /proc/%d/fd/%d\n"
"\tdaup->disk: /proc/%d/fd/%d\n",
proc_disk, socket+1,
proc_disk, socket+3);
/*
* Closing comm_server_disk[1] before function entry
*/
close(comm_server_disk[childsocket]); // Write to server
return disk_main(comm_server_disk);
}
else {
// Server process, closes comm_server_disk[0]
server_main(comm_server_disk);
// Never reached
// calling process sets SIGCHLD to SIG_IGN, ECHILD is set
CallErrExit(waitpid, (proc_disk,&status,0), EXIT_FAILURE);
return EXIT_SUCCESS;
}
}
typedef struct internal_cmd {
char cmd[32];
} internal_cmd_t;
enum e_cmd {
eExit = 0x01,
};
// TODO: Some map between enums and cmd[32]
int disk_main(int comm_server[]) {
char buf[1024];
int select_width;
fd_set rfds, wfds, efds;
int next_select_width;
fd_set next_rfds, next_wfds, next_efds;
int n_fds;
struct timeval timeout = { 10, 0 };
FD_ZERO(&next_rfds);
FD_ZERO(&next_wfds);
FD_ZERO(&next_efds);
FD_SET(comm_server[0], &next_rfds);
// Default: is blocking, but be sure
fcntl(comm_server[0],
F_SETFL, fcntl(comm_server[0], F_GETFL, 0) & ~O_NONBLOCK);
// Add other file descriptors
int ofd = comm_server[0];
next_select_width = std::max(comm_server[0],ofd) + 1;
do {
// Update read/write state
select_width = next_select_width;
rfds = next_rfds;
wfds = next_wfds;
efds = next_efds;
// Wait for interrupt
n_fds = select(select_width,&rfds, &wfds, &efds, &timeout);
if (n_fds < 0) {
fprintf(stderr,"ERROR\n");
exit(1);
}
if (FD_ISSET(comm_server[0], &wfds))
printf("Disk process can write to server\n");
if (FD_ISSET(comm_server[0], &rfds)) {
printf("Disk process received message from server\n");
int rv = recv(comm_server[0], buf, sizeof(buf), MSG_DONTWAIT);
if (rv < 0) {
printf("Disk process - %s(%d)\n", strerror(errno), errno);
exit (1);
}
printf("Disk process received %d bytes:\n", rv);
// Interpret command
if (rv == sizeof(internal_cmd_t)) {
// Here interpret command
e_cmd cmd;
if (cmd == eExit) {
printf("Exiting\n");
close(comm_server[0]);
return EXIT_SUCCESS;
}
}
}
FD_ZERO(&next_rfds); FD_ZERO(&next_wfds); FD_ZERO(&next_efds);
FD_SET(comm_server[0], &next_rfds);
fcntl(comm_server[0], F_SETFL, fcntl(comm_server[0], F_GETFL, 0) & ~O_NONBLOCK);
int ofd = comm_server[0];
next_select_width = std::max(comm_server[0],ofd) + 1;
}
while (true);
close(comm_server[0]);
return EXIT_SUCCESS;
}
您的編寫邏輯值得懷疑,管道的布局也很復雜。 下面是根據我的需求量身定制的簡單代碼。 包含評論以幫助您。 我發現在處理鏈管(最適合所有意圖)時,最簡單的方法是布置一個由表現鏈的宏索引的描述符數組:
// some hand macros for access the correct pipes
#define P_READ 0
#define C_WRITE 1
#define C_READ 2
#define P_WRITE 3
#define N_PIPES 4
以上將在最終的源列表中。 該綽號應該是不言而喻的,但如果不是, P_XXX
指出父進程使用管道, C_XXX
注意到孩子過程中使用的管道。 看到代碼時請記住:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
// some hand macros for access the correct pipes
#define P_READ 0
#define C_WRITE 1
#define C_READ 2
#define P_WRITE 3
#define N_PIPES 4
// reads a buffer up-to len size.
ssize_t readFromPipe(int pipe, char *buff, size_t len)
{
buff[0] = 0;
ssize_t res = read(pipe, buff, len);
assert(res >= 0 && "Failed to read from pipe");
return res;
}
ssize_t writeToPipe(int pipe, const char *input)
{
size_t len = strlen(input)+1;
ssize_t res = write(pipe, input, len);
assert(res == len && "Failed to write to pipe");
return res;
}
int main(void)
{
int pipes[N_PIPES];
char msg[128] = {0};
pid_t pid;
if(pipe(pipes) < 0 || pipe(pipes+2) < 0)
{
printf("[ERROR] create pipes\n");
return EXIT_FAILURE;
}
if((pid = fork()) < 0)
{
printf("[ERROR] Fork error\n");
return EXIT_FAILURE;
}
// parent code
if(pid > 0)
{
// Parent code. close down the child pipes; don't need them
printf("parent(%d) create: child(%d)\n", getpid(), pid);
close(pipes[C_WRITE]);
close(pipes[C_READ]);
do
{
if (readFromPipe(pipes[P_READ], msg, sizeof(msg)) > 0)
{
printf("parent(%d) read : %s\n", getpid(), msg);
writeToPipe(pipes[P_WRITE], "-1");
}
else break;
}
while(atoi(msg) != -1);
// close remaining pipes. no longer needed
close(pipes[P_READ]);
close(pipes[P_WRITE]);
}
else if(pid == 0)
{
// Child code. don't need parent write or child-read lines
close(pipes[P_READ]);
close(pipes[P_WRITE]);
// write message
writeToPipe(pipes[C_WRITE],"test message");
// read test message
if (readFromPipe(pipes[C_READ], msg, sizeof(msg)) > 0)
printf("child(%d) read : %s\n", getpid(), msg);
// write another message
writeToPipe(pipes[C_WRITE], "-1");
// close remaining pipes. no longer needed
close(pipes[C_READ]);
close(pipes[C_WRITE]);
}
return EXIT_SUCCESS;
}
盡管不使用管道描述符數組,但最大的變化是簡化的writeToPipe
邏輯,該邏輯只是將終止符C字符串寫入管道, 包括終止的null char 。
ssize_t writeToPipe(int pipe, const char *input)
{
size_t len = strlen(input)+1;
ssize_t res = write(pipe, input, len);
assert(res == len && "Failed to write to pipe");
return res;
}
調用者檢查結果以確保它寫入了所有請求的數據,並且嵌入式的assert()宏將在失敗時觸發調試器。 讀取功能也存在類似的邏輯。
輸出 (因進程ID而異)
parent(2067) create: child(2068)
parent(2067) read : test message
child(2068) read : -1
parent(2067) read : -1
我希望這有幫助。 當處理管道時,尤其是在不久的將來可能會遇到的stdio重定向( 請參閱此處的破壞器 )時,為您的代碼提供有意義的助記符(例如我在上面使用的宏)將極大地幫助您索引管道數組。
祝你好運。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.