Thoroughly learn to use epoll (E): Notes in ET mode

Editor: wind over the summer ChinaUnix blog

Write mode in 5.1 ET

    After analysis of the previous sections, we can know, when the notification epoll work in the next ET mode, for reading, do not read if a read data buffer in, then the next time will not be ready for reading, resulting buffer has been some data have no chance to read, unless new data arrives again. For write operations, it is a major problem because in ET mode fd usually caused by non-blocking - how to ensure that user requirements will write the data written.

To solve the problem of literacy in both ET mode, we must achieve:

. A read for as long as there is data in the buffer has been read;

b. For writing, as long as there is space buffer and write data requested by the user has not yet finished, has been written.

To achieve the above a, b two effects, we have two solutions.

A method l

(1) into each read operation (read, recv), the user is actively epoll_mod IN event, time as long as there are data of the buffer fd can be read, the read returns epoll_wait ready.

(2) Each output operation (write, send), the user is actively epoll_mod OUT event, as long as the case of the fd buffer may transmit data (transmission buffer less than), then the write-ready epoll_wait returns (sometimes using this mechanism notice epoll_wai wake up).

The principle of this method is that we talked about before: When the buffer has data readable (ie buffer is not empty) and the corresponding user fd be epoll_mod IN event ET mode returns ready for reading, when there may write buffer space (ie, buffer dissatisfaction ) returns the user to write and be ready when the corresponding event epoll_mod OUT fd.

So we get the following solution:

if (events [i] .events & EPOLLIN) // if data is received, reads the

{

    cout << "EPOLLIN" << endl;

    sockfd = events[i].data.fd;

    if ( (n = read(sockfd, line, MAXLINE))>0) 

{

line[n] = '/0';

        cout << "read " << line << endl;

if(n==MAXLINE)

{

ev.data.fd=sockfd;

ev.events=EPOLLIN|EPOLLET;

epoll_ctl (epfd, EPOLL_CTL_MOD, sockfd, & ev); // data is not read, re-MOD IN event

}

else

{

ev.data.fd=sockfd;

ev.events=EPOLLIN|EPOLLET;

epoll_ctl (epfd, EPOLL_CTL_MOD, sockfd, & ev); // buffer data has been read in the event MOD OUT

}

}

 else if (n == 0) 

{

close(sockfd);

    }

    

}

else if (events [i] .events & EPOLLOUT) // if the data transmission

{

    sockfd = events[i].data.fd;

    write(sockfd, line, n);

    ev.data.fd = sockfd; // file descriptors for a read operation provided

    ev.events = EPOLLIN | EPOLLET; // read set of events for dispensing probe

    epoll_ctl (epfd, EPOLL_CTL_MOD, sockfd, & ev); // modify the sockfd to process events EPOLIN

}

 

NOTE: For write operations, since the work is in a sockfd blocking mode, there is no need for special treatment, and used as LT.

Analysis: There are several problems with this approach:

(1) For the read operation to determine --if (n == MAXLINE), can not explain this situation there is no buffer will certainly read data buffer in the event of a total imagine there MAXLINE bytes of data it? This continues MOD IN will no longer be notified, but there will be no chance for the corresponding sockfd MOD OUT.

(2) If so the server can use other means of corresponding sockfd MOD OUT, whether this approach can be taken at the appropriate time? First, why should we think about ET mode, because ET mode can reduce epoll_wait system calls, and we are here to be read after each MOD IN, but also after epoll_wait, will inevitably result in reduced efficiency, this is not it just the opposite?

In summary, this method should not be used.

l Method Two

Read: As long as read, has been read, until the returns 0, or errno = EAGAIN 

Write: write as long as it has been written, until the data is sent out, or errno = EAGAIN 

  

if (events[i].events & EPOLLIN) 

  {

  n = 0;

      while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) 

  {

  n += nread;

      }

 if (nread == -1 && errno != EAGAIN) 

  {

 perror("read error");

      }

      ev.data.fd = fd;

      ev.events = events[i].events | EPOLLOUT;

      epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);

  }

  if (events[i].events & EPOLLOUT) 

  { 

  int nwrite, data_size = strlen(buf);

      n = data_size;

      while (n > 0) 

  {

  nwrite = write(fd, buf + data_size - n, n);

          if (nwrite < n) 

  {

             if (nwrite == -1 && errno != EAGAIN) 

 {

 perror("write error");

             }

             break;

           }

          n - = nwrite;

        }

    ev.data.fd=fd; 

    ev.events=EPOLLIN|EPOLLET; 

    epoll_ctl (epfd, EPOLL_CTL_MOD, fd, & ev); // modify the sockfd to process events EPOLIN

 }  

