2010-06-17 95 views
17

Ứng dụng khách của tôi sử dụng boost::asio::ip::tcp::socket để kết nối với máy chủ từ xa. Nếu ứng dụng mất kết nối với máy chủ này (ví dụ: do máy chủ gặp sự cố hoặc bị tắt), tôi muốn thử kết nối lại theo khoảng thời gian đều đặn cho đến khi ứng dụng thành công.Làm thế nào để tôi kết nối lại một cách hiệu quả boost :: socket sau khi ngắt kết nối?

Tôi cần làm gì ở phía máy khách để xử lý ngắt kết nối, dọn dẹp và sau đó liên tục thử kết nối lại?

Hiện tại, các bit thú vị của mã của tôi trông giống như thế này.

tôi connect như thế này:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    socket.connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     mydisconnect(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 

Trường hợp phương pháp runIOServer() chỉ là:

void MyClient::runIOService(boost::asio::io_service& io_service) 
{ 
    size_t executedCount = io_service.run(); 
    cout << "io_service: " << executedCount << " handlers executed." << endl; 
    io_service.reset(); 
} 

Và nếu bất kỳ của các bộ xử lý async đọc trả về một lỗi sau đó họ chỉ cần gọi phương thức disconnect này:

void MyClient::mydisconnect(void) 
{ 
    boost::system::error_code errorcode; 

    if (socket.is_open()) 
    { 
     // Boost documentation recommends calling shutdown first 
     // for "graceful" closing of socket. 
     socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.shutdown error: " << errorcode.message() << endl; 
     } 

     socket.close(errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.close error: " << errorcode.message() << endl; 
     }  

     // Notify the observer we have disconnected 
     myObserver->disconnected();    
    } 

..những lần thử ngắt kết nối hoàn toàn và sau đó thông báo cho một người quan sát, sẽ bắt đầu gọi connect() sau năm giây cho đến khi nó được kết nối lại.

Tôi còn cần phải làm gì khác không?

Hiện tại, điều này có nghĩa là dường như để hoạt động. Nếu tôi giết máy chủ mà nó được kết nối với tôi nhận được lỗi "End of file" được mong đợi tại các trình xử lý đọc của tôi và mydisconnect() được gọi mà không có bất kỳ vấn đề nào.

Nhưng khi nó cố gắng kết nối lại và không thành công, tôi thấy báo cáo "socket.shutdown error: Invalid argument". Đây có phải chỉ vì tôi đang cố gắng tắt một ổ cắm không có đọc/ghi đang chờ xử lý không? Hay là một cái gì đó nhiều hơn?

+0

Lý do để gọi tắt máy của bạn là gì nếu bạn đã phát hiện đầu kia của kết nối bị đóng? –

+0

@samm: Điều đó không được khuyến nghị? Tôi nghĩ rằng có thể có các hoạt động đang chờ xử lý trên socket cần được hủy bỏ bằng 'shutdown()'. Tôi chủ yếu chỉ làm điều đó để đơn giản mặc dù: cùng một phương thức 'mydisconnect()' được gọi nếu tôi muốn ngắt kết nối bình thường, hoặc nếu bất kỳ hoạt động async nào trả về lỗi. – GrahamS

+0

Tôi không chắc chắn nó có được khuyên dùng hay không. Dữ liệu hoặc hoạt động đang chờ xử lý sẽ đi đâu? Đầu kia của kết nối không có ở đó. –

Trả lời

22

Bạn cần tạo boost::asio::ip::tcp::socket mới mỗi khi bạn kết nối lại. Cách dễ nhất để làm điều này có lẽ là chỉ phân bổ ổ cắm trên heap bằng cách sử dụng boost::shared_ptr (bạn có thể cũng có thể lấy đi với scoped_ptr nếu ổ cắm của bạn được đóng gói hoàn toàn trong một lớp). Ví dụ:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    // socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket> 
    socket.reset(new boost::asio::ip::tcp::socket(...)); 
    socket->connect(server_endpoint, errorcode); 
    // ... 
} 

Sau đó, khi mydisconnect được gọi, bạn có thể deallocate ổ cắm:

void MyClient::mydisconnect(void) 
{ 
    // ... 
    // deallocate socket. will close any open descriptors 
    socket.reset(); 
} 

