socket中read、write、send、recv函数的比较

What is the difference between read() and recv()? 
From Andrew Gierth (andrew@erlenstar.demon.co.uk): 

read() is equivalent to recv() with a flags parameter of 0. Other values for the flags parameter change the behaviour of recv(). Similarly, write() is equivalent to send() with flags == 0. 

It is unlikely that send()/recv() would be dropped; perhaps someone with a copy of the POSIX drafts for socket calls can check... 

Portability note: non-unix systems may not allow read()/write() on sockets, but recv()/send() are usually ok. This is true on Windows and OS/2, for example.

1、recv和send
  recv和send函数提供了和read和write差不多的功能.但是他们提供了第四个参数来控制读写操作。
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)
前面的三个参数和read,write相同,第四个参数能够是0或是以下的组合
_______________________________________________________________
 MSG_DONTROUTE:不查找路由表
 MSG_OOB:接受或发送带外数据
 MSG_PEEK:查看数据,并不从系统缓冲区移走数据
 MSG_WAITALL :等待任何数据
————————————————————–
MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面
MSG_OOB:表示能够接收和发送带外的数据.关于带外数据我们以后会解释的.
MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志。
MSG_WAITALL:是recv函数的使用标志,表示等到任何的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足,或是发生了错误。 1)当读到了指定的字节时,函数正常返回,返回值等于len 2)当读到了文档的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且配置错误为相应的错误号(errno)
  假如flags为0,则和read,write相同的操作,更有其他的几个选项,但是我们实际上用的很少,能够查看 Linux Programmer’s Manual得到周详解释。

建立好了TCP连接之后,我们就可以把得到的套接字当做文件描述符来使用,由此,想到了网络程序里面的基本的读写函数read和write函数。

write函数

    ssize_t write(int fd,const void *buf,size_t nbytes);

    write函数将buf中的nbytes字节内容写入到文件描述符中,成功返回写的字节数,失败返回-1.并设置errno变量。在网络程序中,当我们向套接字文件描述符写数据时有两种可能:

    1、write的返回值大于0,表示写了部分数据或者是全部的数据,这样用一个while循环不断的写入数据,但是循环过程中的buf参数和nbytes参数是我们自己来更新的,也就是说,网络编程中写函数是不负责将全部数据写完之后再返回的,说不定中途就返回了!

    2、返回值小于0,此时出错了,需要根据错误类型进行相应的处理。

    如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

read函数

    ssize_t read(int fd,void *buf,size_t nbyte)

    read函数是负责从fd中读取内容,当读取成功时,read返回实际读取到的字节数,如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。

    如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

 

    有了上面的两个函数,我们就可以向客户端或者是服务器端进行数据传输了!比如我要传送一个结构体,可以使用下面的方法:

    客户端向服务器:

    struct student stu;

    write(sock,(void *)&stu,sizeof(struct student));

    服务器读:

    char buffer[sizeof(struct student)];

    struct *my_student;

    read(sock,(void *)buffer,sizeof(struct student));

    my_student=(struct student)buffer;

    在网络上传递数据时,我们一般把数据转换为char类型,接收的时候也是一样的的。没必要在网络上传递指针。


2、recvfrom和sendto
这两个函数一般用在非套接字的网络程序当中(UDP)。


3、recvmsg和sendmsg
recvmsg和sendmsg能够实现前面任何的读写函数的功能。
int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)
struct msghdr
{
  void *msg_name;
  int msg_namelen;
  struct iovec *msg_iov;
  int msg_iovlen;
  void *msg_control;
  int msg_controllen;
  int msg_flags;
}


struct iovec
{
  void *iov_base;
  size_t iov_len;
}
  msg_name 和 msg_namelen当套接字是非面向连接时(UDP),他们存储接收和发送方的地址信息。msg_name实际上是个指向struct sockaddr的指针,msg_name是结构的长度。当套接字是面向连接时,这两个值应设为NULL。 msg_iov和msg_iovlen指出接受和发送的缓冲区内容。msg_iov是个结构指针,msg_iovlen指出这个结构数组的大小。msg_control和msg_controllen这两个变量是用来接收和发送控制数据时的 msg_flags指定接受和发送的操作选项。和recv,send的选项相同


