多年以来,我一直在使用不同的语言(Java,PHP,ASP,JS ...),但从未接触过C。总是想着它,但总有明天。现在,我终于做到了,并且已经玩了一段时间,并想尝试使用套接字。因此,我制作了一个小型测试程序,该程序将读取文件并将其发送给客户端。使用epoll来处理fd状态,效果很好。然后,我想我将把这个测试扩展到包括更多有趣的东西,并决定将fork()添加到混合中。然后,与其正常地读取文件,不如简单地“捕获”文件内容并从设置的管道中读取输出。
这本来很简单,然后我也可以添加更多或其他内容,但是事实证明,这比我想象的要困难得多。用流程管道更改普通文件描述符并不是那么容易。如果您希望它正常运行,至少不会。
我测试过的文件包含60000+字节,但是在关闭连接之前,我得到了大约8000到14000的随机字节数。到进程的两个管道以及客户端套接字本身都接收到“ EPOLLHUP”,同时仍有数据要从进程输出中读取,并且服务器与客户端之间的连接已完全建立。我已经调试了S ***,并多次重写了整个内容,以尝试不同的方法。但是,当涉及从进程中提取数据并通过套接字将其发送到客户端时,我总是会失败。
这次是epoll事件标志,一直困扰着我。如果我删除了对“ EPOLLRDHUP | EPOLLHUP | EPOLLERR”标志的任何管理,我将获得完整的60000字节传输。
服务器代码
typedef struct stream_fd_info {
int fd;
uint32_t events;
} stream_fd_info_t;
typedef struct stream_info {
char buffer[1024];
ssize_t rc;
ssize_t wc;
ssize_t ofs;
stream_fd_info_t output;
stream_fd_info_t input;
} stream_info_t;
#define EPOLLDIED (EPOLLRDHUP|EPOLLHUP|EPOLLERR)
int main(int argc, char **argv) {
struct epoll_event event, events[6];
int epoll = epoll_create1(0);
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0), clifd;
struct sockaddr_un sockaddr, cliaddr;
socklen_t clilen;
sockaddr.sun_path[0] = '\0';
char* sockuri = "/tmp/ctest.sock";
sockaddr.sun_family = AF_UNIX;
memcpy(&sockaddr.sun_path[1], sockuri, strlen(sockuri));
socklen_t socklen = sizeof(sockaddr.sun_family) + strlen(sockuri) + 1;
if (bind(sockfd, (struct sockaddr *) &sockaddr, socklen) < 0 || listen(sockfd, 1) < 0) {
perror("Socket"); exit(1);
}
clifd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen);
if (clifd < 0) {
perror("Client"); exit(1);
}
int pipeout[2];
int pipein[2];
// Create the pipe FD's
pipe(pipeout);
pipe(pipein);
pid_t pid = fork();
if (pid < 0) {
perror("Fork"); exit(1);
} else if(!pid) {
fprintf(stderr, "Starting client process shell\n");
dup2(pipeout[0], 0);
dup2(pipein[1], 1);
dup2(1, 2);
close(pipein[0]);
close(pipein[1]);
close(pipeout[0]);
close(pipeout[1]);
char *args[] = {"cat", {"/opt/php/cli/php.ini"}};
execvp(args[0], args);
} else {
sleep(1);
close(pipein[1]);
close(pipeout[0]);
stream_info_t streams[2];
streams[0].input.fd = clifd;
streams[0].input.events = EPOLLDIED|EPOLLIN;
streams[0].output = streams[0].input;
streams[1].input.fd = pipein[0];
streams[1].output.fd = pipeout[1];
streams[1].input.events = EPOLLDIED|EPOLLIN;
streams[1].output.events = EPOLLDIED;
fprintf(stderr, "Monitoring uni-directional client descriptor %d\n", streams[0].output.fd);
fcntl(streams[0].input.fd, F_SETFL, fcntl(streams[0].input.fd, F_GETFL, 0) | O_NONBLOCK);
event.events = streams[0].input.events;
event.data.fd = streams[0].input.fd;
epoll_ctl(epoll, EPOLL_CTL_ADD, streams[0].input.fd, &event);
fprintf(stderr, "Monitoring process output descriptor %d\n", streams[1].output.fd);
fcntl(streams[1].output.fd, F_SETFL, fcntl(streams[1].output.fd, F_GETFL, 0) | O_NONBLOCK);
event.events = streams[1].output.events;
event.data.fd = streams[1].output.fd;
epoll_ctl(epoll, EPOLL_CTL_ADD, streams[1].output.fd, &event);
fprintf(stderr, "Monitoring process input descriptor %d\n", streams[1].input.fd);
fcntl(streams[1].input.fd, F_SETFL, fcntl(streams[1].input.fd, F_GETFL, 0) | O_NONBLOCK);
event.events = streams[1].input.events;
event.data.fd = streams[1].input.fd;
epoll_ctl(epoll, EPOLL_CTL_ADD, streams[1].input.fd, &event);
int ready, i, x;
int totalRead = 0, totalWrite = 0;
fprintf(stderr, "Setting up client connection\n");
int running = 1;
while (running) {
ready = epoll_wait(epoll, events, 6, 30000);
// fprintf(stderr, "There are %d descriptor(s) ready\n", ready);
for(i = 0; i < ready; i++) {
// fprintf(stderr, "Checking flags %d on descriptor %d\n", events[i].events, events[i].data.fd);
if ((events[i].events & EPOLLOUT) != 0) {
x = events[i].data.fd == streams[0].input.fd ? 0 : 1;
if (streams[x].wc > 0) {
streams[x].wc = write(events[i].data.fd, &streams[x].buffer[ streams[x].ofs ], (size_t) streams[x].rc);
if (streams[x].wc < 0) {
perror("Write"); exit(1);
} else if (streams[x].wc > 0) {
fprintf(stderr, "Wrote %d byte(s) of %d to descriptor %d\n", streams[x].wc, streams[x].rc, events[i].data.fd);
streams[x].rc -= streams[x].wc;
streams[x].ofs += streams[x].wc;
totalWrite += streams[x].wc;
}
if (streams[x].rc <= 0) {
streams[x].output.events &= ~EPOLLOUT;
event.events = streams[x].output.events;
event.data.fd = streams[x].output.fd;
fprintf(stderr, "Removed EPOLLOUT from descriptor %d\n", event.data.fd);
epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
}
}
}
if ((events[i].events & EPOLLIN) != 0) {
x = events[i].data.fd == streams[0].input.fd ? 1 : 0;
if (streams[x].rc <= 0) {
streams[x].rc = read(events[i].data.fd, streams[x].buffer, sizeof streams[x].buffer);
streams[x].ofs = 0;
if (streams[x].rc < 0) {
perror("Read"); exit(1);
} else if (streams[x].rc > 0) {
fprintf(stderr, "Read %d byte(s) from descriptor %d into %d bytes cache\n", streams[x].rc, events[i].data.fd, sizeof streams[x].buffer);
totalRead += streams[x].rc;
streams[x].output.events |= EPOLLOUT;
event.events = streams[x].output.events;
event.data.fd = streams[x].output.fd;
fprintf(stderr, "Added EPOLLOUT to descriptor %d\n", event.data.fd);
epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
}
}
}
if ((events[i].events & EPOLLDIED) != 0) {
// fprintf(stderr, "Descriptor %d has closed. Flags %d\n", events[i].data.fd, events[i].events);
/*x = events[i].data.fd == streams[0].input.fd ? 1 : 0;
if ((events[i].events & (EPOLLHUP|EPOLLERR)) != 0
&& streams[x].rc <= 0) {
running = 0;
} else {
fprintf(stderr, "Removing descriptor %d from the poll\n", events[i].data.fd);
epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, NULL);
}*/
}
}
}
int pidres;
if (waitpid(pid, &pidres, WNOHANG) == 0) {
// TODO: Cleaner shutdown
fprintf(stderr, "Terminating child process %d\n", pid);
kill(pid, SIGKILL);
}
fprintf(stderr, "Connection was close with total read count of %d bytes and write count of %d bytes\n", totalRead, totalWrite);
close(streams[1].input.fd);
close(streams[1].output.fd);
}
close(clifd);
close(sockfd);
close(epoll);
}
客户代码
#!/usr/bin/env php
<?php
echo "Establishing connection to the server...\n";
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
if (!$socket) {
die("Could not connect to the server socket\n");
} else {
socket_connect($socket, "\0/tmp/ctest.sock");
}
$totalRead = 0;
for (;;) {
echo "Reading from server...\n";
$buffer = socket_read($socket, 1024);
if ($socket !== FALSE && strcmp($buffer, '') != 0) {
$totalRead += strlen($buffer);
//echo $buffer;
echo "Total read count $totalRead bytes\n";
sleep(1);
} else {
break;
}
}
echo "Connection was closed with a total read of $totalRead bytes\n";
socket_close($socket);
C对我来说就像是一门外语,我仍在学习我的ABC。因此,如果那里的C大师能够发现我的新手错误并通过提示进行提示,那就太好了。下面的东西对您来说似乎很简单,但是我花了数周的时间解决了这个问题,但是却无济于事:o
谢谢
编辑: 在关闭任何内容之前添加'EPOLLIN | EPOLLOUT'检查可以解决某些问题。
if ((events[i].events & EPOLLDIED) != 0
&& (events[i].events & (EPOLLIN|EPOLLOUT)) == 0) {
这使进程fds按其应有的方式工作。输入流在开头关闭,这很好,因为“ cat”在输出数据时不使用它,而在读取最后一个字节时关闭输出流。
但是现在它无法检测到套接字连接丢失,因为“ EPOLLIN”始终处于打开状态,没有其他问题。我可以找到解决方法,但这真的是这些标志应如何起作用的吗?误报?如果插座是关闭的,则无法读取。
编辑2: 因此,我对其进行了重新组织,以使其更加可见,并修复了在清理过程中偶然发现的一些错误。在这里和那里添加了很多检查,现在终于可以在我测试的所有场景中正常工作了。为了安全起见,还建议对零读/写进行处理。
/*
*
*/
typedef struct stream_fd_info {
uint32_t events;
int fd;
int epoll;
} stream_fd_info_t;
/*
*
*/
typedef struct stream_info {
char buffer[4096];
ssize_t rc;
ssize_t wc;
ssize_t ofs;
stream_fd_info_t output;
stream_fd_info_t input;
} stream_info_t;
/*
*
*/
#define EPOLLDIED (EPOLLRDHUP|EPOLLHUP|EPOLLERR)
/**
*
*/
int process_validator(void* args) {
pid_t* pid = (pid_t*) args;
return waitpid(*pid, NULL, WNOHANG) == 0 ? 1 : 0;
}
/**
*
*/
void* stream_handler(stream_info_t* streams, int (*validator)(void*), void* args) { // stream_info_t[2]
stream_fd_info_t fdinfo;
struct epoll_event event, events[4];
int epoll = epoll_create1(0);
int i, x, y;
int ready, running = 1;
/*
* Start epoll monitoring on all FDs
*/
for (i=0; i < 2; i++) {
for (x=0; x < 2; x++) {
fdinfo = x == 0 ? streams[i].input : streams[i].output;
fprintf(stderr, "Monitoring %s descriptor %d\n", x == 0 ? "input" : "output", fdinfo.fd);
event.events = fdinfo.events;
event.data.fd = fdinfo.fd;
epoll_ctl(epoll, EPOLL_CTL_ADD, fdinfo.fd, &event);
fcntl(fdinfo.fd, F_SETFL, fcntl(fdinfo.fd, F_GETFL, 0) | O_NONBLOCK);
fdinfo.epoll = 1;
}
}
while (running) {
ready = epoll_wait(epoll, events, 4, 20000);
// fprintf(stderr, "There are %d descriptor(s) ready\n", ready);
/*
* If both sets of FD's can only read or only write, then there is no
* way for them to communicate. As such we can consider this connection closed/broken.
*/
if (ready == 0
&& (( (!streams[0].input.epoll || !streams[1].output.epoll) && (!streams[0].output.epoll || !streams[1].input.epoll) )
|| !validator(args))) {
fprintf(stderr, "The connection has timed-out\n");
running = 0;
} else if (ready < 0) {
fprintf(stderr, "Epoll crashed on error code %d\n", errno);
running = 0;
}
for(i = 0; i < ready; i++) {
// fprintf(stderr, "Checking flags %d on descriptor %d\n", events[i].events, events[i].data.fd);
if ((events[i].events & EPOLLOUT) != 0) {
x = events[i].data.fd == streams[0].output.fd ? 0 : 1;
if (streams[x].rc > 0) {
streams[x].wc = write(events[i].data.fd, &streams[x].buffer[ streams[x].ofs ], (size_t) streams[x].rc);
if (streams[x].wc < 0
&& errno != EWOULDBLOCK && errno != EAGAIN) {
running = 0;
} else if (streams[x].wc > 0) {
fprintf(stderr, "Wrote %d byte(s) of %d to descriptor %d\n", streams[x].wc, streams[x].rc, events[i].data.fd);
streams[x].rc -= streams[x].wc;
streams[x].ofs += streams[x].wc;
} else if (streams[x].wc == 0) {
// Lets deal with this at the end
if ((events[i].events & EPOLLDIED) == 0) {
events[i].events |= EPOLLDIED;
}
events[i].events &= ~EPOLLOUT;
}
if (streams[x].rc <= 0) {
streams[x].output.events &= ~EPOLLOUT;
event.events = streams[x].output.events;
event.data.fd = streams[x].output.fd;
fprintf(stderr, "Removed EPOLLOUT from descriptor %d\n", event.data.fd);
epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
}
}
}
if ((events[i].events & EPOLLIN) != 0) {
x = events[i].data.fd == streams[0].input.fd ? 1 : 0;
if (streams[x].rc <= 0) {
streams[x].rc = read(events[i].data.fd, streams[x].buffer, sizeof streams[x].buffer);
streams[x].ofs = 0;
if (streams[x].rc < 0
&& errno != EWOULDBLOCK && errno != EAGAIN) {
running = 0;
} else if (streams[x].rc > 0) {
fprintf(stderr, "Read %d byte(s) from descriptor %d into %d bytes cache\n", streams[x].rc, events[i].data.fd, sizeof streams[x].buffer);
streams[x].output.events |= EPOLLOUT;
event.events = streams[x].output.events;
event.data.fd = streams[x].output.fd;
fprintf(stderr, "Added EPOLLOUT to descriptor %d\n", event.data.fd);
epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
} else if (streams[x].rc == 0) {
// Lets deal with this at the end
if ((events[i].events & EPOLLDIED) == 0) {
events[i].events |= EPOLLDIED;
}
events[i].events &= ~EPOLLIN;
}
}
}
if ((events[i].events & EPOLLDIED) != 0
&& (events[i].events & (EPOLLIN|EPOLLOUT)) == 0) {
fprintf(stderr, "Descriptor %d has closed. Flags %d\n", events[i].data.fd, events[i].events);
x = events[i].data.fd == streams[0].input.fd
|| events[i].data.fd == streams[0].output.fd ? 1 : 0;
y = x == 0 ? 1 : 0;
if ((events[i].events & (EPOLLHUP|EPOLLERR)) != 0
&& streams[x].rc <= 0) {
running = 0;
} else {
fprintf(stderr, "Removing descriptor %d from the poll\n", events[i].data.fd);
epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, NULL);
fdinfo = streams[y].input.fd == events[i].data.fd
? streams[y].input : streams[y].output;
fdinfo.epoll = 0;
}
}
}
}
close(epoll);
}
/**
*
*/
int main(int argc, char **argv) {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0), clifd;
struct sockaddr_un sockaddr, cliaddr;
socklen_t socklen, clilen;
char* sockuri = "/tmp/ctest.sock";
int pipeout[2], pipein[2];
stream_info_t streams[2];
pid_t pid;
int pidres;
sockaddr.sun_path[0] = '\0';
sockaddr.sun_family = AF_UNIX;
memcpy(&sockaddr.sun_path[1], sockuri, strlen(sockuri));
socklen = sizeof(sockaddr.sun_family) + strlen(sockuri) + 1;
if (bind(sockfd, (struct sockaddr *) &sockaddr, socklen) < 0 || listen(sockfd, 1) < 0) {
perror("Socket"); exit(1);
}
clifd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen);
if (clifd < 0) {
perror("Client"); exit(1);
}
fprintf(stderr, "Setting up client environment\n");
pipe(pipeout);
pipe(pipein);
pid = fork();
if (pid < 0) {
perror("Fork"); exit(1);
} else if(!pid) {
fprintf(stderr, "Starting client sub-process\n");
dup2(pipeout[0], 0);
dup2(pipein[1], 1);
dup2(1, 2);
close(pipein[0]);
close(pipein[1]);
close(pipeout[0]);
close(pipeout[1]);
char *args[] = {"cat", {"/opt/php/cli/php.ini"}};
execvp(args[0], args);
} else {
close(pipein[1]);
close(pipeout[0]);
streams[0].input.fd = clifd;
streams[0].input.events = EPOLLDIED|EPOLLIN;
streams[0].input.epoll = 0;
streams[0].output = streams[0].input;
streams[0].rc = 0;
streams[0].wc = 0;
streams[0].ofs = 0;
streams[1].input.fd = pipein[0];
streams[1].output.fd = pipeout[1];
streams[1].input.events = EPOLLDIED|EPOLLIN;
streams[1].output.events = EPOLLDIED;
streams[1].input.epoll = 0;
streams[1].output.epoll = 0;
streams[1].rc = 0;
streams[1].wc = 0;
streams[1].ofs = 0;
/*
* Now let the stream handler deal with I/O
*/
stream_handler(streams, process_validator, &pid);
if (waitpid(pid, &pidres, WNOHANG) == 0) {
fprintf(stderr, "Terminating child process %d\n", pid);
kill(pid, SIGKILL);
}
close(streams[1].input.fd);
close(streams[1].output.fd);
}
close(clifd);
close(sockfd);
fprintf(stderr, "Connection has been closed\n");
}
不确定这是正确的还是我遗漏了一些东西,但是它现在可以正常工作了。