Linux网络编程之TCP(下)- I/O数据复用

来源:互联网 发布:网络电视蚂蚁 编辑:程序博客网 时间:2024/06/05 16:39

原文:http://blog.csdn.net/chenjin_zhong/article/details/7255705


1.介绍



网络数据的发送与接收有多种方式,可以直接直接从套接字读取数据或向套接字写入函数,如read/write. 也可以通过向量发送与接收数据,如readv/writev. 另外还可以通过消息发送与接收数据,如sendmsg/recvmsg. 主要的I/O模型有以下几种:


(1)阻塞I/O-通常情况下,当数据没有到来的时候,会阻塞进程一直等待数据,如recv函数,它是最常用的模型.


(2)非阻塞I/O-当没有数据到达时,程序不会阻塞,而是立即返回,并返回一个错误.


(3)I/O复用-阻塞I/O会一直阻塞程序,而I/O复用是等待一定的时间,如果等待的时间到,那么进程就会返回. 如select函数会按照一定的时间轮循环.


(4)信号驱动I/O模型-进程首先注册一个信号处理回调函数,然后继续执行,当有数据到来时,通过发送信号告诉进程,然后处理数据,


(5)异步I/O模型-与信号驱动I/O不同的,异步I/O在内核完成数据复制之后才送信号通知进程。
对于网络数据的处理,通常情况下采用阻塞I/O模型和I/O复用模型.


2. 相关函数


(1)字节序的转换


字节序分为大端字节序与小端字节序,所谓小端字节序就是低字节部分存放于低地址,高字节部分存放于高地址. 大端字节序正好相反. 在网络数据传输中,涉及到两个字节序,一个是主机字节序,另一个是网络字节序。主机字节序可以是小端字节序也可以是大端字节序,而网络字节序是大端字节序。这样,在发送数据的时候,会进行字节序的相应转换。


#include <arpa/inet.h>


uin32_t htonl(uint32_t hostlong);  //主机字节序转换成网络字节序长整形
uin16_t htons(uint16_t hostshort);//主机字节序转换成网络字节序短整形


uint32_t ntohl(uint32_t netlong);//网络字节序转换成主机字节序长整形


uint16_t ntohs(uint16_t netshort);//网络字节序转换成主机字节序短整形




(2)IP地址的转换


#include <sys/socket.h>


#include <netinet/inet.h>


#include <arpa/inet.h>
in_addr_t inet_addr(const char*cp); //将字符串的IP地址转换为in_addr类型
char*inet_ntoa(struct in_addr in);//将in_addr类型的二进制地址转换成字符串
int inet_pton(int af,const char*src,void*dst);//将字符串类型的IP地址转换成in_addr
参数:


af-协议族
src-表示需要转换的字符串


dst-指向struct in_addr指针


返回值:


返回0表示转换成功,-1表示协议af不支持。


const char*inet_ntop(int af,const void*src,char*dst,socklen_t cnt);//把in_addr类型的二进制地址转换成字符串


参数


af-协议族


src-指针struct in_addr的指针


dst-保存转换字符串的缓冲区


cnt-此缓冲区的长度
返回值:


当发生错误时,返回NULL. 成功返回指向dst的指针。
(3)获得主机信息


#include <netdb.h>


struct hostent *gethostbyname(const char*name);
返回值:


成功返回指向hostent的指针,返回NULL表示发生错误。


错误类型保存在errno中:


HOST_NOT_FOUND:表示查询的主机不存在


NO_ADDRESS和NO_DATA:请求的名称合法但没有合适的IP地址


NO_RECOVERY:域名服务器不响应


TRY_AGAIN:域名服务器出现临时性错误,请重试。


struct hostent{


 char* h_name;//主机的正式名称


char**h_aliases;//别名列表
int h_addrtype;//主机地址类型


int h_length;//地址长度


char**h_addr_list;//地址列表
}
#define h_addr h_addr_list[0]


struct hostent* gethostbyaddr(const void*addr,int len,int type);//通过地址来查看主机的信息


参数:


addr-struct in_addr指针,表示主机的IP地址


len-所指地址的长度,即sizeof(struct in_addr)
type-表示协议族
返回值:


成功返回hostent指针,错误返回NULL,同上.
(4)协议名称处理函数


struct  protent *getprotobyname(const char*name);//通过协议名获得相关的协议项


struct protent*getprotobynumber(int proto);//通过协议值获得相关协议项


