2012-01-20 109 views
1

最近我一直在研究一些使用線程從服務器發送和接收消息的客戶端代碼。下面的代碼在運行時表現得很奇怪。輸入消息發送到服務器後,代碼完成任務,儘管「套接字已在使用」錯誤,服務器獲取它。但是,我嘗試發送給服務器的每個後續消息都不會立即收到,但它在客戶端程序終止時似乎都立即收到。地址已在使用中。

(此外,我敢肯定錯誤的是客戶端,如果一個評論的輸出功能的奇怪的行爲不能發揮。)

我怎樣才能解決這個問題?

客戶

#include <stdio.h> 
#include <cstdlib> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/time.h> 
#include <unistd.h> 
#include <netdb.h> 
#include <arpa/inet.h> 
#include <string> 
#include <iostream> 
#include <errno.h> 
#include <pthread.h>  
void* input(void* ptr) 
{ 
    int on = 1; 
    bool *input_done = ((struct thread_args*)ptr)->process_done; 
    struct addrinfo *res = ((struct thread_args*)ptr)->result; 
    char msg[256]; 
    int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol); 
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on)); 
    bind(sock,res->ai_addr,res->ai_addrlen); 
    connect(sock,res->ai_addr,res->ai_addrlen); 
    cin.getline(msg,256); 
    if (msg[0] == '/') {exit(1);} 
    send(sock,msg,sizeof msg,0); 
    cout << "You:" << msg << endl; 
    *input_done = 1; 
    close(sock); 
    pthread_exit(NULL); 
} 
void* output(void* ptr) 
{ 
     int on = 1; 
     bool *output_done = ((struct thread_args*)ptr)->process_done; 
    struct addrinfo *res = ((struct thread_args*)ptr)->result; 
    char msg[256]; 
    int sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol); 
    bind(sock,res->ai_addr,res->ai_addrlen); 
    connect(sock,res->ai_addr,res->ai_addrlen); 
    recv(sock,msg,sizeof msg,0); 
    cout << "Recieved:" << msg; 
    *output_done = 1; 
    close(sock); 
    pthread_exit(NULL); 
} 

void io_client() 
{ 
    //thread function variables 
    pthread_t t1,t2; 
    bool input_done = 1, output_done = 1; 
    //socket setup variables 
    struct addrinfo hints, *res; 
    memset(&hints,0,sizeof hints); 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    getaddrinfo("localhost","8080",&hints,&res); 
    //setting up structures to pass data to threaded functions 
    struct thread_args i_args, o_args; 
    i_args.result = res; i_args.process_done = &input_done; 
    o_args.result = res; o_args.process_done = &output_done; 
    while(1) 
    { 
     if (output_done) 
     { 
      pthread_create(&t2,NULL,output,&o_args); 
      output_done = 0; 
     } 
     if (input_done) 
     { 
      pthread_create(&t1,NULL,input,&i_args); 
      input_done = 0; 
     } 
    } 
} 
int main() 
{ 
    io_client(); 
} 

服務器

void server() 
{ 
    struct addrinfo hints, *res; 
    int sock=-1, newsock=-1; 
    int length, on=1; 
    char **address_list; int entries = 0; 
    //fd_set read_fd; 
    //struct timeval timeout; 
    char buffer[100]; 
    memset(&hints,0,sizeof hints); 
    res = NULL; 
    memset(&res,0,sizeof res); 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    getaddrinfo("localhost","8080",&hints,&res); 
    sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol); 
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&on,sizeof(on)); 
    bind(sock,res->ai_addr,res->ai_addrlen); 
    listen(sock,10); 
    while(1) 
    { 
     struct sockaddr_storage addr; 
     char ipstr[INET6_ADDRSTRLEN]; 
     socklen_t len; 
     len = sizeof addr; 
     newsock = accept(sock,NULL,NULL); 
     getpeername(newsock,(struct sockaddr*)&addr,&len); 
     struct sockaddr_in *s = (struct sockaddr_in*)&addr; 
     inet_ntop(AF_INET,&s->sin_addr,ipstr,sizeof ipstr); 
     length = 100; 
     setsockopt(newsock,SOL_SOCKET,SO_RCVLOWAT, (char*)&length,sizeof length); 
     recv(newsock,buffer,sizeof buffer,0); 
     cout << buffer << endl; 
    } 
    if (newsock != -1) 
    { 
     close(newsock); 
    } 
    if (sock != -1) 
    { 
     close(sock); 
    } 
} 
int main() 
{ 
    server(); 
} 
+1

