2010-03-30 27 views
6

man page gửi() cho thấy cờ MSG_MORE được xác nhận là hành động như TCP_CORK. Tôi có một chức năng bao bọc xung quanh send():Bộ đệm TCP của hạt nhân xả đối với các gói được gắn cờ `MSG_MORE`

int SocketConnection_Write(SocketConnection *this, void *buf, int len) { 
    errno = 0; 

    int sent = send(this->fd, buf, len, MSG_NOSIGNAL); 

    if (errno == EPIPE || errno == ENOTCONN) { 
     throw(exc, &SocketConnection_NotConnectedException); 
    } else if (errno == ECONNRESET) { 
     throw(exc, &SocketConnection_ConnectionResetException); 
    } else if (sent != len) { 
     throw(exc, &SocketConnection_LengthMismatchException); 
    } 

    return sent; 
} 

Giả sử tôi muốn sử dụng bộ đệm hạt nhân, tôi có thể đi với TCP_CORK, cho phép bất cứ khi nào nó là cần thiết và sau đó vô hiệu hóa nó để tuôn ra bộ đệm. Nhưng mặt khác, do đó sự cần thiết cho một cuộc gọi hệ thống bổ sung phát sinh. Do đó, việc sử dụng MSG_MORE có vẻ phù hợp hơn với tôi. Tôi chỉ đơn giản muốn thay đổi dòng gửi ở trên() tới:

int sent = send(this->fd, buf, len, MSG_NOSIGNAL | MSG_MORE); 

Theo lwm.net, gói sẽ được rửa tự động nếu chúng đủ lớn:

Nếu một ứng dụng đặt rằng tùy chọn trên một socket, hạt nhân sẽ không gửi ra các gói tin ngắn . Thay vào đó, nó sẽ đợi cho đến khi đủ dữ liệu hiển thị để điền một gói kích thước tối đa, sau đó gửi nó. Khi TCP_CORK bị tắt, mọi dữ liệu còn lại sẽ xuất hiện trên dây .

Nhưng phần này chỉ đề cập đến TCP_CORK. Bây giờ, cách thích hợp để tuôn ra các gói MSG_MORE là gì?

tôi chỉ có thể nghĩ đến hai khả năng:

  1. Gọi send() với một bộ đệm rỗng và không có MSG_MORE được thiết
  2. Tái áp dụng các tùy chọn TCP_CORK như mô tả trên this trang

Thật không may là toàn bộ chủ đề là rất kém tài liệu và tôi không thể tìm thấy nhiều trên Internet.

Tôi cũng tự hỏi cách kiểm tra xem mọi thứ có hoạt động như mong đợi không? Rõ ràng là chạy máy chủ thông qua strace không phải là một tùy chọn. Vì vậy, cách đơn giản nhất là sử dụng netcat và sau đó nhìn vào đầu ra strace của nó? Hoặc hạt nhân sẽ xử lý lưu lượng truy cập được truyền qua một giao diện loopback khác nhau?

+0

sendfile() giữ lại cờ 'MSG_MORE'. Bộ nhớ cache sau đó được flushed khi sendfile() trả về. – user206268

Trả lời

11

Tôi đã xem xét nguồn nhân và cả hai giả định có vẻ đúng. Mã sau đây là các chất chiết xuất từ ​​net/ipv4/tcp.c (2.6.33.1).

static inline void tcp_push(struct sock *sk, int flags, int mss_now, 
       int nonagle) 
{ 
    struct tcp_sock *tp = tcp_sk(sk); 

    if (tcp_send_head(sk)) { 
     struct sk_buff *skb = tcp_write_queue_tail(sk); 
     if (!(flags & MSG_MORE) || forced_push(tp)) 
      tcp_mark_push(tp, skb); 
     tcp_mark_urg(tp, flags, skb); 
     __tcp_push_pending_frames(sk, mss_now, 
         (flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle); 
    } 
} 

Do đó, nếu lá cờ là không bộ, các khung cấp phát chắc chắn sẽ được rửa. Nhưng điều này là có chỉ trường hợp khi bộ đệm là không có sản phẩm nào:

static ssize_t do_tcp_sendpages(struct sock *sk, struct page **pages, int poffset, 
      size_t psize, int flags) 
{ 
(...) 
    ssize_t copied; 
(...) 
    copied = 0; 

    while (psize > 0) { 
(...) 
     if (forced_push(tp)) { 
      tcp_mark_push(tp, skb); 
      __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH); 
     } else if (skb == tcp_send_head(sk)) 
      tcp_push_one(sk, mss_now); 
     continue; 

wait_for_sndbuf: 
     set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); 
wait_for_memory: 
     if (copied) 
      tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH); 

     if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) 
      goto do_error; 

     mss_now = tcp_send_mss(sk, &size_goal, flags); 
    } 