void setprotoent(int stayopen);//设置协议文件的打开状态,打开/etc/protocols文件
struct protoent{


char* p_name; //协议的官方名称
char**p_aliases;//协议的别名


int p_proto;//协议的值
}
(5) recv与send函数


#include <sys/types.h>


#include <sys/socket.h>


ssize_t recv(int s,void*buf,size_t len,int flags);
函数recv表示从套接字s接收数据放到数据缓冲区buf中,


参数:


s-套接字描述符


buf-接收缓冲区指针


len-接收缓冲区的长度(缓冲区的size,如1024)


flags-表示接收数据的方式,通常为0


flags:


MSG_DONTWAIT-非阻塞操作,立刻返回


MSG_ERRQUEUE-错误消息从套接字错误队列接收
MSG_OOB-接收带外数据


MSG_PEEK-查看数据,不进行数据缓冲区的清空


MSG_TRUNC-返回所有数据,即使指定缓冲区过小


MSG_WAITALL-等待所有消息


返回值:


成功返回接收的字节数,错误返回-1.查看errno可获得错误信息.
ssize_t send(int s,const void*buf,size_t len,int flags);
将缓冲区buf中大小为len的数据发送出去.


参数:


s-发送数据的套接字描述符


buf-发送缓冲区指针


len-要发送的数据长度(即使缓冲区大小为1024,只有10个字节数据要发送,那么len=10)
flags-发送数据的方式


返回值:


成功返回发送数据的字节数,发送错误返回为-1.


(6)readv与writev函数


这两函数可以使用向量缓冲区进行数据的发送与接收,即多个缓冲区.


#include <sys/uio.h>


ssize_t readv(int fd,const struct iovec* vector,int count);
struct ivoec{


 void* iov_base; //向量缓冲区的地址
size_t iov_len;//向量缓冲区的大小
}
参数:


fd-套接字描述符


vector-缓冲区指针


count-vector的个数


struct iovec就是一个缓冲区,可以接收count个缓冲区的数据


返回值:


成功返回表示接收到的字节数,失败返回-1,返回值保存在errno




#include <sys/uio.h>


ssize_t writev(int fd,const struct iovec*vector,count);
参数:


fd-套接字描述符


vector-向量缓冲区指针


count-缓冲区的个数


返回值:


成功返回表示发送的字节数,失败返回-1


(6) recvmsg函数与sendmsg函数


#include <sys/types.h>


#include <sys/socket.h>


ssize_t recvmsg(int s,struct msghdr*msg,int flags);
从套接字s中接收数据放入缓冲区msg中。


参数:


s-套接字描述符


msg-struct msghdr结构体指针


flags-接收标志信息同send,recv标志.


struct msghdr{


 void *msg_name; //可选地址


socklen_t msg_namelen;//可选地址的长度


struct iovec*msg_iov;//接收数据的缓冲区
size_t msg_iovlen;//msg_iov的数量


void *msg_control;//控制 缓冲区,根据msg_flags放入不同的值


socklen_t msg_controllen;//控制缓冲区的长度


int msg_flags;//接收消息的标志
}
返回值:


成功返回接收的字节数,失败返回-1.




ssize_t sendmsg(int s,const struct msghdr*msg,int flags);
向msg中写入数据并发送.


参数:


s-套接字描述符


msg-struct msghdr结构体指针


flags-发送数据的标志


返回值:


成功返回发送的字节数,失败返回-1.




(7) I/O复用函数


#include <sys/select.h>


#include <sys/time.h>


#include <unistd.h>


int select(int nfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,struct timeval *timeout);
select函数对文件描述符进行查询,查看目标是否可以进行读,写或者是错误操作,直到条件满足时才进行真正的I/O操作.


参数:


nfds-所有文件描述符最大值加1


readfds-读文件描述符集合,这个文件描述符集合监视任何可读文件,当select返回时,readfds将清除不可读的文件描述符,只留下可读的文件描述符


writefds-写文件描述符集合,这个文件描述符集合监视任何可写文件,当select返回进,writefds将清除不可写的文件描述符,只留下可写的文件描述符


excepts-异常文件描述符集合,这个文件描述符集合监视是否有错误发生. 可用于其它用途,如监视带外数据OOB,select返回进将清除其它文件描述符,只留下带外数据。


timeout-等待的最长时间,超过此时间,select函数返回。如果NULL,表示阻塞操作一直等待. timeout的值为0,select立即返回