【recv/recvfrom/recvmsg系统调用】  
功能描述: 
从套接字上接收一个消息。对于recvfrom 和 recvmsg,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。
如果消息太大,无法完整存放在所提供的缓冲区,根据不同的套接字,多余的字节会丢弃。
假如套接字上没有消息可以读取,除了套接字已被设置为非阻塞模式,否则接收调用会等待消息的到来。

用法: 
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sock, void *buf, size_t len, int flags);
ssize_t recvfrom(int sock, void *buf, size_t len, int flags, 
     struct sockaddr *from, socklen_t *fromlen);
ssize_t recvmsg(int sock, struct msghdr *msg, int flags);
参数:   
sock:索引将要从其接收数据的套接字。
buf:存放消息接收后的缓冲区。
len:buf所指缓冲区的容量。
flags:是以下一个或者多个标志的组合体,可通过or操作连在一起
MSG_DONTWAIT:操作不会被阻塞。
MSG_ERRQUEUE: 指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来, 使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。 错误以sock_extended_err结构形态被使用,定义如下
#define SO_EE_ORIGIN_NONE    0
#define SO_EE_ORIGIN_LOCAL   1
#define SO_EE_ORIGIN_ICMP    2
#define SO_EE_ORIGIN_ICMP6   3
struct sock_extended_err
{
    u_int32_t ee_errno;   /* error number */
    u_int8_t ee_origin; /* where the error originated */
    u_int8_t ee_type;    /* type */
    u_int8_t ee_code;    /* code */
    u_int8_t ee_pad;
    u_int32_t ee_info;    /* additional information */
    u_int32_t ee_data;    /* other data */
    /* More data may follow */
};
MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。 
MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
MSG_EOR:指示记录的结束,返回的数据完成一个记录。
MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
from:指向存放对端地址的区域,如果为NULL,不储存对端地址。
fromlen:作为入口参数,指向存放表示from最大容量的内存单元。作为出口参数,指向存放表示from实际长度的内存单元。
msg:指向存放进入消息头的内存缓冲,结构形态如下
struct msghdr {
    void         *msg_name;       /* optional address */
    socklen_t     msg_namelen;    /* size of address */
    struct iovec *msg_iov;        /* scatter/gather array */
    size_t        msg_iovlen;     /* # elements in msg_iov */
    void         *msg_control;    /* ancillary data, see below */
    socklen_t     msg_controllen; /* ancillary data buffer len */
    int           msg_flags;      /* flags on received message */
};

可能用到的数据结构有
struct cmsghdr {
    socklen_t cmsg_len;     /* data byte count, including hdr */
    int       cmsg_level;   /* originating protocol */
    int       cmsg_type;    /* protocol-specific type */
    /* followed by
    u_char    cmsg_data[]; */
};

返回说明:   
成功执行时,返回接收到的字节数。另一端已关闭则返回0。失败返回-1,errno被设为以下的某个值   
EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
EBADF:sock不是有效的描述词
ECONNREFUSE:远程主机阻绝网络连接
EFAULT:内存空间访问出错
EINTR:操作被信号中断
EINVAL:参数无效
ENOMEM:内存不足
ENOTCONN:与面向连接关联的套接字尚未被连接上
ENOTSOCK:sock索引的不是套接字

send、sendto、sendmsg函数分析

功能描述:
发送消息,send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。sendto 和 sendmsg既可用于无连接的套接字,也可用于基于连接的套接字。除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。

