网络编程中的select实现超时检测和通用API
来源:互联网 发布:知乎 匿名用户 编辑:程序博客网 时间:2024/05/16 19:39
关于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 __cplusplusextern "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 8001void 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;}
0 0
- 网络编程中的select实现超时检测和通用API
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络编程中的超时检测
- 网络中的超时检测!
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- linux网络编程之套接字:套接字I/O超时设置方法和用select实现超时
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- 【Linux网络编程】超时检测
- 网络超时检测-select()函数
- javascript字典数据结构Dictionary实现
- 机器学习实战学习笔记6——AdaBoost
- 初级小白改bug心得
- S8.1_Struts2_Interceptor 拦截器的原理 拦截器与过滤器的区别 自定义拦截器 拦截器防止表单重复提交
- BZOJ1877: [SDOI2009]晨跑
- 网络编程中的select实现超时检测和通用API
- 【Hadoop】Sqoop部署入门指南
- 编译uboot遇到/bin/sh: dtc: 未找到命令 make[2]: *** [arch/arm/dts/zynq-zc702.dtb] 错误 127
- 计算x的y次方的值
- 类型转换
- 留言板------Ajax与mysql数据交互01
- ContentProvider
- java 远程 发送文件到服务器
- 领域驱动设计——项目生成与落地