关于select的基础知识在上一篇文章已经初步总结了,他的应用主要分为三个方向:
- 用于完成超时检测(connect、accept、read以及write)
- 用于处理客户端的普通文件描述符和套接字文件描述符,监控这些描述符可以避免在服务器死掉以后,客户端阻塞在等待标准输入或者其他文件的读取。
- 用于服务器构造多并发,使用select可以实现单进程实现多并发!只不过是自己管理所有建立的用于通信的链接和用于监听的套接字,有别于poll和epoll(底层自己管理)
实现connect超时检测
首先要明确以下知识点:
- 在建立套接字(fd)以后默认是阻塞的
- 如果客户端连接服务器发生异常,则默认阻塞的情况下,返回时间是1.5RTT,大约100秒!–软件质量低下
- 先把套接字通过fcntl变为非阻塞模型,再调用connect函数
- 如果网络顺利,直接建立链接
- 如果网络不好,则根据返回值判断,connect的返回值如果是-1而且errno==EINPROGRESS表示客户端和服务器正在建立连接,需要等待一段时间才能建立链接
- 等待的时间我们可以自己设定
- 利用select监控该套接字—提高软件质量
- 尽管select返回了套接字的可写状态(套接字默认是可写入状态,正常建立连接以后也是表返回可写入状态)但不一定表示就是正确建立链接
- select返回大于零的值
- 真正建立链接
- 建立套接字产生错误,会返回写失败信息,造成可写入的状态。 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。
/**
* activate_noblock - 设置I/O为非阻塞模式
* @fd: 文件描符符
*/
int activate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd,F_GETFL);
if(-1 == flags)
{
ret =flags;
perror("fcntl");
return ret;
}
flags |= O_NONBLOCK;
ret = fcntl(fd,F_SETFL,flags);
if(ret == -1)
{
perror("fcntl(fd,F_SETFL,flags)");
return ret;
}
return ret;
}
/**
* deactivate_nonblock - 设置I/O为阻塞模式
* @fd: 文件描符符
*/
int deactivate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd,F_GETFL);
if(-1 == flags)
{
ret =flags;
perror("fcntl");
return ret;
}
flags &= ~O_NONBLOCK;
ret = fcntl(fd,F_SETFL,flags);
if(ret == -1)
{
perror("fcntl(fd,F_SETFL,flags)");
return ret;
}
return ret;
}
/**
* connect_timeout - connect
* @fd: 套接字
* @addr: 要连接的对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret = 0;
socklen_t addrlen = sizeof(struct sockaddr_in);
fd_set connect_fdset;
struct timeval timeout;
int err;
socklen_t socklen = sizeof(err);
int sockoptret;
if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的
{
activate_nonblock(fd);
}
/*非阻塞
--成功:立马建立连接
--失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接
*/
ret = connect(fd,(struct sockaddr*)addr,addrlen);
if(ret < 0 && errno == EINPROGRESS)
{
FD_ZERO(&connect_fdset);
FD_SET(fd,&connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
// 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中
ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接
}while(ret < 0 && errno == EINTR);
if(ret == 0)//超时
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret < 0)//select出错
{
return -1;
}
else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态
{
/* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态
if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败
{
return -1;
}
if(err == 0)//真正可写入/准备好
ret = 0;
else{//-1表示套接字产生错误
errno = err;
ret = -1;
}
}
}
if (wait_seconds > 0)
{
deactivate_nonblock(fd);
}
return ret;
}
实现accept超时检测
/**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
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;
}
}
实现read超时检测
/**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
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的过程中被别的信号中断(可中断睡眠原理)
//2-1 若返回-1,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;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
实现write超时检测
/**
* write_timeout - 写超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
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 ret;
}
利用select实现的通用套接字编程库
- 头文件
#ifndef __COMSOCKET_H__
#define __COMSOCKET_H__
#ifdef __cplusplus
extern "C"
{
#endif
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
//错误码定义
#define Sck_Ok 0
#define Sck_BaseErr 3000
#define Sck_ErrParam (Sck_BaseErr+1)
#define Sck_ErrTimeOut (Sck_BaseErr+2)
#define Sck_ErrPeerClosed (Sck_BaseErr+3)
#define Sck_ErrMalloc (Sck_BaseErr+4)
//函数声明
//客户端环境低级初始化
int sckClient_init01(void **handle,char * ip,int port,int con_time,int send_time,int recv_time);
//客户端环境高级初始化
int sckClient_init(void **handle,int con_time,int send_time,int recv_time,int cnt);
//客户端获取链接
int sckClient_getConn(void *handle,char * ip,int port,int * con_fd);
//客户端关闭连接
int sckClient_closeConn(int con_fd);
//客户端发送报文
int sckClient_send(void *handle, int con_fd,unsigned char *data, int datalen);
//客户端端接受报文
int sckClient_rcv(void *handle,int con_fd, unsigned char *out, int *outlen); //1
// 客户端环境释放
void sckClient_destroy(void **handle);
/*=========================================================================================*/
//函数声明
//服务器端初始化
int sckServer_init(int port, int *listenfd);
int sckServer_accept(int listenfd, int *con_fd, int timeout);
//服务器端发送报文
int sckServer_send(int con_fd, unsigned char *data, int datalen, int timeout);
//服务器端端接受报文
int sckServer_rcv(int con_fd, unsigned char *out, int *outlen, int timeout);
//服务器端环境释放
int sckServer_destroy(int con_fd);
#ifdef __cplusplus
}
#endif
#endif
- 实现文件
#include "comsocket.h"
typedef struct _sckHandle
{
int sock_fd;
int arrayNum;
int con_time;
int send_time;
int recv_time;
}SckHandle;
/**
* readn - 读取固定字节数
* @fd: 文件描述符
* @buf: 接收缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1,读到EOF返回<count
*/
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count;//剩余字节数
ssize_t nread;//已经读取的字节数
char *bufp = (char*)buf;
while (nleft > 0)
{
if ((nread = read(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
return count - nleft;
bufp += nread;
nleft -= nread;
}
return count;
}
/**
* writen - 发送固定字节数
* @fd: 文件描述符
* @buf: 发送缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1
*/
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char *bufp = (char*)buf;
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
continue;
return -1;
}
else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/**
* recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @len: 长度
* 成功返回>=0,失败返回-1
*/
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
/**
* activate_noblock - 设置I/O为非阻塞模式
* @fd: 文件描符符
*/
int activate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd,F_GETFL);
if(-1 == flags)
{
ret =flags;
perror("fcntl");
return ret;
}
flags |= O_NONBLOCK;
ret = fcntl(fd,F_SETFL,flags);
if(ret == -1)
{
perror("fcntl(fd,F_SETFL,flags)");
return ret;
}
return ret;
}
/**
* deactivate_nonblock - 设置I/O为阻塞模式
* @fd: 文件描符符
*/
int deactivate_nonblock(int fd)
{
int ret = 0;
int flags = fcntl(fd,F_GETFL);
if(-1 == flags)
{
ret =flags;
perror("fcntl");
return ret;
}
flags &= ~O_NONBLOCK;
ret = fcntl(fd,F_SETFL,flags);
if(ret == -1)
{
perror("fcntl(fd,F_SETFL,flags)");
return ret;
}
return ret;
}
/**
* connect_timeout - connect
* @fd: 套接字
* @addr: 要连接的对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret = 0;
socklen_t addrlen = sizeof(struct sockaddr_in);
fd_set connect_fdset;
struct timeval timeout;
int err;
socklen_t socklen = sizeof(err);
int sockoptret;
if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的
{
activate_nonblock(fd);
}
/*非阻塞
--成功:立马建立连接
--失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接
*/
ret = connect(fd,(struct sockaddr*)addr,addrlen);
if(ret < 0 && errno == EINPROGRESS)
{
FD_ZERO(&connect_fdset);
FD_SET(fd,&connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
// 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中
ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接
}while(ret < 0 && errno == EINTR);
if(ret == 0)//超时
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret < 0)//select出错
{
return -1;
}
else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态
{
/* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态
if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败
{
return -1;
}
if(err == 0)//真正可写入/准备好
ret = 0;
else{//-1表示套接字产生错误
errno = err;
ret = -1;
}
}
}
if (wait_seconds > 0)
{
deactivate_nonblock(fd);
}
return ret;
}
/**
* write_timeout - 写超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
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 ret;
}
/**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
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的过程中被别的信号中断(可中断睡眠原理)
//2-1 若返回-1,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;
}
else if (ret == 1)
ret = 0;
}
return ret;
}
//客户端环境低级初始化
int sckClient_init01(void **handle,char * ip,int port,int con_time,int send_time,int recv_time)
{
int ret = 0;
SckHandle * tmp = NULL;
int sock;
int con;
struct sockaddr_in serv_addr;
if(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0
|| ip == NULL|| port < 0||port > 65535)
{
ret = Sck_ErrParam;
perror("sckClient_init:(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)");
return ret;
}
tmp = (SckHandle *)malloc(sizeof(SckHandle));
if(tmp == NULL)
{
ret = Sck_ErrMalloc;
perror("malloc(sizeof(SckHandle)");
return ret;
}
tmp->con_time = con_time;
tmp->send_time = send_time;
tmp->recv_time = recv_time;
sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock < 0)
{
ret = errno;
perror("socket");
return ret;
}
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ip);
if((con = connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))) < 0)
{
ret = errno;
perror("connect");
return ret;
}
//tmp->con_fd = con;
tmp->sock_fd = sock;
*handle = tmp;
return ret;
}
//客户端环境高级初始化
//成功返回0,失败返回相应的错误码
//参数分别是SckHandle的结构体成员
int sckClient_init(void **handle,int con_time,int send_time,int recv_time,int cnt)
{
int ret = 0;
SckHandle * tmp = NULL;
if(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)//合法性检测
{
ret = Sck_ErrParam;
printf("sckClient_init:(handle == NULL || con_time < 0 || send_time < 0 || recv_time < 0)");
return ret;
}
/*分配堆空间内存--临时变量*/
tmp = (SckHandle *)malloc(sizeof(SckHandle));
if(tmp == NULL)
{
ret = Sck_ErrMalloc;
perror("malloc(sizeof(SckHandle)");
return ret;
}
//进行初始化
tmp->con_time = con_time;
tmp->send_time = send_time;
tmp->recv_time = recv_time;
tmp->arrayNum = cnt;
//将分配好的结果甩出去
*handle = tmp;
return ret;
}
//客户端获取链接
//成功:返回0,失败返回错误代码
//参数是服务器地址以及已经初始化好的句柄和准备建立的套接字
int sckClient_getConn(void *handle,char * ip,int port,int * con_fd)
{
int ret = 0;
struct sockaddr_in serv_addr;
SckHandle * tmp = (SckHandle *)handle;
int sock;
//合法性检测
if(handle == NULL || ip == NULL || con_fd == NULL
|| port < 0 || port > 65535)
{
ret = Sck_ErrParam;
printf("sckClient_getConn(handle == NULL || ip == NULL || con_fd == NULL port < 0 || port > 65535)\n");
return ret;
}
//建立套接字
sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock < 0)
{
ret = errno;
perror("socket");
return ret;
}
//将建立好的套接字赋值给临时变量
tmp->sock_fd = sock;
//设置要连接的服务器地址
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ip);
/*if(connect(tmp->sock_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)
{
ret = errno;
perror("connect");
return ret;
}*/
//连接服务器
// 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
ret = connect_timeout(tmp->sock_fd, (struct sockaddr_in*) (&serv_addr), (unsigned int )tmp->con_time);
if (ret < 0)//连接服务器失败
{
if (ret==-1 && errno == ETIMEDOUT)//连接超时
{
ret = Sck_ErrTimeOut;
return ret;
}
else//其他情况的连接失败
{
printf("func connect_timeout() err: %d\n", ret);
}
}
*con_fd = tmp->sock_fd; //将建立好的套接字甩出去
return ret;
}
//客户端关闭连接
//参数:要关闭的套接字
int sckClient_closeConn(int con_fd)
{
if(con_fd > 0)
{
close(con_fd);
}
return 0;
}
//客户端发送报文
//成功:返回0,失败:返回错误代码(-1或者其他非零值)
//参数:句柄、已经建立的套接字、要发送数据缓冲区的地址、要发送的数据长度
int sckClient_send(void *handle, int con_fd,unsigned char *data, int datalen)
{
int ret = 0;
SckHandle * tmp = (SckHandle *)handle;
unsigned char * pkt = NULL;
int nwrite = 0;
int pkt_len = 0;
//合法性检测
if(handle == NULL || data == NULL
|| con_fd < 0 || con_fd > 65535 || datalen <0)
{
ret = Sck_ErrParam;
perror("Sck_ErrParam");
return ret;
}
//写超时检测
// 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
ret = write_timeout(con_fd,tmp->send_time);
if(ret == 0)//没有超时
{
pkt = (unsigned char *)malloc(datalen + sizeof(int));//分配堆空间内存--临时数据包
if(pkt == NULL)
{
ret = Sck_ErrMalloc;
perror("malloc(datalen + sizeof(int))");
return ret;
}
pkt_len = htonl(datalen);//包头(数据包长度)字节序转换
memcpy(pkt,&pkt_len,sizeof(int));//复制转换后表示长度的数据到包头
memcpy(pkt+sizeof(int),data,datalen);//复制有效数据到包体
nwrite = writen(con_fd,pkt,datalen + sizeof(int));//向服务器发送数据
if(nwrite < (datalen + sizeof(int)))//写入出错
{
if(pkt != NULL)//回收内存
{
free(pkt);
pkt = NULL;
}
return nwrite;
}
}
else if(ret < 0)//在检测超时的时候调用select失败
{
if(ret == -1 && errno == ETIMEDOUT)//确实超时了
{
ret = Sck_ErrTimeOut;
printf("write_timeout(con_fd,tmp->send_time)");
return ret;
}
return ret;//其他失败的情况
}
//内存回收
free(pkt);
pkt = NULL;
return ret;
}
/*typedef struct _pkt
{
int len;
unsigned char buf[1024];
}pkt_t;
*/
//客户端端接受报文
//成功:返回0,失败:返回错误代码或者实际读取到的字节数,但是该数字小于包头存储的数字
//参数:句柄、已经建立好的套接字,目的缓冲区地址、正确读取以后读取到的字节数
int sckClient_rcv(void *handle,int con_fd, unsigned char *out, int *outlen)
{
int ret = 0;
SckHandle * tmp = (SckHandle *)handle;
int tmp_len ;
//合法性检测
if(handle == NULL || out == NULL
|| outlen == NULL||con_fd < 0 || con_fd > 65535)
{
ret = Sck_ErrParam;
perror("Sck_ErrParam");
return ret;
}
//超时检测
ret = read_timeout(con_fd, tmp->recv_time);
if(ret == -1)//调用select失败
{
if(errno == ETIMEDOUT)//确实超时了
{
ret = Sck_ErrTimeOut;
perror("read_timeout");
return ret;
}
perror("read_timeout");//其他情况的失败
return ret;
}
else if(ret == 0)//没有超时
{
//成功返回count,失败返回-1,读到EOF返回<count
ret = readn(con_fd,&tmp_len,sizeof(tmp_len));//读取包头--表示有效数据长度
if(ret == -1)//读取失败
{
printf("func readn() err:%d\n",ret);
return ret;
}
else if(ret < sizeof(tmp_len))//读取结束--没有数据可读--对方关闭连接
{
ret = Sck_ErrPeerClosed;
printf("client close\n");
return ret ;//返回读取到的字节数--没用的字节数--表示错误
}
tmp_len = ntohl(tmp_len);//字节序转换得到包体长度
ret = readn(con_fd,out,tmp_len);//读取指定长度的有效数据
if(ret == -1)//读取失败
{
printf("func readn() err:%d\n",ret);
return ret;
}
else if(ret < tmp_len)//对方关闭连接
{
ret = Sck_ErrPeerClosed;
printf("client close\n");
return ret ;//返回读取到的字节数--没用的字节数--表示错误
}
}
*outlen = tmp_len;
return 0;//最后一定要返回0,因为ret在最后变成了readn函数的返回结果--读取到的字节数
}
// 客户端环境释放
//参数:句柄
void sckClient_destroy(void **handle)
{
if(handle != NULL)
{
free(*handle);
*handle = NULL;
}
}
/*===================================================================================*/
//服务器端初始化
//成功:返回0,失败:返回错误码
//参数:要绑定的端口号、准备建立的监听套接字
int sckServer_init(int port, int *listenfd)
{
int ret = 0;
int mylistenfd;
struct sockaddr_in servaddr;
//初始化服务器地址
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//建立监听用的套接字
mylistenfd = socket(AF_INET, SOCK_STREAM, 0);
if (mylistenfd < 0)
{
ret = errno ;
printf("func socket() err:%d \n", ret);
return ret;
}
//设置地址复用
int on = 1;
ret = setsockopt(mylistenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
if (ret < 0)
{
ret = errno ;
printf("func setsockopt() err:%d \n", ret);
return ret;
}
//绑定地址/端口和监听用的套接字
ret = bind(mylistenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret < 0)
{
ret = errno ;
printf("func bind() err:%d \n", ret);
return ret;
}
//开始监听
ret = listen(mylistenfd, SOMAXCONN);
if (ret < 0)
{
ret = errno ;
printf("func listen() err:%d \n", ret);
return ret;
}
//将建立好的监听套接字甩出去
*listenfd = mylistenfd;
return ret;//成功初始化返回0,否则返回出错步骤返回的错误代码
}
/**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
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;
}
return ret ;
}
//一但检测出 有select事件发生,表示对等方完成了三次握手,客户端有新连接建立
//此时再调用accept将不会堵塞
if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen); //返回已连接套接字
else
ret = accept(fd, NULL, NULL);
if (ret == -1)
{
ret = errno;
printf("func accept() err:%d \n", ret);
return ret;
}
return ret;
}
//服务器端接受客户端连接请求
//成功:返回0,失败:返回错误码
//参数:已经建立好的用于监听的套接字,准备建立的用于连接的套接字文件描述符--连接、超时时间值
int sckServer_accept(int listenfd, int *con_fd, int timeout)
{
int ret = 0;
//超时检测
//成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
ret = accept_timeout(listenfd, NULL, (unsigned int) timeout);//这里可以改进--这里没有保存客户端的地址
if (ret < 0)
{
if (ret == -1 && errno == ETIMEDOUT)//确实超时
{
ret = Sck_ErrTimeOut;
printf("func accept_timeout() timeout err:%d \n", ret);
return ret;
}
else//其他select失败的情况
{
ret = errno;
printf("func accept_timeout() err:%d \n", ret);
return ret;
}
}
*con_fd = ret;//将从完成三次握手的队列里面取出可用的连接甩出去
return ret;//成功返回0,失败返回错误代码
}
//服务器端发送报文
//成功:返回0,失败:返回错误代码或者实际写入的字节数
//参数:已经建立好的连接,待发送数据、要发送长度、超时时间
int sckServer_send(int con_fd, unsigned char *data, int datalen, int timeout)
{
int ret = 0;
int writed = 0;
unsigned char *netdata = NULL;
//超时检测
// 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
ret = write_timeout(con_fd, timeout);
if (ret == 0)//没有超时
{
writed = 0;
netdata = ( unsigned char *)malloc(datalen + 4);//分配空间
if ( netdata == NULL)
{
ret = Sck_ErrMalloc;
printf("func sckServer_send() mlloc Err:%d\n ", ret);
return ret;
}
//数据打包
int netlen = htonl(datalen);
memcpy(netdata, &netlen, 4);
memcpy(netdata+4, data, datalen);
//数据发送
writed = writen(con_fd, netdata, datalen + 4);
if (writed < (datalen + 4) )//对方关闭
{
if (netdata != NULL)
{
free(netdata);//内存回收
netdata = NULL;
}
return writed;//返回实际写入的数据--非零都表示发送失败
}
}
else if (ret < 0)
{
//失败返回-1,超时返回-1并且errno = ETIMEDOUT
if (ret == -1 && errno == ETIMEDOUT)//确实超时
{
ret = Sck_ErrTimeOut;
printf("func sckServer_send() mlloc Err:%d\n ", ret);
return ret;
}
return ret;//其他情况的select失败。ret非零,但不一定是-1
}
free(netdata);//内存回收
netdata = NULL;
return ret;
}
//服务器端端接受报文
//成功:返回0,失败:返回错误代码或者实际读取到的字节数,但是该数字小于包头存储的数字
//参数:已经建立好的连接,目的缓冲区地址、正确读取以后读取到的字节数,超时时间
int sckServer_rcv(int con_fd, unsigned char *out, int *outlen, int timeout)
{
int ret = 0;
//合法性检测
if (out==NULL || outlen==NULL)
{
ret = Sck_ErrParam;
printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);
return ret;
}
ret = read_timeout(con_fd, timeout); //超时检测
if (ret != 0)//select调用失败
{
if (ret==-1 || errno == ETIMEDOUT)//确实超时了
{
ret = Sck_ErrTimeOut;
printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);
return ret;
}
else//其他情况的失败
{
printf("func sckClient_rev() timeout , err:%d \n", Sck_ErrTimeOut);
return ret;
}
}
//没有超时
int netdatalen = 0;
ret = readn(con_fd, &netdatalen, 4); //读包头 4个字节
if (ret == -1)//读取失败
{
printf("func readn() err:%d \n", ret);
return ret;
}
else if (ret < 4)//对方关闭
{
ret = Sck_ErrPeerClosed;
printf("func readn() err peer closed:%d \n", ret);
return ret;
}
int n;
n = ntohl(netdatalen);//字节序转换
ret = readn(con_fd, out, n); //根据长度读数据
if (ret == -1)//读取失败
{
printf("func readn() err:%d \n", ret);
return ret;
}
else if (ret < n)//对方关闭
{
ret = Sck_ErrPeerClosed;
printf("func readn() err peer closed:%d \n", ret);
return ret;//返回读取到的字节数--没用的字节数--表示错误
}
*outlen = n;//将正确读取以后读取到的字节数甩出去
return 0;//最后一定要返回0,因为ret在最后变成了readn函数的返回结果--读取到的实际字节数
}
//服务器端环境释放
//成功:返回,失败:返回-1
//准备关闭的链接
int sckServer_destroy(int con_fd)
{
if(close(con_fd) == 0)
return 0;
else
return -1;
}
- 客户端测试代码
#include "comsocket.h"
int main()
{
void * handle = NULL;
int ret = 0;
unsigned char *data = (unsigned char *)"asdfghjklaedas";
unsigned char out[1024] = {0};
int datalen = 10;
int outlen = 1024;
int con_fd;
//客户端环境初始化
ret = sckClient_init(&handle,5,5,5,1);
ret = sckClient_getConn(handle,"192.168.1.110",8001,&con_fd);
//客户端发送报文
ret = sckClient_send(handle,con_fd, data,datalen);
//客户端端接受报文
ret = sckClient_rcv(handle,con_fd,out, &outlen);
out[outlen] = '\0';
printf("data: %s \nlen: %d\n", out,outlen);
// 客户端环境释放
sckClient_destroy(&handle);
return ret;
}
- 服务器端测试代码
#include "comsocket.h"
#define PORT 8001
void chld_handle(int sig)
{
pid_t my_pid;
//wait(NULL);//只能处理单进程的情况
while((my_pid = waitpid(-1, NULL, WNOHANG)) > 0)
printf("child is died,parent take care of it:%d\n", my_pid);
}
int main()
{
int ret = 0;
int listenfd;
int con_fd;
int datalen = 1024;
int timeout = 5;
//unsigned char *data = (unsigned char *)"lzjyrm";
unsigned char recv[1024] = {0};
pid_t pid;
signal(SIGCHLD,chld_handle);
signal(SIGPIPE,SIG_IGN);
ret = sckServer_init(PORT, &listenfd);
if(ret != 0)
{
printf("sckServer_init err:%d\n ",ret);
return ret;
}
printf("Waiting for connect.....\n");
while(1)
{
ret = sckServer_accept(listenfd, &con_fd, timeout);
if(ret == Sck_ErrTimeOut)
{
printf("sckServer_accept Sck_ErrTimeOut");
continue;
}
pid = fork();
if(pid == -1)
{
perror("fork");
return errno;
}
else if(pid == 0)
{
close(listenfd);
while(1)
{
memset(recv,0,sizeof(recv));
ret = sckServer_rcv(con_fd, recv, &datalen, timeout); //服务器端端接受报文
if(ret != 0)
{
printf("sckServer_rcv: %d\n",ret);
break;
}
recv[datalen] = '\0';
printf("rcv:%s\n==================\n",recv);
//服务器端发送报文
ret = sckServer_send(con_fd,recv, datalen, timeout);
if(ret != 0)
{
printf("sckServer_send: %d\n",ret);
break;
}
}
close(con_fd);
exit(ret);
}
else if(pid > 0)
{
close(con_fd);
}
}
//服务器端环境释放
//ret = sckServer_destroy(void **handle);
return 0;
}