這不是你的問題的答案,但我會建議你使用Boost.Asio。 =) –

+0

這就是純粹的C,除了單個'cout'語句。 – vines

回答

0

我猜你是給 「SO_REUSEADDR」 套接字選項的問題。

您是否在不關閉客戶端套接字的情況下一次又一次地調用該函數?在這種情況下,它不會工作。

這個套接字選項的目的是「當已經打開的同一個地址的套接字處於TIME_WAIT狀態時重新使用地址,否則你會得到提到的錯誤」。

如果客戶端每一次打開一個新的連接,然後我必須說,你必須更有效地組織你的代碼和處理插座關閉的情況也是如此。

3

它看起來像你試圖讓你的客戶端綁定()到服務器的同一端口。這沒有必要。更糟糕的是,你試圖綁定到服務器的IP地址 - 這也是一個更大的問題。一般來說,對於要調用connect()函數的客戶端套接字,您應該讓套接字綁定到端口0和IP 0,從而讓操作系統爲您選擇一個隨機可用的端口,並啓用正確的本地IP地址和連接適配器。您可以調用getsockname()來調用連接後發現OS爲您選擇的端口。

如果你讓操作系統爲你選擇客戶端端口,你將不需要SO_REUSESADDR調用。雖然,您的服務器代碼可以調用它,以便在關閉連接後仍然需要重新啓動,但連接仍處於等待關閉狀態。

另外。你沒有檢查任何套接字調用的返回值。這可能就是爲什麼你會得到一些神祕的結果。對bind()的調用更可能失敗,因爲您指定了服務器IP,但connect()成功,因爲它將自動綁定套接字(如果它尚未綁定)。

這是您輸入()函數的清理版本。轉換你的output()函數是一個留給讀者的練習。如果你按照我的例子,你會保持良好狀態。

void* input(void* ptr) 
{ 
    int on = 1; 
    bool *input_done = ((struct thread_args*)ptr)->process_done; 
    int ret; 
    int success = true; 

    struct sockaddr_in addrLocal = {}; 

    struct addrinfo *res = ((struct thread_args*)ptr)->result; 
    char msg[256]; 

    int sock = socket(AF_INET, SOCK_STREAM, 0); 
    success = (sock != -1); 

    if (success) 
    { 
     addrLocal.sin_family = AF_INET; 
     addrLocal.sin_port = INADDR_ANY;  // INADDR_ANY == 0 --> pick a random port for me 
     addrLocal.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY == 0 --> use all appropriate network 
     ret = bind(sock,(sockaddr*)&addrLocal,sizeof(addrLocal)); 
     if (ret == -1) perror("bind: "); 
     success = (ret != -1); 
    } 

    if (success) 
    { 
     ret = connect(sock,res->ai_addr,res->ai_addrlen); 
     if (ret == -1) perror("connect: "); 
     success = (ret != -1); 
    } 

    if (success) 
    { 
     cin.getline(msg,256); 
     if (msg[0] == '/') {exit(1);} 
     ret = send(sock,msg,sizeof msg,0); 
     if (ret == -1) perror("send: "); 
     success = (ret != -1); 
    } 

    if (success) 
    { 
     cout << "You:" << msg << endl; 
     *input_done = 1; 
    } 

    if (sock != -1) 
    { 
     close(sock); 
     sock = -1; 
    } 

    return NULL; 
}