2013-03-21 12 views
12

Có thể liên kết và nghe nhiều cổng trong Linux trong một ứng dụng không?Nghe nhiều cổng từ một máy chủ

+1

có thể thực hiện được, bạn cần sử dụng ' select' hoặc threads mặc dù – perreal

+4

Có. Câu trả lời hay nhất cho các loại câu hỏi này là để bạn viết một ứng dụng thử nghiệm nhỏ và * tự mình thử *. Khi bạn trở nên giàu kinh nghiệm hơn, bạn sẽ thấy mình thường xuyên viết những "chương trình thử nghiệm" này ít hơn để tìm ra mọi thứ. –

+0

Làm cách nào để có thể chọn? Tôi không chắc chắn làm thế nào để làm nhiều ràng buộc cho một ổ cắm – user2175831

Trả lời

13

Đối với mỗi cổng mà bạn muốn nghe, bạn:

  1. Tạo một ổ cắm riêng biệt với socket.
  2. Kết nối nó với cổng thích hợp với bind.
  3. Gọi listen trên ổ cắm để thiết bị được thiết lập với hàng đợi nghe.

Vào thời điểm đó, chương trình của bạn đang nghe trên nhiều ổ cắm. Để chấp nhận các kết nối trên các ổ cắm này, bạn cần phải biết ổ cắm nào mà máy khách đang kết nối đến. Đó là nơi mà select xuất hiện. Khi nó xảy ra, tôi có mã thực hiện chính xác việc này, vì vậy đây là một ví dụ được kiểm tra đầy đủ về việc chờ kết nối trên nhiều ổ cắm và trả về bộ mô tả tập tin của kết nối. Địa chỉ từ xa được trả lại trong các tham số bổ sung (bộ đệm phải được cung cấp bởi người gọi, giống như chấp nhận).

(socket_type đây là một typedef cho int trên các hệ thống Linux, và INVALID_SOCKET-1. Đó là có vì mã này đã được chuyển sang Windows là tốt.)

socket_type 
network_accept_any(socket_type fds[], unsigned int count, 
        struct sockaddr *addr, socklen_t *addrlen) 
{ 
    fd_set readfds; 
    socket_type maxfd, fd; 
    unsigned int i; 
    int status; 

    FD_ZERO(&readfds); 
    maxfd = -1; 
    for (i = 0; i < count; i++) { 
     FD_SET(fds[i], &readfds); 
     if (fds[i] > maxfd) 
      maxfd = fds[i]; 
    } 
    status = select(maxfd + 1, &readfds, NULL, NULL, NULL); 
    if (status < 0) 
     return INVALID_SOCKET; 
    fd = INVALID_SOCKET; 
    for (i = 0; i < count; i++) 
     if (FD_ISSET(fds[i], &readfds)) { 
      fd = fds[i]; 
      break; 
     } 
    if (fd == INVALID_SOCKET) 
     return INVALID_SOCKET; 
    else 
     return accept(fd, addr, addrlen); 
} 

Mã này không nói lên người gọi cổng kết nối máy khách với khách hàng, nhưng bạn có thể dễ dàng thêm tham số int * sẽ nhận được bộ mô tả tệp nhìn thấy kết nối đến.

+0

Tôi đã tự hỏi nếu điều này có thể xảy ra mà không cần chọn/bình chọn, nhưng có lẽ không có cách nào. – Nick

2

Bạn chỉ bind() vào một ổ cắm, sau đó listen()accept() - ổ cắm cho liên kết là dành cho máy chủ, fd từ accept() là dành cho khách hàng. Bạn làm lựa chọn của bạn trên sau này tìm kiếm bất kỳ ổ cắm khách hàng có dữ liệu đang chờ xử lý trên đầu vào.

+0

btw, bạn đã xóa câu trả lời cho câu hỏi của tôi hoặc đã làm một mod làm điều đó? Loại điên rồ mà họ đã làm nếu như vậy, các ý kiến ​​kèm theo là thông tin hữu ích mà người khác có thể đã sử dụng. –

+0

không ... không phải tôi. Tôi nghĩ đó là một cuộc trò chuyện khá hay. : -/ –

+0

yeah, nó rất hữu ích, thậm chí để biết những gì không làm việc ... mods là điên ở đây; có lẽ bạn có thể chỉnh sửa nó để kết hợp một số cuộc thảo luận của chúng tôi và yêu cầu nó được phục hồi? bạn thậm chí có thể nói rằng chúng tôi đã thử tất cả điều này và làm một số ma thuật là lựa chọn tốt nhất cho tất cả, vì vậy nó sẽ là một câu trả lời hợp pháp mà tôi có thể chấp nhận (kể từ đó có vẻ là câu trả lời thực anyway) với bạn, tôi cũng không thực sự bận tâm. Tôi có lẽ sẽ chỉ viết câu trả lời của riêng mình cho hiệu ứng đó, bởi vì cái được đưa ra cho đến nay vẫn chưa giải quyết được vấn đề của tôi. –

