17 July 2014

##Linux下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);
}

##recv/recvfrom 函数详解

###功能描述

从套接字上接收一个消息。对于recvfrom ,可同时应用于面向连接的和无连接的套接字。 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);

###参数

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实际长度的内存单元。

###返回说明

成功执行时,返回接收到的字节数。另一端已关闭则返回0。

失败返回-1,errno被设为以下的某个值

  • EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
  • EBADF:sock不是有效的描述词
  • ECONNREFUSE:远程主机阻绝网络连接
  • EFAULT:内存空间访问出错
  • EINTR:操作被信号中断
  • EINVAL:参数无效
  • ENOMEM:内存不足
  • ENOTCONN:与面向连接关联的套接字尚未被连接上
  • ENOTSOCK:sock索引的不是套接字