天天看点

read write 替代 send recv

Linux下C语言编程中间夹杂一篇文章吧~~

writer: demonalex
email: demonalex#dark2s.org


前言:环境是:FreeBSD 5.1-RELEASE  &&  Linux 2.6.5-1.358。



BSD Socket的I/O处理调用方式有三种基本的配搭方式,当然,你可以在自己的需要在不违背运作机制的情况
“混合”这些方式来进行I/O处理的。


1)connect()+write()+read()  [适用于TCP]
这是一个“古老”的I/O搭配了,“远在”无名管道的工作部分就已经有详细地记录了,不过在这里偶还是要
讲一下。write()+read()主要出现在管道I/O工作上,无论是有名管道还是无名管道,它们主要都是依靠writ
e()+read()进行基本的数据通信的,这个搭配属于UNIX系统中非常低层的I/O系统调用了,而TCP开始设计时
就被当作是网络上传输管道数据的替代品,当然也就顺理成章地继承这个基本的I/O调用搭配了。下面讲解的
write()+read()调用主要是以BSD Socket下的运用方式为主。关于connect()因为在上一篇文中已作了介绍,
所以这里就不多作说明了。

必须头:
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

关于write()
ssize_t write(int d,const void *buf,size_t nbytes);
调用成功返回成功写入的字节数,调用失败则返回-1。参数1为对象的句柄;参数2是写入的内容;参数3是前
者的大小。

关于read()
ssize_t read(int d,void *buf,size_t nbytes);
正常调用返回成功读入的字节数,当读到句柄对象的底部时返回0,调用失败返回-1。参数1为对象句柄;参
数2是读入容器的地址;参数3是前者的大小。


2)sendto()+recvfrom()  [适用于TCP、UDP,多数用于UDP]
这是一个比较“游动”的I/O方式调用,仅适用于UDP传输(也是UDP的I/O调用的魅力所在),因为UDP本身的
特性(不需要三次握手),所以它需要一种非常灵活的传输模式,有了这个模式,服务端与客户端的模式就
没有一个很笼统的分化了(在范例中我们可以看出服务端与客户端在程序的编排上几乎是一致的,除了服务
端多了一个bind()调用与一个循环结构以外),程序设计者可以按照需求更巧妙地设计自己的程序。

必须头:
#include <sys/types.h>
#include <sys/socket.h>

关于sendto()
ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t
 tolen);
调用成功返回成功写入的字节数,调用失败则返回-1。参数1是套接字句柄;参数2是需要发送的数据;参数3
是前者的大小;参数4是特殊传输标识,其值多为0;参数5是发送目的地的sockaddr结构主机地址的指针值;
参数6是前者的长度。

关于recvfrom()
ssize_t recvfrom(int s,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen
);
正常调用返回成功读入的字节数,调用失败返回-1。参数1是套接字句柄;参数2是成功接收到远程传输过来
后的数据时放入的变量地址;参数3是前者的大小;参数4是特殊传输标识,其值多为0;参数5是接收到的数
据的发送端的sockaddr结构主机地址的指针值;参数6是前者长度的地址值。

/*
以前总以为sendto()+recvfrom()调用只能用于UDP传输,原来这个论点是错的,这里偶做了个小实验...服务
端用我在《BSD Socket在传输层中的应用范例(TCP)》一文中的服务端,而客户端是:
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>

int main(int argc,char *argv[]){
int sock;
struct sockaddr_in addr;
char msg[101];
bzero((char *)msg,101);

if((sock=socket(AF_INET,SOCK_STREAM,0))==-1){  //这里用的是TCP
printf("fail for socket./n");
exit(-1);
}

bzero((char *)&addr,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(argv[1]);
addr.sin_port=htons(atoi(argv[2]));

printf("message:");
scanf("%100s",msg);
if(sendto(sock,msg,sizeof(msg),0,(struct sockaddr *)&addr,sizeof(addr))){
//I/O这里我用了sendto()来代替send()
printf("send is ok./n");
close(sock);
exit(0);
}else{
printf("send is fail./n");
close(sock);
exit(-1);
}
}
上面我用了sendto()来代替send(),并去除开始时的connect()调用(根据UDP的调用模式),结果发现必须
运行两次服务端才能接收msg的数据,可见第一次sendto()代替connect()了,这样才能正常地运作。但如果
在前面一开始就调用connect()的话sendto()是可以完全代替send()的,不过因为sendto()多了两个参数,所
以相信还不会有人会这样调用的,这样的使用方法仅仅用于实验用途。
*/


3)connect()+send()+recv()  [适用于TCP、UDP,多数用于TCP]
因为connect()调用在前一篇文中已经介绍过了,所以我就不多说了,下面主要谈谈send()+recv()。在TCP的
I/O传输中它们可以完全地取代write()+read()(而且在功能上回更优胜一些,看它的最后一个调用参数就知
道了);在UDP则不能完全取代sendto()+recvfrom(),原因显而易见,因为send()+recv()少了“游动”地址
部分,所以如果在UDP中调用它们的话套接字根本就不知道应该发到哪里去,这也是为什么在UDP中要用conne
ct()调用作为绑定了,又因为UDP不存在所谓的“三次握手”(需要发送连接请求),所以在UDP中我们可以
把“绑定”这种行为简单地归结为:bind()调用绑定本地地址,connect()调用绑定远程地址。

必须头:
#include <sys/types.h>
#include <sys/socket.h>

关于send()
ssize_t send(int s, const void *msg, size_t len, int flags);
调用成功返回成功写入的字节数,调用失败则返回-1。参数1是套接字句柄;参数2是需要发送的数据;参数3
是前者的大小;参数4是特殊传输标识,其值多为0。

关于recv()
ssize_t recv(int s, void *buf, size_t len, int flags);
正常调用返回成功读入的字节数,调用失败返回-1。参数1是套接字句柄;参数2是成功接收到远程传输过来
后的数据时放入的变量地址;参数3是前者的大小;参数4是特殊传输标识,其值多为0。      

继续阅读