sckutil.h头文件
#ifndef _SCKUTIL_H_
#define _SCKUTIL_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#define ERR_EXIT(m) \
do{\
perror(m); \
exit(EXIT_FAILURE); \
}while(0);
void activate_nonblock(int fd);
void deactibe_nonblock(int fd);
int read_timeout(int fd,unsigned int wait_seconds);
int write_timeout(int fd,unsigned int wait_seconds);
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds);
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds);
ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,const void* buf,size_t count);
ssize_t recv_peek(int fd,void* buf,size_t len);
ssize_t readline(int fd,void* buf,size_t maxline);
#endif
sckutil.c
//设置I/O为非阻塞模式
void activate_nonblock(int fd){
int ret;
int flags=fcntl(fd,F_GETFL);
if(flags==-1)
ERR_EXIT("fcntl");
flags|=O_NONBLOCK;
ret=fcntl(fd,F_SETFL,flags);
if(ret==-1)
ERR_EXIT("fcntl");
}
//设置I/O为阻塞模式
void deactibe_nonblock(int fd){
int ret;
int flags=fcntl(fd,F_GETFL);
if(flags==-1)
ERR_EXIT("fcntl");
flags&=~O_NONBLOCK;
ret=fcntl(fd,F_SETFL,flags);
if(ret==-1)
ERR_EXIT("fcntl");
}
//-------------------------------
/* read_timeout的使用方法:
int ret;
ret=read_timeout(fd,5);
if(ret==0){ //成功(未超时):真正的调用[read/readn/readline]去读取数据
read(fd,recvbuf,sizeof(recvbuf));
... ...
}
else if(ret==-1&&errno==ETIMEOUT){ //超时
printf("读超时...\n");
continue;
}
else //异常
ERR_EXIT("read_line");
*/
//---------------------------
/*
read_timeout-读超时检测函数,不含读操作
(即:判断[从fd套接字]中读数据,是否超时,不真正的读走数据)
@fd:文件描述符
@wait_seconds:等待超时秒数,如果为0表示不检测超时
成功(未超时):返回0
失败:返回-1
超时:返回-1并且errno=ETIME_OUT
*/
int read_timeout(int fd,unsigned int wait_seconds){
int ret=0;
if(wait_seconds>0){
fd_set read_fdset;
struct timeval timeout;
FD_ZERO(&read_fdset);
FD_SET(fd,&read_fdset);
timeout.tv_sec=wait_seconds;
timeout.tv_usec=0;
//select返回值三态
//1.若timeout时间到(超时),没检测到读事件,返回值ret=0
//2.若返回值ret<0&&errno==EINTR,说明select的过程被别的信号中断(可中断睡眠)
//3.若返回值ret>0,表示有read事件发生,返回事件发生的个数
do{
ret=select(fd+1,&read_fdset,NULL,NULL,&timeout);
}while(ret<0&&errno==EINTR);
if(ret==0){
ret=-1;
errno=ETIMEDOUT;
}
}
return ret;
}
//--------------------------------
/*
write_timeout-写超时检测函数,不含写操作
(即:判断[向fd套接字]中写数据,是否超时,不真正的写入数据)
@fd:文件描述符
@wait_seconds:等待超时秒数,如果为0表示不检测超时
成功(未超时):返回0
失败:返回-1
超时:返回-1并且errno=ETIME_OUT
*/
int write_timeout(int fd,unsigned int wait_seconds){
int ret=0;
if(wait_seconds>0){
fd_set write_fdset;
struct timeval timeout;
FD_ZERO(&write_fdset);
FD_SET(fd,&write_fdset);
timeout.tv_sec=wait_seconds;
timeout.tv_usec=0;
do{
ret=select(fd+1,NULL,&write_fdset,NULL,&timeout);
}while(ret<0&&errno==EINTR);
if(ret==0){
ret=-1;
errno=ETIMEDOUT;
}
else if(ret==-1)
ret=0;
}
return 0;
}
//-------------------------
/*
accept_timeout-带超时的accept
@fd:套接字
@addr:输出参数,返回对方地址
@wait_seconds:等待超时秒数,如果为0表示正常模式
成功(未超时):返回已连接的套接字
失败:返回-1
超时:返回-1并且errno=ETIME_OUT
*/
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds){
int ret;
if(wait_seconds>0){
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd,&accept_fdset);
timeout.tv_sec=wait_seconds;
timeout.tv_usec=0;
do{
ret=select(fd+1,&accept_fdset,NULL,NULL,&timeout);
}while(ret<0&&errno==EINTR);
if(ret==-1)
return -1;
else if(ret==0){
errno=ETIMEDOUT;
return -1;
}
}
socklen_t addrlen=sizeof(struct sockaddr_in);
if(addr!=NULL)
ret=accept(fd,(struct sockaddr*)addr,&addrlen);
else
ret=accept(fd,NULL,NULL);
if(ret==-1)
ERR_EXIT("accept");
return ret;
}
//-------------------------
/*connect_timeout为什么要这么做呢?设计思路!
1.TCPIP在客户端连接服务器的时候,如果异常,connect在fd是阻塞的情况下,返回时
间是1.5RTT(大约是75s以上),势必造成软件质量的下降
2.首先将fd变成非阻塞,之后试着先调用connect连接服务器
[1]如果网络好,回合服务器马上建立连接
[2]如果网络不好,则根据返回值进行下面操作:
if(ret<0&&errno==EINPROGRESS) 表示客户端和服务器[正在建立连接]
[2]我们此时需要等一等,等待的时间我们可以自己控制(即:[使用select管理
中心去监控fd],这样会大大的提高我们的产品质量)
其中:需要注意一点—>
select监控到fd[可读],并不能代表连接是可用的,还需要做进一步的判断—>
造成可读有2种情况:
case1:真正的连接建立起来了;
case2:建立连接的时候,失败了(失败会回写失败信息,造成fd也可读)
可以通过int sockoptret=getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);做一个容错即可*/
/*connect_timeout
@fd:套接字
@addr:要连接的对方地址
@wait_seconds:等待超时秒数,如果为0表示正常模式
成功(未超时):返回0
失败:返回-1
超时:返回-1并且errno=ETIMEOUT
*/
int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds){
int ret;
if(wait_seconds>0)
activate_nonblock(fd);
socklen_t addrlen=sizeof(struct sockaddr_in);
ret=connect(fd,(struct sockaddr*)addr,addrlen);
if(ret<0&&errno==EINPROGRESS){ //客户端和服务器正在建立连接
fd_set connect_fdset;
struct timeval timeout;
FD_ZERO(&connect_fdset);
FD_SET(fd,&connect_fdset);
timeout.tv_sec=wait_seconds;
timeout.tv_usec=0;
do{
//一旦连接建立,则fd就可写,所以connect_fdset放在select的写集合中
ret=select(fd+1,NULL,&connect_fdset,NULL,&timeout);
}while(ret<0&&errno==EINTR);
if(ret==0){ //select返回0,表示select调用超时
ret==-1;
errno=ETIMEDOUT;
}
else if(ret<0) //select调用失败
return -1;
else if(ret==1){ /*表示套接字可写(但是有两种情况:一种是建立连接成功,一种是套
接字产生错误[此时错误信息不会保存在errno变量中,需要使用getsockopt去获取]*/
int err;
socklen_t socklen=sizeof(err);
int sockoptret=getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);
if(sockoptret==-1){
return -1;
}
if(err==0){
ret=0;
}
}
}
if(wait_seconds>0){
deactibe_nonblock(fd);
}
return ret;
}
//----------------------------
ssize_t readn(int fd,void* buf,size_t count){
ssize_t nread; //每次循环read函数的返回值
char* bufp=(char*)buf; //作为辅助指针变量:每次读到nread个数据后,指针都要后移nread位置
size_t nleft=count; //nleft:剩余nleft个数据[未读]
while(nleft>0) //当有数据可读时,进入循环
{
if((nread=read(fd,bufp,nleft))<0) //read是可中断睡眠
{
if(errno==EINTR) //在读的过程中,被其他信号中断
continue; //执行continue,返回去继续读
return -1;
}
else if(nread==0) //本次read返回值nread=0,说明对方socket已经关闭
return count-nleft; //返回readn读取到的值
//读取到nread个数据,更新bufp和nleft
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd,const void* buf,size_t count){
ssize_t nwrite;
char* bufp=(char*)buf;
size_t nleft=count;
while(nleft>0)
{
if((nwrite=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
continue;
return -1;
}
else if(nwrite==0) //当且仅当nleft==0时,nwrite才等于0--->执行continue,此时while(nleft>0)不成立,退出while循环
continue;
bufp+=nwrite;
nleft-=nwrite;
}
return count;
}
ssize_t recv_peek(int fd,void* buf,size_t len){
while(1){
int ret=recv(fd,buf,len,MSG_PEEK);
if(ret==-1 && errno==EINTR)
continue;
return ret;
}
}
ssize_t readline(int fd,void* buf,size_t maxline)
{
int ret;
int nread;
char* bufp=(char*)buf;
size_t nleft=maxline;
while(1){
ret=recv_peek(fd,bufp,nleft);
if(ret<0) //失败
return ret;
else if(ret==0) //对方已关闭
return ret;
//else if(ret>0) //recv_peekt偷窥到了ret个字节的数据
nread=ret;
int i;
for(i=0;i<nread;i++){ //逐个判断读到的bufp中是否有\n
if(bufp[i]=='\n'){ //如果缓冲区中有\n
ret=readn(fd,bufp,i+1); //读走数据
if(ret!=i+1)
exit(EXIT_FAILURE);
return ret; //有\n就返回,并返回读走的字节数
}
}
if(nread>nleft) //if 读到的数 > 一行最大数 —> 异常处理
exit(EXIT_FAILURE);
nleft-=nread; //若缓冲区没有\n,把剩余的数据读走
ret=readn(fd,bufp,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread; //bufp指针后移后,再接着偷看缓冲区数据recv_peek,直到遇到\n
}
}