struct timeval{


time_t tv_sec;//秒


long tv_usec;//微秒
}
4个宏:


FD_ZERO-清理文件描述符集合
FD_SET-将某个文件描述符集合加入文件描述符
FD_CLR-将某个文件描述符从文件描述符集合中移除


FD_ISSET-测试文件描述符是否在文件描述符集合中



函数小结:


read/write,readv/writev可用于任何文件描述符


recv/send,recvmsg/sendmsg只用于套接字描述符


readv/writev,recvmsg/sendmsg可发送多个缓冲区数据


read/write,recv/send只能发送一个缓冲区数据


recv/send,recvmsg/sendmsg具有可选标志flags.
recvmsg/sendmsg具有控制信息。


3.例子


服务器:


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <linux/in.h>
#include <signal.h>
#define PORT 8888
#define BACKLOG 2
void process(int sc);//send与recv函数,阻塞操作与非阻塞操作
void processv(int s);//readv,writev向量操作
void processmsg(int s);//recvmsg,sendmsg将向量挂载到消息结构上
int main(int argc,char* argv[]){
 int ss,sc;//ss为服务器端套接字描述符,sc为客户端套接字描述符
 struct sockaddr_in server_addr;//服务器地址结构
 struct sockaddr_in client_addr;//客户端地址结构
 int err;
 pid_t pid;
 //建立套接字描述符
 ss=socket(AF_INET,SOCK_STREAM,0);//第一个参数是域,使用IPv4协议,第二个参数是通信类型,第三个参数表示某个具体的协议,如果只有一个为0,建立一个流式套接字
 if(ss<0){
    perror("socket error");
    return -1;
}
//将端口地址结构绑定到套接字描述符
 bzero(&server_addr,sizeof(server_addr));//清0
 server_addr.sin_family=AF_INET;//协议族
 server_addr.sin_port=htons(PORT);//htons主机字节序转换为网络字节序,端口号
 server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//IP地址主机字节序长整形转换为网络字节序
 err=bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));//将地址结构绑定到套接字描述符
 if(err<0){
   perror("bind error");
    return -1;
}
//设定侦听队列的长度
err=listen(ss,BACKLOG);
if(err<0){
    perror("listen error");
    return -1;
}


//等待客户端连接服务器
for(;;){
    int addrlen=sizeof(struct sockaddr);
    sc=accept(ss,(struct sockaddr*)&client_addr,&addrlen);//客户端的信息保存在地址结构client_addr中,新返回的描述符sc用于客户端与服务器端发送与接收消息
    if(sc<0){
        continue;
    }


    pid=fork();
    if(pid==0){//在子进程中处理消息 
        close(ss);//关闭服务器端描述符
        processmsg(sc);//使用的是recvmsg与sendmsg函数


    }else {
        close(sc);//在父进程关闭客户端连接
    }








}


}
//使用send与recv来发送和接收数据
void process(int s){
//使用recv,send函数
/**
recv函数用于接收数据,send函数用于发送数据。
ssize_t recv(int s,void*buf,size_t len,int flags);从套接字s中接收数据放到长度为len的buf中去,flags设置接收数据的方式
返回值:成功接收到的字节数,返回-1表示错误发生,可查看错误码
flags: MSG_DONTWAIT 非阻塞方式立刻返回,不等待
       MSG_WAITALL 等待所有消息


当从内核缓冲区接收到的数据比指定的缓冲区小时,会将所有的数据复制到用户缓冲区
当从内核缓冲区接收到的数据比用户缓冲区大时,会将用户指定的长度len的数据复制到用户缓冲区,其余的数据下次再进行复制,复制完用户指定的数据之后,会销毁已经复制的数据


ssize_t send(int s,void*buf,size_t len,int flags);
返回值: 成功返回已经发送的字节数,错误返回值为-1,错误码在error中
如果发送的数据小于len时,剩余的数据会重新发送。
**/


//MSG_DONTWAIT没有数据直接返回
//MSG_WAITALL等待长度为len的数据返回,如果一方正常关闭,则这个函数返回0
ssize_t size=0;
char buffer[1024];
char sa[1024];
for(;;){
 size=recv(s,buffer,10,MSG_WAITALL);//从套接字读取数据放到缓冲区buffer,MSG_DONTWAIT即使没有收到数据也立即返回, 没有读到10字节数不会返回
 printf("size=%d\n",size);//当另一方用正常方式关闭时返回值为0,如close
 if(size==0){
    return;
    }


if(size>0){
write(1,buffer,size);
sprintf(buffer,"%d bytes receive",size);
//向客户端发送数据
send(s,buffer,strlen(buffer)+1,0);
}
}
}


