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] ~]# ./s1data 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