Các lỗi mà bạn thấy có lẽ là một kết quả của hệ điều hành dọn dẹp các mô tả tập tin sau khi bạn' đã gọi là close. Khi bạn gọi close và sau đó thử đến connect trên cùng một ổ cắm, có thể bạn đang cố gắng kết nối một bộ mô tả tệp không hợp lệ. Tại thời điểm này, bạn sẽ thấy thông báo lỗi bắt đầu bằng "Kết nối không thành công: ..." dựa trên logic của bạn, nhưng sau đó bạn gọi mydisconnect có thể sau đó đang cố gắng gọi shutdown trên bộ mô tả tệp không hợp lệ. Vòng luẩn quẩn!

+0

Cố gắng ngăn cản tính di động không bao giờ là ý tưởng hay, nhất là khi bạn thậm chí không biết hệ điều hành được nhắm mục tiêu hoặc hệ điều hành được sử dụng để phát triển, cho vấn đề đó !! – Geoff

+0

@Geoff: đó là điểm công bằng ... Tôi đã chỉnh sửa phản hồi của tôi. – bjlaub

+0

Cảm ơn cách tiếp cận này đã làm việc tốt cho tôi. Tôi sửa đổi kết nối để luôn luôn tạo ra một ổ cắm mới và sau đó gọi '.reset' trên' shared_ptr' như bạn mô tả. Nếu cố gắng kết nối thất bại, tôi chỉ gọi 'socket-> close()' và để nó ở đó. Tôi đã thoát khỏi phương thức 'disconnect' như với lệnh gọi * shutdown *' shutdown'. – GrahamS

1

Tôi đã thực hiện điều gì đó tương tự bằng cách sử dụng Boost.Asio trong quá khứ. Tôi sử dụng các phương thức không đồng bộ, do đó, kết nối lại thường cho phép đối tượng ip :: tcp :: socket hiện tại của tôi ra khỏi phạm vi, sau đó tạo một đối tượng mới cho các cuộc gọi đến async_connect. Nếu async_connect không thành công, tôi sử dụng bộ hẹn giờ để ngủ một chút rồi thử lại.

+0

cảm ơn, vì vậy bạn chỉ cần gọi 'socket.close()' khi bạn phát hiện lỗi, và sau đó tạo một lỗi mới? – GrahamS

+0

bộ mô tả được đóng khi đối tượng ip :: tcp :: socket nằm ngoài phạm vi. –

4

Vì lợi ích của sự rõ ràng đây là cách tiếp cận chính thức tôi đã sử dụng (nhưng điều này được dựa trên câu trả lời bjlaub, vì vậy xin vui lòng cho bất kỳ upvotes để anh ta):

tôi tuyên bố là thành viên socket như một scoped_ptr:

boost::scoped_ptr<boost::asio::ip::tcp::socket> socket; 

Sau đó, tôi đổi phương pháp connect của tôi là:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Create new socket (old one is destroyed automatically) 
    socket.reset(new boost::asio::ip::tcp::socket(io_service)); 

    // Attempt connection 
    socket->connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     socket->close(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 
0

tôi đã thử cả hai phương pháp chặt chẽ() và phương pháp tắt máy và họ chỉ là khó khăn đối với tôi. Close() có thể ném một lỗi mà bạn cần để nắm bắt và là cách thô lỗ để làm những gì bạn muốn :) và tắt máy() có vẻ là tốt nhất nhưng trên phần mềm đa luồng, tôi thấy nó có thể được cầu kỳ. Vì vậy, cách tốt nhất là, như Sam nói, để cho nó đi ra khỏi phạm vi. Nếu socket là thành viên của lớp, bạn có thể 1) thiết kế lại để lớp sử dụng đối tượng 'kết nối' để bọc ổ cắm và để nó ra khỏi phạm vi hoặc 2) quấn nó vào con trỏ thông minh và đặt lại con trỏ thông minh. Nếu bạn sử dụng boost, bao gồm shared_ptr là rẻ và hoạt động như một sự quyến rũ. Không bao giờ có một vấn đề ổ cắm làm sạch nó với một shared_ptr. Chỉ là kinh nghiệm của tôi.

+0

Bạn * phải * gọi 'close(),' 'tricky' hay không. Nếu không, bạn có một rò rỉ tài nguyên. Không có gì 'thô lỗ' về nó. Và 'shutdown()' không phải là một thay thế cho 'close()'. – EJP

Các vấn đề liên quan