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
#include
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来关闭子进程的套接字描述符.