Я просто хочу написать простой многоклиентный сервер с именованными каналами. У меня проблема с серверной стороной. Когда у меня было только чтение, все было отлично. Теперь я добавил для сервера возможность отправлять сообщение обратно клиенту — и теперь он вообще не работает …
Вот мой код сервера (обратите внимание, что когда я удаляю закомментированные строки / * * /, он читает со всех клиентов, он работает просто отлично, но — ТОЛЬКО ЧТЕНИЕ). Как заставить сервер писать всем клиентам тоже? (Концепция заключается в том, что клиент сначала записывает, отправляет на сервер клиенты fifoname. Сервер создает и открывает такой fifo и может записывать в него, но записывающая часть по-прежнему не хочет работать …: /)
int main (int argc, char *argv[])
{
int fd_in, fd_out, j, result;
char buffer[4096];
const char *myfifo = "./fifo";
mkfifo(myfifo, 0666 );
if ((fd_in = open(myfifo, O_RDONLY | O_NONBLOCK)) < 0)
perror(myfifo);
printf("Server reads from: %s\n", myfifo);for (;;) {
fd_set fds_r;
fd_set master;
FD_ZERO (&fds_r);
FD_ZERO (&master);
FD_SET (fd_in, &fds_r);
master = fds_r;
if ((result = select(fd_in + 1, &fds_r, NULL, NULL, NULL)) < 0)
perror ("select()");
if (! FD_ISSET(fd_in, &fds_r))
continue;
result = read(fd_in, buffer, 4096);
printf("FROM CLIENT: %s\n", buffer);
/*if(startsWith(buffer, "#"))
{
// remove # from message from client
string login = removePrefix(buffer);
string fifoname = "./fifo";
fifoname += login;
printf("REGISTERED: %s\n", fifoname.c_str());
mkfifo(fifoname.c_str(), 0666);
if ((fd_out = open(fifoname.c_str(), O_WRONLY)) < 0)
perror(fifoname.c_str());
FD_SET (fd_out, &master);
}
/* for(j = 0; j <= fd_in; j++) {
// send to everyone!
if (FD_ISSET(j, &master)) {
// except the listener and ourselves
if (j != fd_out) {
if (write(j, buffer, sizeof(buffer)) == -1) {
perror("write");
}
}
}
}*/memset(buffer, 0, sizeof(buffer));
}
fprintf (stderr, "Got EOF!\n");
close (fd_in);
return 0;
}
client.cpp
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <string.h>
using namespace std;
int main(int argc, char **argv)
{
int client_to_server;
const char *myfifo = "./fifo";
int server_to_client;
string f = "./fifo";
string u(argv[1]);
f += u;
const char *myfifo2 = f.c_str();
char str[BUFSIZ];
/* write str to the FIFO */
client_to_server = open(myfifo, O_WRONLY);
server_to_client = open(myfifo2, O_RDONLY);
// register message #username
string reg = "#";
reg += u;
printf("Client writes to: %s\n", myfifo);
printf("Client reads from: %s\n", myfifo2);
// first write to server, he can now make a fifo called fifo+username
if(write(client_to_server, reg.c_str(), strlen(reg.c_str())) == -1)
perror("write:");
while(1)
{
printf("Input message to serwer: ");
scanf("%s", str);
if(write(client_to_server, str, sizeof(str))==-1)
perror("Write:"); //Very crude error check
if(read(server_to_client,str,sizeof(str))==-1)
perror("Read:"); // Very crude error check
printf("...received from the server: %s\n",str);
}
close(client_to_server);
close(server_to_client);
/* remove the FIFO */
return 0;
}
Мой вывод:
на стороне сервера:
$ ./SERVER
Server reads from: ./fifo
FROM CLIENT: #username
FROM CLIENT: hello
FROM CLIENT:
FROM CLIENT: ?
FROM CLIENT:
FROM CLIENT: huh
FROM CLIENT:
сторона клиента:
$ ./CLIENT username
Client writes to: ./fifo
Client reads from: ./fifousername
Input message to serwer: hello
Read:: Bad file descriptor
...received from the server: hello
Input message to serwer: ?
Read:: Bad file descriptor
...received from the server: ?
Input message to serwer: huh
Read:: Bad file descriptor
...received from the server: huh
Input message to serwer:
Сначала проверьте наличие ошибок на open
вызов. Это, скорее всего, объясняет проблему, о которой вы спрашивали.
Мне не ясно, каким должно быть «сообщение». Я вижу, это должно начаться с #
, но как сервер должен знать, где он заканчивается? Это всегда 4096 байт?
Но у вас есть три основные проблемы:
Ваш код нигде не должен блокироваться, но он не устанавливает дескрипторы неблокирующими.
Ваш код ожидает 4096 байт сообщений. Но если он читает меньше байтов, он не ждет, пока прочитает остальные, а вместо этого искажает сообщение. (Или какое бы сообщение оно ни ожидало, оно может явно превышать количество байтов, которое канал может обработать атомарно. Вам нужно как-то найти границы сообщения.)
Ваш код не может обработать частичную запись. Если какой-либо клиент недостаточно быстро читает, он в настоящее время зависает (из-за проблемы 1 выше), но как только вы исправите это, он будет искажать данные.
Вам нужны буферы приема приложений для каждого соединения. Когда вы читаете данные из соединения, считывайте их в буфер приложения. Если вы получили все сообщение, отлично, обработайте сообщение. В противном случае, подождите, пока вы не прочитаете остальное. Если вы не используете сообщения фиксированной длины, вы должны обработать случай, когда вы читаете конец одного сообщения и начало другого.
У вас есть два варианта стратегии записи:
Используйте для каждого подключения буфер записи приложения. Когда вы отправляете данные в соединение, если все байты отправляются не сразу, сохраните остаток в буфере приложения. Начните выбирать по этому дескриптору как для записи, так и для чтения. Если вы получили запись, попробуйте выполнить запись из буфера приложения.
Используйте только буфер ядра. Если вы получаете частичную запись, буфер ядра заполнен. Отключите клиента, потому что он не может идти в ногу.
Других решений пока нет …