我正在尝试在Linux中使用C实现TCP服务器。我希望这台服务器永远接受来自多个客户端的传入数据,同时每3秒钟将一些数据发送回每个连接的客户端。
我的问题是我不知道如何在与处理客户端的流程不同的流程中正确地执行send()
。
我正在做的是在程序开始时执行fork()
并执行
while (1) {
sleep(3);
// compute and `send()` data to each connected peers
}
在子进程中,执行
sock = create_socket();
while (1) {
client_sock = accept_connection(sock);
if (fork() == 0) {
close(sock);
handle_client(client_sock);
exit(0);
}
close(client_sock);
// clean up zombies
}
在父进程中。 handle_client()
只是无限循环中的recv()
数据。因为send()
和recv()
在不同的进程中执行,所以我无法在父进程中将套接字文件描述符用于send()
。在父进程中我需要做什么才能执行send()
?
答案 0 :(得分:3)
您有三个级别的进程,父级,子级和许多孙子级。摆脱这些级别,不要分叉; ;而是在一个过程中使用事件驱动的模型。
粗略的伪代码(翻译成您的首选语言):
listening_fd = create_socket(); EventQueueOfSomeKind q; // kqueue()-style q.add_or_update_event(listening_fd, EVFILT_READ, EV_ENABLE); q.add_or_update_event(3, EVFILT_TIMER, EV_ENABLE, NOTE_SECONDS); FDToContextMapOfSomeKind context_map; EventVector event_vector; // vector of kevent-like things while (1) { q.wait_for_events(&event_vector); // kevent()-style foreach e <- event_vector { switch (e.type) { case EVFILT_READ: if (listening_fd == e.fd) { client_sock = accept_connection(e.fd, SOCK_NONBLOCK); q.add_or_update_event(client_sock, EVFILT_READ, EV_ENABLE); q.add_or_update_event(client_sock, EVFILT_WRITE, EV_DISABLE); context_map.add_new_context(client_socket); } else { // Must be one of the client sockets if (e.flags & EV_EOF) { context_map.remove_context(e.fd); q.remove_event(e.fd, EVFILT_READ); q.remove_event(e.fd, EVFILT_WRITE); close(e.fd); } else { recv(e.fd, buffer); handle_client_input(&context_map[e.fd], buffer); } } break; case EVFILT_WRITE: if (has_queued_output(context_map[e.fd])) { send(e.fd, pull_queued_output(&context_map[e.fd])); } else { q.add_or_update_event(client_sock, EVFILT_WRITE, EV_DISABLE); } break; case EVFILT_TIMER: foreach client_sock,context <- context_map { push_queued_output(&context, computed_data(context)); q.add_or_update_event(client_sock, EVFILT_WRITE, EV_ENABLE); } break; } } }
我已经掩盖了部分send()
和recv()
s,写入端关闭以及所有错误处理,但这是一般的想法。
kqueue
。 OpenBSD系统调用手册。kqueue
。 Darwin BSD Calls Manual 。 Apple公司。答案 1 :(得分:0)
这是使用Linux epoll
和timerfd
的解决方案(省略错误处理):
int start_timer(unsigned int interval) {
int tfd;
struct itimerspec tspec;
tspec.it_value.tv_sec = 1;
tspec.it_value.tv_nsec = 0;
tspec.it_interval.tv_sec = 3;
tspec.it_interval.tv_nsec = 0;
tfd = timerfd_create(CLOCK_MONOTONIC, 0);
timerfd_settime(tfd, TFD_TIMER_ABSTIME, &tspec, NULL);
return tfd;
}
void epset_add(int epfd, int fd, uint32_t events)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = events;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}
int main()
{
int epfd, tfd, sock, nfds, i;
struct epoll_event events[MAX_EVENTS];
/* create new epoll instance */
epfd = epoll_create1(0);
tfd = start_timer(TIMER_INTERVAL);
/* socket(), bind() and listen() omitted in create_socket() */
sock = create_socket(PORT_NUMBER);
/* add sock and tfd to epoll set */
epset_add(epfd, tfd, EPOLLIN);
epset_add(epfd, sock, EPOLLIN | EPOLLET);
for (;;) {
for (i = 0; i < nfds; ++i) {
if (events[i].data.fd == tfd) {
/* handle timer notification, it's run
periodically with interval TIMER_INTERVAL */
} else if (events[i].data.fd == sock) {
/* accept() incoming connections,
set non-blocking,
and add new connection sockets to epoll set */
} else {
/* recv() from connection sockets and handle */
}
}
}
}
这个程序很有帮助https://github.com/eklitzke/epollet/blob/master/poll.c我将timerfd添加到epoll集中,以便服务器继续监听和接收数据,同时可以定期向客户端发送数据