0

Trong trường hợp như vậy, bạn có thể quan tâm đến libevent. Nó sẽ làm công việc của select() cho bạn, có thể sử dụng một giao diện tốt hơn nhiều như epoll().

Hạn chế lớn với select() là việc sử dụng các macro FD_... giới hạn số ổ cắm với số lượng bit tối đa trong biến số fd_set (từ khoảng 100 đến 256). Nếu bạn có một máy chủ nhỏ với 2 hoặc 3 kết nối, bạn sẽ ổn. Nếu bạn định làm việc trên một máy chủ lớn hơn nhiều, thì fd_set có thể dễ dàng bị tràn ngập.

Ngoài ra, việc sử dụng các select() hoặc poll() cho phép bạn để tránh chủ đề trong máy chủ (ví dụ: bạn có thể poll() ổ cắm của bạn và biết liệu bạn có thể accept(), read(), hoặc write() đối với họ.)

Nhưng nếu bạn thực sự muốn làm điều đó Unix giống như, sau đó bạn muốn xem xét fork() -ing trước khi bạn gọi accept(). Trong trường hợp này, bạn không hoàn toàn cần select() hoặc poll() (trừ khi bạn đang nghe trên nhiều IP/cổng và muốn tất cả trẻ em có khả năng trả lời mọi kết nối đến, nhưng bạn có nhược điểm với những kết nối này ...hạt nhân có thể gửi cho bạn một yêu cầu khác trong khi bạn đang xử lý một yêu cầu, trong khi, chỉ với một số accept(), hạt nhân biết rằng bạn đang bận nếu không có trong cuộc gọi accept() - tốt, nó không hoạt động chính xác như vậy, nhưng người dùng, đó là cách nó hoạt động cho bạn.)

Với fork() bạn chuẩn bị ổ cắm trong quy trình chính và sau đó gọi handle_request() trong quá trình con để gọi hàm accept(). Bằng cách đó bạn có thể có bất kỳ số lượng cổng và một hoặc nhiều trẻ em để lắng nghe trên mỗi cổng. Đó là cách tốt nhất để thực sự rất nhanh chóng đáp ứng với bất kỳ kết nối đến dưới Linux (tức là như một người sử dụng và miễn là bạn có tiến trình con đợi cho một khách hàng, đây là tức thời.)

void init_server(int port) 
{ 
    int server_socket = socket(); 
    bind(server_socket, ...port...); 
    listen(server_socket); 
    for(int c = 0; c < 10; ++c) 
    { 
     pid_t child_pid = fork(); 
     if(child_pid == 0) 
     { 
      // here we are in a child 
      handle_request(server_socket); 
     } 
    } 

    // WARNING: this loop cannot be here, since it is blocking... 
    //   you will want to wait and see which child died and 
    //   create a new child for the same `server_socket`... 
    //   but this loop should get you started 
    for(;;) 
    { 
     // wait on children death (you'll need to do things with SIGCHLD too) 
     // and create a new children as they die... 
     wait(...); 
     pid_t child_pid = fork(); 
     if(child_pid == 0) 
     { 
      handle_request(server_socket); 
     } 
    } 
} 

void handle_request(int server_socket) 
{ 
    // here child blocks until a connection arrives on 'server_socket' 
    int client_socket = accept(server_socket, ...); 
    ...handle the request... 
    exit(0); 
} 

int create_servers() 
{ 
    init_server(80); // create a connection on port 80 
    init_server(443); // create a connection on port 443 
} 

Lưu ý rằng handle_request() chức năng được hiển thị ở đây khi xử lý một yêu cầu. Ưu điểm của việc xử lý một yêu cầu duy nhất là bạn có thể thực hiện theo cách Unix: phân bổ tài nguyên theo yêu cầu và khi yêu cầu được trả lời, exit(0). exit(0) sẽ gọi số close(), free(), v.v. cần thiết cho bạn.

Ngược lại, nếu bạn muốn xử lý nhiều yêu cầu liên tiếp, bạn muốn đảm bảo rằng tài nguyên được giải ngân trước khi bạn lặp lại cuộc gọi accept(). Ngoài ra, các chức năng sbrk() là khá nhiều không bao giờ sẽ được gọi là để giảm bớt dấu chân bộ nhớ của con bạn. Điều này có nghĩa là nó sẽ có xu hướng phát triển một chút mỗi bây giờ và sau đó. Đây là lý do tại sao một máy chủ như Apache2 được thiết lập để trả lời một số yêu cầu nhất định cho mỗi đứa trẻ trước khi bắt đầu một đứa trẻ mới (theo mặc định là từ 100 đến 1.000 ngày này.)