//使用readv,writev向量缓冲区来接收和发送数据
void processv(int s){
/**
用readv,writev向量来完成数据的接收与响应
ssize_t readv(int s,const struct iovec *v, int count);
readv函数可用来接收多个缓冲区数据
返回值: 成功读取的字节数,当返回-1值表示发生错误,errno
第一个参数表示套接字描述符
iovect是一个向量指针
struct iovec{
 void *iov_base 向量缓冲区的地址
 size_t iov_len 向量缓冲区的长度


}


第三个参数表示iovec的个数,即缓冲区的个数


ssize_t writev(int fd,const struct iovec* v,int count);
第一个是套接字描述符
struct iovec{
 void *iov_base
 size_t iov_len


}
第三个表示缓冲区的个数,即struct iovec*的个数


**/
//使用3个向量缓冲区
char buffer[30];//向量缓冲区
ssize_t size=0;
struct iovec* v=(struct iovec*)malloc(3*sizeof(struct iovec));//申请3个向量
if(!v){
 perror("not enough memory");


}
//每一个缓冲区占10个字节
v[0].iov_base=buffer;
v[1].iov_base=buffer+10;
v[2].iov_base=buffer+20;
v[0].iov_len=v[1].iov_len=v[2].iov_len=10;//表示接收缓冲区的大小
bzero(v[0].iov_base,10);
bzero(v[1].iov_base,10);
bzero(v[2].iov_base,10);
for(;;){
  size=readv(s,v,3);//先从客户端读入消息,放到3个向量缓冲区
 printf("size=%d\n",size);
 printf("v[0].iov_len=%d\n",strlen(v[0].iov_base));
 write(1,v[0].iov_base,strlen(v[0].iov_base));
 printf("v[1].iov_len=%d\n",strlen(v[1].iov_base));
 write(1,v[1].iov_base,strlen(v[1].iov_base));
 printf("v[2].iov_len=%d\n",strlen(v[2].iov_base));
 write(1,v[2].iov_base,strlen(v[2].iov_base));
  if(size==0){
    return;
}


//响应客户端
sprintf(v[0].iov_base,"%d ",size);
sprintf(v[1].iov_base,"bytes alt");
sprintf(v[2].iov_base,"ogether\n");
v[0].iov_len=strlen(v[0].iov_base);//发送数据时,表示发送数据的实际长度
v[1].iov_len=strlen(v[1].iov_base);
v[2].iov_len=strlen(v[2].iov_base);
writev(s,v,3);//发送给客户端


}


}