NOTE: in this way a non-blocking mode, since the need to write until the error has been read or written (for reading, when operatively connected to each socket actual number of bytes to read is smaller than the requested number of bytes must make We can stop), and if you file descriptor, if not non-blocking, and that this has been read or write is bound to be a blockage in the final. This can not be blocked on epoll_wait, resulting in other file descriptor task starve.

To sum up: a method is not suitable for use, we only use the second method, so it is often said "ET needs to work in non-blocking mode," Of course, this does not mean that ET does not work in blocking mode, but may work in blocking mode there will be some problems in the operation.

Method Three l

Careful analysis write two methods, we find that this approach is not perfect, because the write operation returns EAGAIN write terminated, but only say the return EAGAIN not write the name of the current buffer is full, does not guarantee that the user (or the server) He asked to write the data has been written. So how do you ensure non-blocking sockets write the number of bytes requested only enough to return it (blocking socket until the number of bytes written request to finish before returning)?

We need to function package socket_write () is used to deal with this situation, the function will try to finish the data back, -1 to indicate an error. In socket_write () inside, when the write buffer is full (Send () return -1 errno to EAGAIN), it will wait and try again.

ssize_t socket_write(int sockfd, const char* buffer, size_t buflen)

{

  ssize_t tmp;

  size_t total = buflen;

  const char* p = buffer;

 

  while(1)

  {

    tmp = write(sockfd, p, total);

    if(tmp < 0)

    {

      // When the send signal is received, you can continue to write, but here return -1.

      if(errno == EINTR)

        return -1;

      // When the socket is non-blocking when this error is returned as for a write buffer queue is full,

      // do here delay and try again.

      if(errno == EAGAIN)

      {

        usleep(1000);

        continue;

      }

      return -1;

    }

    if((size_t)tmp == total)

        return buflen;

     total -= tmp;

     p += tmp;

  }

  return tmp; // returns the number of bytes to write

}

Analysis: This approach is also problematic because, in theory, might be prolonged obstruction () internal (data buffer is not sent, always return EAGAIN) in socket_write, but temporarily there is no better way.

But when you see this way, I would like to change blocking mode should be as feasible in the sockfd socket_write in, and so on and then changed it again before epoll_wait non-blocking.

accept under 5.2 ET mode

    Consider this scenario: multiple connections at the same time arrives, the server's TCP ready queue instantly ready to accumulate more

Connection, because it is edge-triggered mode, epoll only notice once, accept only one connection process, resulting in the ready queue the rest of the TCP connections are not addressed.

     The solution is to use a while loop clinging to accept calls, and then finished with all TCP connections exit the loop in the ready queue. How do you know whether the connection is ready to handle all queues done yet? accept returns -1 with errno set to EAGAIN it means that all connections are processed. 

The correct way to use: 

while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) {   

    handle_client(conn_sock);   

}   

if (conn_sock == -1) {   

     if (errno != EAGAIN && errno != ECONNABORTED    

            && errno != EPROTO && errno != EINTR)    

        perror("accept");   

 

Extended: The service uses a multiplexer technology (select, poll, epoll, etc.), accept to be working in non-blocking mode. 

The reason: If accept work in blocking mode, consider this scenario: TCP connection is aborted clients that accept before (this time select, have already returned connection arrives ready for reading) in the call server, the client sends RST to terminate the connection, resulting in just established connection is removed from the ready queue if the socket is set to blocking mode, the server would have been blocked on accept calls until one of the other customers to build up a new connection. But in the meantime, the server simply blocking (actually should be blocked on select), Ready other descriptor queue are not processed on the accept calls.

    The solution is to listen socket is set to non-blocking, when a customer calls accept before the server abort

When a connection, accept calls can immediately return -1, then realized from Berkeley will handle the event in the kernel, and does not notify the event to the epoll, while others realize the errno set to ECONNABORTED or EPROTO error, we should ignore these errors. (Specific reference UNP v1 p363)

Guess you like

Origin www.cnblogs.com/abelchao/p/11703791.html