2012-12-12 91 views
0

我只想用命名管道編寫一個簡單的多客戶端服務器。我在服務器端遇到問題。當我只讀書時,一切都很好。現在,我添加了一個服務器發送消息回客戶端的可能性 - 現在它不工作...命名管道,選擇錯誤的文件描述符C++

繼承人我的服務器代碼(注意,當我刪除註釋行/ * * /,它從所有客戶讀取,它工作得很好,但 - 只讀)。如何強制服務器寫入所有客戶端呢? (這個概念是,客戶端首先寫入發送到服務器的客戶端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; 
} 

客戶端。 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: 
+0

您忘了設置套接字非阻塞。這個「寫」可以永遠阻止。 (另外,如果你描述了實際的問題,這真的很有幫助,不幸的是,「它根本不工作」不是一個很好的問題描述,它是否會掛起?崩潰?) –

+0

它可以與參數有關:O_RDONLY ,當你打開管道? – Pavenhimself

+0

@DavidSchwartz:看到我的編輯,它解釋了我的問題 – yak

回答

4

首先,檢查在open調用錯誤。這很可能是你問到的問題的解釋。

我不清楚「消息」應該是什麼。我發現它應該從#開始,但服務器應該如何知道它結束的位置?它總是4,096字節?

但是,你有三個主要問題:

  1. 您的代碼不應該阻止任何地方,但它不設置描述無阻塞。

  2. 您的代碼需要4,096字節的消息。但是,如果它讀取的字節更少,則不會等到它讀取其餘的內容,而是會破壞該消息。 (或者它期望的任何消息,它顯然可以超過管道可以自動處理的字節數,但需要以某種方式查找消息邊界。)

  3. 您的代碼無法處理部分寫入。如果任何客戶的閱讀速度不夠快,它會暫停(由於上面的問題1),但是一旦你解決了這個問題,它就會破壞數據。

您需要每個連接的應用程序接收緩衝區。當您從連接讀取數據時,請將其讀入應用程序緩衝區。如果你收到了整個消息,那麼處理好消息。否則,等到你讀完了剩下的部分。如果您不使用固定長度的消息,則必須處理讀取一條消息的結尾和另一條消息的開頭的情況。

你必須寫策略兩種選擇:

  1. 使用每個連接,應用程序寫入緩衝區。將數據發送到連接時,如果不立即發送所有字節,請將剩餘的數據保存到應用程序緩衝區中。開始選擇該描述符進行寫入以及讀取。如果您獲得寫入命中,請嘗試從應用程序緩衝區寫入數據。

  2. 只使用內核緩衝區。如果你得到部分寫入,內核緩衝區已滿。斷開客戶端,因爲它跟不上。

+0

感謝您的提示我想出了幾乎所有的東西(現在我的服務器讀取每個連接到它的客戶端)。但是,你能否給我一些關於如何寫給每個客戶端的代碼示例?服務器仍然不會寫入客戶端。 – yak

+0

1)將套接字設置爲非阻塞。 2)爲每個客戶端提供應用程序寫入緩衝區。 3)當你寫一個客戶端時,檢查它的緩衝區是否有數據。如果是這樣,只需將數據添加到緩衝區。 4)如果緩衝區爲空,請嘗試寫入數據。如果你寫這一切,很好。如果沒有,請將未發送的數據存儲在應用程序級緩衝區中,然後將套接字添加到您選擇要寫入的集合中。 5)如果您在套接字上發生寫入命中,請從緩衝區發送儘可能多的數據。 6)如果清空緩衝區,請從寫入集中移除套接字。 –

相關問題