//使用recvmsg,sendmsg将向量缓冲区挂载到消息上
void processmsg(int s){
/**
ssize_t recvmsg(int s,struct msghdr *msg,int flags);
从套接字s中接收数据放到缓冲区msg中,将向量挂载到消息结构msg_iov上。
返回值:函数返回成功接收到的字节数,-1时表示发生错误,错误码在errno中.
第一个参数是套接字描述符,第二个参数是struct msghdr结构体,第三个参数是一些标志,如MSG_DONTWAIT,MSG_WAITALL.
struct msghdr {
 void *msg_name; //可选地址,为strcut sockaddr指针
 socketlen_t msg_namelen;//msg_name的长度
 struct iovec* msg_iov //接收数据的向量缓冲区
 size_t msg_iovlen;//msg_iov中的元素个数
 void *msg_control;//msg_control指向缓冲区,根据msg_flags放入不同的值,控制消息
 socklen_t msg_controllen;//为msg_control指向缓冲区的大小
 int msg_flags;//接收消息的标志
}


ssize_t sendmsg(int s,const struct msghdr*msg,int flags);
向套接字描述符s中写入数据
**/


char buffer[30];
ssize_t size=0;
struct msghdr msg;//消息结构
struct iovec* v=(struct iovec*)malloc(3*sizeof(struct iovec));
if(!v){
 perror("memory error");
 return ;
}
v[0].iov_base=buffer;
v[1].iov_base=buffer+10;
v[2].iov_base=buffer+20;
v[0].iov_len=v[1].iov_len=v[2].iov_len=10;
bzero(v[0].iov_base,10);
bzero(v[1].iov_base,10);
bzero(v[2].iov_base,10);
//初始化消息结构
msg.msg_name=NULL;
msg.msg_namelen=0;
msg.msg_control=NULL;//没有控制域
msg.msg_controllen=0;
msg.msg_iov=v;//挂载向量指针
msg.msg_iovlen=3;//表示使用的向量缓冲区的个数
msg.msg_flags=0;//无特殊操作
int i=0;
fd_set fdset;//套接字文件描述符集合
FD_ZERO(&fdset);
FD_SET(s,&fdset);//将套接字文件描述符加入到文件描述符集合中去
int ret;
struct timeval tv;
for(;;){
tv.tv_sec=5;//设置超时时间
tv.tv_usec=0;
FD_ZERO(&fdset);
FD_SET(s,&fdset);//需要重新把套接字描述符s加入到文件描述符集合中去
ret=select(s+1,&fdset,NULL,NULL,&tv);//监视套接字描述符是否有数据可读
if(ret==-1){
 perror("select");
 return;
}
else if(ret==0){//如果超时,那么fdset会清除文件描述符集合中的文件描述符
 printf("no data is available within five seconds.\n");
 if(!FD_ISSET(s,&fdset)){
   printf("s is not in fdset\n");
}
 continue;//return 在5秒之内如果没有数据到来,那到就直接返回到accept等待下一个客户端
}
else
 printf("data is available\n");
 if(FD_ISSET(s,&fdset)){//如果有满足条件的文件描述符,那么文件描述符集合仍然保留原来的文件描述符,即集合中保留满足条件的文件描述符
   printf("s is in fdset\n");
}
v[0].iov_len=10;
size=recvmsg(s,&msg,0);//从套接字中读取数据到缓冲区msg,当按下CTRL+C关闭时返回的长度为0
printf("size=%d\n",size);
 if(size==0){
    return;
 }
 
//显示收到客户端信息
 write(1,v[0].iov_base,size);//打印第一个缓冲区内的数据,size是接收到的缓冲区数据长度 
 sprintf(v[0].iov_base,"%d ",size);
 sprintf(v[1].iov_base,"bytes alt");
 sprintf(v[2].iov_base,"pogether\n");
 v[0].iov_len=strlen(v[0].iov_base);//表示第一个缓冲区发送的数据长度,第一个缓冲区发送多少数据
 v[1].iov_len=strlen(v[1].iov_base);//表示第二个缓冲区发送数据的长度  第二个缓冲区发送多少数据
 v[2].iov_len=strlen(v[2].iov_base);//表示第三个缓冲区发送数据的长度  第三个缓冲区发送多少数据
 sendmsg(s,&msg,0);//向客户端发送消息 
}


}