out: 
    if (copied) 
     tcp_push(sk, flags, mss_now, tp->nonagle); 
    return copied; 

do_error: 
    if (copied) 
     goto out; 
out_err: 
    return sk_stream_error(sk, flags, err); 
} 

cơ thể Các while vòng lặp sẽ không bao giờ được thực thi vì psize không lớn 0. là Sau đó, trong phần out, có một cơ hội khác, tcp_push() được gọi nhưng vì copied vẫn có giá trị mặc định, nó cũng sẽ thất bại.

Vì vậy việc gửi gói có độ dài 0 sẽ không bao giờ dẫn đến tuôn ra.

Lý thuyết tiếp theo là áp dụng lại TCP_CORK. Trước tiên, hãy xem mã:

static int do_tcp_setsockopt(struct sock *sk, int level, 
     int optname, char __user *optval, unsigned int optlen) 
{ 

(...) 

    switch (optname) { 
(...) 

    case TCP_NODELAY: 
     if (val) { 
      /* TCP_NODELAY is weaker than TCP_CORK, so that 
      * this option on corked socket is remembered, but 
      * it is not activated until cork is cleared. 
      * 
      * However, when TCP_NODELAY is set we make 
      * an explicit push, which overrides even TCP_CORK 
      * for currently queued segments. 
      */ 
      tp->nonagle |= TCP_NAGLE_OFF|TCP_NAGLE_PUSH; 
      tcp_push_pending_frames(sk); 
     } else { 
      tp->nonagle &= ~TCP_NAGLE_OFF; 
     } 
     break; 

    case TCP_CORK: 
     /* When set indicates to always queue non-full frames. 
     * Later the user clears this option and we transmit 
     * any pending partial frames in the queue. This is 
     * meant to be used alongside sendfile() to get properly 
     * filled frames when the user (for example) must write 
     * out headers with a write() call first and then use 
     * sendfile to send out the data parts. 
     * 
     * TCP_CORK can be set together with TCP_NODELAY and it is 
     * stronger than TCP_NODELAY. 
     */ 
     if (val) { 
      tp->nonagle |= TCP_NAGLE_CORK; 
     } else { 
      tp->nonagle &= ~TCP_NAGLE_CORK; 
      if (tp->nonagle&TCP_NAGLE_OFF) 
       tp->nonagle |= TCP_NAGLE_PUSH; 
      tcp_push_pending_frames(sk); 
     } 
     break; 
(...) 

Như bạn có thể thấy, có hai cách để xả. Bạn có thể đặt TCP_NODELAY thành 1 hoặc TCP_CORK đến 0. May mắn thay, cả hai sẽ không kiểm tra xem cờ đã được đặt chưa. Do đó, kế hoạch ban đầu của tôi để áp dụng lại cờ TCP_CORK có thể được tối ưu hóa để chỉ vô hiệu hóa nó, ngay cả khi nó hiện không được đặt.

Tôi hy vọng điều này sẽ giúp ai đó có vấn đề tương tự.

+0

Cảm ơn bạn đã nghiên cứu này. Rất hữu ích. – bvanderveen

3

Đó là rất nhiều nghiên cứu ... tất cả tôi có thể cung cấp là bài thực nghiệm này lưu ý:

Gửi một loạt các gói tin với MSG_MORE bộ, tiếp theo là một gói tin mà không MSG_MORE, toàn bộ lô đi ra ngoài. Nó hoạt động một điều trị cho một cái gì đó như thế này:

for (i=0; i<mg_live.length; i++) { 
     // [...] 
     if ((n = pth_send(sock, query, len, MSG_MORE | MSG_NOSIGNAL)) < len) { 
      printf("error writing to socket (sent %i bytes of %i)\n", n, len); 
      exit(1); 
     } 
    } 
    } 

    pth_send(sock, "END\n", 4, MSG_NOSIGNAL); 

Đó là, khi bạn đang gửi đi tất cả các gói cùng một lúc, và có một kết thúc rõ ràng ... và em chỉ sử dụng một ổ cắm.

Nếu bạn đã cố gắng ghi vào ổ cắm khác ở giữa vòng lặp trên, bạn có thể thấy rằng Linux phát hành các gói đã lưu trước đó. Ít nhất đó dường như là rắc rối tôi đang gặp ngay bây giờ. Nhưng nó có thể là một giải pháp dễ dàng cho bạn.

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