用法:
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sock, const void *buf, size_t len, int flags);
ssize_t sendto(int sock, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
ssize_t sendmsg(int sock, const struct msghdr *msg, int flags);

参数:  
 

sock:索引将要从其发送数据的套接字。
buf:指向将要发送数据的缓冲区。
len:以上缓冲区的长度。
flags:是以下零个或者多个标志的组合体,可通过or操作连在一起

MSG_DONTROUTE:不要使用网关来发送封包,只发送到直接联网的主机。这个标志主要用于诊断或者路由程序。
MSG_DONTWAIT:操作不会被阻塞。
MSG_EOR:终止一个记录。
MSG_MORE:调用者有更多的数据需要发送。
MSG_NOSIGNAL:当另一端终止连接时,请求在基于流的错误套接字上不要发送SIGPIPE信号。
MSG_OOB:发送out-of-band数据(需要优先处理的数据),同时现行协议必须支持此种操作。

to:指向存放接收端地址的区域,可以为NULL。
tolen:以上内存区的长度,可以为0。
msg:指向存放发送消息头的内存缓冲,结构形态如下

struct msghdr {
    void         *msg_name;     


 

    socklen_t     msg_namelen;   
    struct iovec *msg_iov;       
    size_t        msg_iovlen;    
    void         *msg_control;   
    socklen_t     msg_controllen;
    int           msg_flags;     
};


可能用到的数据结构有

struct cmsghdr {
    socklen_t cmsg_len;    
    int       cmsg_level;  
    int       cmsg_type;   
   
};
返回说明:  
成功执行时,返回已发送的字节数。失败返回-1,errno被设为以下的某个值  
EACCES:对于Unix域套接字,不允许对目标套接字文件进行写,或者路径前驱的一个目录节点不可搜索
EAGAIN,EWOULDBLOCK: 套接字已标记为非阻塞,而发送操作被阻塞
EBADF:sock不是有效的描述词
ECONNRESET:连接被用户重置
EDESTADDRREQ:套接字不处于连接模式,没有指定对端地址
EFAULT:内存空间访问出错
EINTR:操作被信号中断
EINVAL:参数无效
EISCONN:基于连接的套接字已被连接上,同时指定接收对象
EMSGSIZE:消息太大
ENOMEM:内存不足
ENOTCONN:套接字尚未连接,目标没有给出
ENOTSOCK:sock索引的不是套接字
EPIPE:本地连接已关闭

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
void Recv()
{
   struct sockaddr_in serv_addr;
   int sock_fd;
   char line[15] = "Hello world!";
   int size = 13;
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   serv_addr.sin_port = htons(5000);
   sock_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   connect(sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
   send(sock_fd, line, size, 0);
   close(sock_fd);
}


#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
void Sendto()
{
   sockaddr_in receiver_addr;
   int sock_fd;
   char line[15] = "Hello World!";
   sock_fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
   receiver_addr.sin_family = AF_INET;
   receiver_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   receiver_addr.sin_port = htons(5000);
   sendto(sock_fd, line, 13, 0,(struct sockaddr*)&receiver_addr,sizeof(receiver_addr));
   close(sock_fd);
}


#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
void sendmsg()
{
   struct sockaddr_in receiver_addr;
   int sock_fd;
   char line[15] = "Hello World!";
   struct msghdr msg;
   struct iovec iov;
   sock_fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 
   receiver_addr.sin_family = AF_INET;
   receiver_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   receiver_addr.sin_port = htons(5000);
   msg.msg_name = &receiver_addr;
   msg.msg_namelen = sizeof(receiver_addr);
   msg.msg_iov = &iov;
   msg.msg_iovlen = 1;
   msg.msg_iov->iov_base = line;
   msg.msg_iov->iov_len = 13;
   msg.msg_control = 0;
   msg.msg_controllen = 0;
   msg.msg_flags = 0;
   sendmsg(sock_fd,&msg,0);
   close(sock_fd);
}


4、套接字的关闭
关闭套接字有两个函数close和shutdown.用close时和我们关闭文档相同。


5、shutdown
int shutdown(int sockfd,int howto)
TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们能够使用shutdown.针对不同的howto,系统回采取不同的关闭方式.
howto=0这个时候系统会关闭读通道.但是能够继续往接字描述符写.
howto=1关闭写通道,和上面相反,着时候就只能够读了.
howto=2关闭读写通道,和close相同在多进程程式里面,假如有几个子进程共享一个套接字时,假如我们使用shutdown, 那么任何的子进程都不能够操作了,这个时候我们只能够使用close来关闭子进程的套接字描述符.


原文见:http://genime.blog.163.com/blog/static/16715775320121010114812136/

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值