socket读写

TCP协议是面向流的,read和write调用的返回值往往小于参数指定的字节数。对于read调用,如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用,如果请求写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节全部交给发送缓冲区才返回(socket阻塞时,write一直阻塞直到所有数据都交给缓冲区)。但如果socket文件描述符中有O_NONBLOCK标志,则write不阻塞,直接返回20。

为避免这些情况干扰主程序逻辑,确保读写所请求的字节数,应包装read和write函数。

当设置socket为非阻塞模式时,要用select()或epoll()判断什么时候可正常写入或读出。

write

sszie_t write(int fd, const void *buf, size_t count);

return:成功,返回写入的字节数;失败-1。

在网络程序中,当我们向socket写时有两种可能:


  1. write的返回值大于0,表示写了部分或者全部的数据。
  2. 返回值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误,应重试。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接,返回-1)(注:EPIPE时,需要设置SIGPIPE信号处理,当采用默认的SIGPIPE信号处理时程序直接退出,write不会返回-1)。
  3. 此外还有可能socket为非阻塞(O_NONBLOCK),当缓冲区满时,立即返回0。(我的想法) ----错误想法,fd设置为非阻塞时,不能写立即返回-1,设置errno为EAGAIN或EWOULDBLOCK(man write ERRORS)。
  4. 返回0时,表示未写数据,继续写即可。

TCP是全双工的信道,可以看作两条单工信道,TCP连接的两个端点各负责一条。当对端调用close,虽然本意是关闭整个两条信道,但本端只是收到FIN包。按照TCP协议的语义,表示对端只是关闭了其所负责的那一条单工信道,仍然可以继续接收数据。也就是说,因为TCP协议的限制,一个端点无法获知对端的socket是调用了close还是shutdown。

但第一次对其调用write方法时,如果 A 发送数据到 B 的缓存没问题,会返回正确写入,但 A 发送的报文会导致B发送RST报文,因为 B 的socket已经调用了close,完全关闭,既不发送,也不接收数据。所以 A 第二次调用write方法(假设在收到 RST 报文之后),会生成 SIGPIPE 信号,导致进程退出。

int my_write(int fd, void *buffer, int len){
  int bytes_left;
  int written_bytes;
  char *ptr;

  ptr = buffer;
  bytes_left = len;

  while(bytes_left > 0){
    written_bytes = write(fd, ptr, bytes_left);
    if(writte_bytes <= 0){ //出错了
      if(errno == EINTR && written_bytes < 0) // 中断错误,继续写。
        writen_bytes = 0;
      else         //其他错误,直接退出
        return -1;
    }
bytes_left -= writen_bytes;
ptr += written_bytes;
}

return len;
}


read

ssize_t read(int fd, void *buf, size_t count);

return:成功,返回读取到的字节数;失败-1。

返回值为0,表示已经读到文件的结束;返回值小于0 表示出现了错误。如果错误为EINTR说明读是由中断引起的(继续读),如果是ECONNREST表示网络连接出了问题。

read()阻塞时,一直等,等于0表示文件结束或网络连接关闭;read()非阻塞,无数据可读时立即返回-1,errno为EAGAIN(若一直读,一直返回-1,errno为EAGAIN),连接关闭时返回0。

int my_read(int fd,void *buffer,int length){
  int bytes_left;
  int bytes_read;
  char *ptr;

  bytes_left=length;
  while(bytes_left>0){
    bytes_read=read(fd,ptr,bytes_left);
    if(bytes_read<0){
      if(errno==EINTR) // 中断引起的,继续读
        bytes_read=0;
      else
        return(-1); //其他原因,直接退出
    }
    else if(bytes_read==0) // 对方close连接(收到FIN包)
      break;

    bytes_left-=bytes_read;
    ptr+=bytes_read;
  }

return(length-bytes_left);
}



  • recvfrom

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr * from, int *fromlen);


  • sendto

int sendto(int sockfd, const void *msg, int len, unsigned int flags, struct sockaddr *to, int tolen);

注:如果对信息的来源不感兴趣,可以将from和fromlen设置为NULL。