客户端:


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#define PORT 8888
void process(int s);
void processv(int s);
void processmsg(int s);
static int s;//客户端套接字描述符
void sig_process(int signo);//客户端信号处理函数
int main(int argc,char*argv[]){
//客户端首先建立一个套接字描述符


struct sockaddr_in server_addr;//地址结构用于连接服务器
if(argc==1){
  printf("input server addr\n");


}
signal(SIGINT,sig_process);
s=socket(AF_INET,SOCK_STREAM,0);
if(s<0){
    perror("socket error");
    return -1;
}
//设置地址结构
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//主机字节序转换为网络字节序,INADDR_ANY表示任何的IP地址
inet_pton(AF_INET,argv[1],&server_addr.sin_addr);
//连接服务器
connect(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
processmsg(s);//客户端处理
}


void process(int s){
 ssize_t size=0;
 char buffer[1024];
 for(;;){
 size=read(0,buffer,1024);//从标准输入流读取数据,标准输入采用的是行缓冲,只有输入一行之后才能进行实际的I/O操作,磁盘上文件采用全缓冲,只有当缓冲区满时才进行操作
 if(size>0){
    printf("size=%d\n",size);
    send(s,buffer,size,0);//向服务器端发送数据,如果flags为0,那么send函数与write函数相同
    //size=recv(s,buffer,1024,0);//从服务器端接收到数据
    //write(1,buffer,size);//将数据写到标准输出,即打印到终端
}


}








}




void processv(int s){
 char buffer[30];
 ssize_t size=0;
 struct iovec *v=(struct iovec*)malloc(3*sizeof(struct iovec));
 if(!v){
 printf("not enough memory\n");
 return;
}


v[0].iov_base=buffer;
v[1].iov_base=buffer+10;
v[2].iov_base=buffer+20;
v[0].iov_len=v[1].iov_len=v[2].iov_len=10;
bzero(v[0].iov_base,10);//为防止出现乱码,先清0
bzero(v[1].iov_base,10);
bzero(v[2].iov_base,10);
int i=0;
for(;;){
  size=read(0,v[0].iov_base,10);//从标准输入读入
  if(size>0){
  v[0].iov_len=size;//实际发送数据的长度
write(1,v[0].iov_base,strlen(v[0].iov_base));
printf("%d\n",strlen(v[0].iov_base));
write(1,v[1].iov_base,strlen(v[1].iov_base));
printf("%d\n",strlen(v[1].iov_base));
write(1,v[2].iov_base,strlen(v[2].iov_base));
printf("%d\n",strlen(v[2].iov_base));
  writev(s,v,1);//发送给服务器,小于10个字节放在v[0]发送
 v[0].iov_len=v[1].iov_len=v[2].iov_len=10;
 size=readv(s,v,3);//从服务器读取数据
 printf("size=%d\n",size);
   for(i=0;i<3;i++){
      if(v[i].iov_len>0){
         printf("v[%d].len=%d\n",i,v[i].iov_len);//3段内存是连接在一起的,前10个是v[0],次之为v[1],最后为v[2]
          write(1,v[i].iov_base,10);//标准输出
      }
  }
 




}




}




}




void processmsg(int s){
  char buffer[30];//用buffer初始化3个缓冲区
  ssize_t size=0;
  struct msghdr msg;//消息结构体
  struct iovec *v=(struct iovec*)malloc(3*sizeof(struct iovec));
  if(!v){
    perror("memory error");


}
v[0].iov_base=buffer;
v[1].iov_base=buffer+10;
v[2].iov_base=buffer+20;
v[0].iov_len=v[1].iov_len=v[2].iov_len=10;
bzero(v[0].iov_base,10);
bzero(v[1].iov_base,10);
bzero(v[2].iov_base,10);


//初始化消息结构
msg.msg_name=NULL;
msg.msg_namelen=0;
msg.msg_control=NULL;
msg.msg_controllen=0;
msg.msg_iov=v;
msg.msg_iovlen=3;//表示向量缓冲区的个数
msg.msg_flags=0;
int i=0;
for(;;){
  size=read(0,v[0].iov_base,10);
  //printf("size=%d\n",size);
  if(size>0){
    //如果iov_len=10即使缓冲区没有数据也被发送,此时接收到数据长度为10,但是并没有数据
    v[0].iov_len=size;//只发送一个缓冲区的数据 
    v[1].iov_len=0;//缓冲区长度清0,表示不发送此缓冲区数据
    v[2].iov_len=0;//缓冲区长度清0,表示不发送此缓冲区数据
    int sd=sendmsg(s,&msg,0);//向服务器发送消息
    //printf("sd=%d\n",sd);
    v[0].iov_len=v[1].iov_len=v[2].iov_len=10;
     size=recvmsg(s,&msg,0);//从服务器接收到消息
     //printf("size=%d\n",size);
     for(i=0;i<3;i++){//打印出接收的消息
         if(v[i].iov_len>0){
             write(1,v[i].iov_base,v[i].iov_len);
         }
 
     }
}




}


}


void sig_process(int signo){
 printf("catch a exit signal\n");
 close(s);
 exit(0);
}




运行结果:


[root@localhost ~]# ./s1
data is available
s is in fdset
size=6
hello
data is available
s is in fdset
size=4
any
no data is available within five seconds.
s is not in fdset
no data is available within five seconds.
s is not in fdset
data is available
s is in fdset
size=0


[root@localhost ~]# ./c1 127.0.0.1
idd
4 bytes altpogether
catch a exit signal
[root@localhost ~]# ./c1 127.0.0.1
hello
6 bytes altpogether
any
4 bytes altpogether
catch a exit signal


最后打印出的size=0是按下CTRL+C
注意:


(1)如果是发送数据,长度必须是要发送数据的实际长度.而接收数据使用缓冲区的大小作为长度即可.


(2)例子使用一个字符数组初始化向量缓冲区.




总结:


文章主要介绍了TCP通过套接字发送与接收数据的一些基本函数,另外介绍了几种I/O模型,最后给出了一个具体的服务器与客户端的实例.
0 0
原创粉丝点击