1. 程式人生 > >Linux網路程式設計之TCP(下)- I/O資料複用

Linux網路程式設計之TCP(下)- I/O資料複用

轉自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);
}

執行結果:

[[email protected] ~]# ./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

[[email protected] ~]# ./c1 127.0.0.1
idd
4 bytes altpogether
catch a exit signal
[[email protected] ~]# ./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模型,最後給出了一個具體的伺服器與客戶端的例項.


相關推薦

Linux網路程式設計TCP()- I/O資料

轉自http://blog.csdn.net/chenjin_zhong/article/details/7255705 1.介紹 網路資料的傳送與接收有多種方式,可以直接直接從套接字讀取資料或向套接字寫入函式,如read/write. 也可以通過向量傳送與接收資料

linux網路程式設計TCP狀態轉換及埠

(1)TCP狀態轉換圖               其中圖中分為三種狀態:實線代表的主動發起連線,虛線代表的被動發起連線,細實線代表的可以雙向發起連線的狀態。 主動發起連線方狀態變化:1)主動發起連線的一方傳送SYN標誌位,進入SYN_SENT狀態,等待接收被髮起連線方

Linux網路程式設計TCP程式設計

直接上程式碼如下所示: 1、標頭檔案定義 #ifndef __HEAD_NET_H__ #define __HEAD_NET_H__ #include <stdio.h> #include <string.h> #include <stdlib.h>

linux網路程式設計TCP介面詳解

socket int socket(int domain, int type, intprotocol);     監聽套接字描述符由socket建立,隨後用作bind和listen的第一個引數。一個伺服器通常僅建立一個監聽套接字,他在該伺服器的生命週期內一直存在。 c

linux網路程式設計TCP/IP基礎(二):利用ARP和ICMP協議解釋ping命令

一、MTU 乙太網和IEEE 802.3對資料幀的長度都有限制,其最大值分別是1500和1492位元組,將這個限制稱作最大傳輸單元(MTU,Maximum Transmission Unit)。如果I

Linux網路程式設計Tcp/Udp socket檔案傳輸示例

本文所述示例程式是基於Linux平臺的socket網路程式設計,實現檔案傳輸功能。該示例是基於TCP流協議實現的socket網路檔案傳輸程式。採用C語言編寫。最終能夠實現傳輸任何格式檔案的檔案傳輸程式。 具體實現程式碼如下: /***********************

Linux網路程式設計I/O迴圈伺服器

原文:http://blog.csdn.net/chenjin_zhong/article/details/7270166 1.介紹 在前幾節,我們介紹了迴圈伺服器,併發伺服器. 簡單的迴圈伺服器每次只能處理一個請求,即處理的請求是序列的。而併發伺服器可以通過建立多

Linux C高階程式設計——網路程式設計TCP(3)

Linux網路程式設計(三)——TCP 宗旨:技術的學習是有限的,分享的精神是無限的。 1、TCP段格式         和UDP協議一樣也有源埠號和目的埠號,通訊的雙方由IP地址和埠號標識。32位序號、32位確認序號、視窗大小。4位首部長度和IP協議頭類似,表示TCP協議頭的長度,以4位元組為單位,

linux網路程式設計socket實現簡單客戶端和服務端的通訊(基於TCP

一、介紹基於TCP協議通過socket實現網路程式設計常用API 1、讀者如果不是很熟悉,可以先看我之前寫的幾篇部落格,有socket,地址結構的理解,更加方便讀者理解 地址分別是: 2、socket(TCP)程式設計API簡介 1)、socket int s

linux網路程式設計select函式實現io(基於TCP)引發的思考

1、基本概念    IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。IO多路複用適用如下場合:   (1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。   (2)當一個客戶同時處理多個套介面時,而這種情況

Linux網路程式設計聊天程式(TCP協議select)

伺服器端:server.c #include <stdio.h> #include <stdlib.h> #include <errno.h> #include &

Linux網路程式設計TCP客戶/伺服器模型及基本socket函式

TCP客戶/伺服器模型 TCP連線的分組交換 在使用socket API的時候應該清楚應用程式和TCP協議棧是如何互動的: 呼叫connect()會發出SYN段(SYN是TCP報文段頭部的一個標誌位,置為1) 阻塞的read()函式返回0就表明收到了FIN段 客戶端呼叫c

linux 網路程式設計廣播

linux 網路程式設計之廣播 轉載:https://blog.csdn.net/qdlovecsj/article/details/8805483 廣播方式主要是指使用UDP套介面傳送資料,傳送資料的目標地址不是普通的地址,而是所指定網路的廣播地址。 什麼是廣播地址?是指IP地

Linux 環境程式設計——淺談標準I/O緩衝區

標準I/O庫提供緩衝的目的是儘可能地減少使用read和write呼叫的次數。它也對每個I/O流自動地進行緩衝管理,從而避免了應用程式需要考慮這一點所帶來的麻煩。不幸的是,標準I/O庫最令人迷惑的也是它的緩衝。   標準I/O提供了三種類型的緩衝: 1、全緩衝: 在填滿標準I

Linux網路程式設計IO模型

本文基於IO訪問中存在的兩個階段詳細介紹了Linux產生的五種IO模型。 上篇文章回顧: 小米開源監控Open-Falcon收錄汽車之家貢獻的Win版Agent 同步與非同步 同步是指一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後

7.4 (java學習筆記)網路程式設計TCP

一、TCP   1.1 TCP(Transmission Control Protocol 傳輸控制協議),是一種面向連線的,安全的傳輸協議,但效率相比於UDP而言比較低。   TCP傳輸時需要確保先建立連線之後,再進行傳輸這樣就保證了傳輸的可靠性。   java中將TCP封裝成了對應的類。     

洞悉C++網路程式設計tcp/ip和socket api

原文地址:https://blog.csdn.net/libaineu2004/article/details/79020403 TCP(Transmission Control Protocol) 傳輸控制協議 三次握手 TCP是主機對主機層的傳輸控制協議,提供可靠的連線服務,採用三次

linux網路程式設計多程序併發伺服器

1)使用多程序併發伺服器考慮的因素:       (1)父程序描述最大檔案描述符的個數(父程序需要關閉accept返回的新檔案描述符)       (2)系統內可建立程序的個數(與記憶體大小相關)       (3)程序建立過多是否降低整體服務效能 2)多程序建立併發

LINUX網路程式設計---實現TCP簡單通訊

#include<stdio.h> #include<errno.h> #include<string.h> #include<strings.h> #include<unistd.h> #include<stdlib.h> #inclu

嵌入式linux網路程式設計TCP、IP協議原理,wireshark抓包工具,乙太網頭(Ethernet header),IP頭,TCP頭,三次握手,四次握手,UDP頭

文章目錄 1,wireshark抓包工具 1.1,wireshark安裝 1.2,wireshark啟動 1.2.1,出現錯誤警告 1.2.2,解決方案 2,常用除錯測試工具 3,TCP