一. select简介
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set); // 清除出集合
int FD_ISSET(int fd, fd_set *set); // 判断是否在集合中
void FD_SET(int fd, fd_set *set); // 添加进集合中
void FD_ZERO(fd_set *set); // 将集合清零
参数1:读写异常集合中的文件描述符的最大值加1;
参数2:读集合,关心可读事件;
套接口缓冲区有数据可读
对等连接的写一半关闭。即接收到FIN段,读操作将返回0
如果是监听套接口,已完成连接队列不为空时。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。
参数3:写集合,关心可写事件;
套接口发送缓冲区有空间容纳数据。(连接一旦建立就可写)
对等连接的读一半关闭。即收到RST段之后,再次调用write操作。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取。
参数4:异常集合,关心异常事件;用于检查带外数据,
套接口存在带外数据(TCP头部 URG标志,16位紧急指针字段)
参数5:超时时间结构体
对于参数2,3,4来说,如果不关心对应事件则设置为NULL即可。注意5个参数都是输入输出参数,即select返回时可能对其进行了修改,比如集合被修改以便标记哪些套接口发生了事件,时间结构体的传出参数是剩余的时间,如果设置为NULL表示永不超时。用select管理多个I/O,select阻塞等待, 一旦其中的一个或多个I/O检测到我们所感兴趣的事件,select函数返回,返回值为检测到的事件个数,并且返回哪些I/O发送了事件,遍历这些事件,进而处理事件。注意当select阻塞返回后,此时调用accept 接收连接是不会阻塞的,直接返回已连接套接字,可以认为是select 提前阻塞了。但此时调用write 还是可能阻塞的,因为需要写入的空间大小可能缓冲区还不满足。
核心代码修改:
void echo_cli(int sock)
{
fd_set rset;// fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
FD_ZERO(&rset);
int nready;
int maxfd;
int fd_stdin = fileno(stdin); //取得参数stream指定的文件流所使用的文件描述符
if (fd_stdin > sock)
maxfd = fd_stdin;
else
maxfd = sock;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (1)
{
FD_SET(fd_stdin, &rset);// 输入流文件描述符添加到文件描述符中
FD_SET(sock, &rset);// sock文件描述符添加到集合中
nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件
if (nready == -1)// 负值:select错误
ERR_EXIT("select error");
if (nready == 0)/// 0:等待超时,没有可读写或错误的文件
continue;
/ 正值:某些文件可读写或出错
if (FD_ISSET(sock, &rset)) // 判断sock 是否在集合中,如果在,进行读操作
{
int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取
if (ret == -1)
ERR_EXIT("read error");
else if (ret == 0) //服务器关闭
{
printf("server close\n");
break;
}
fputs(recvbuf, stdout);
memset(recvbuf, 0, sizeof(recvbuf));
}
if (FD_ISSET(fd_stdin, &rset))// 判断fd_stdin 是否在集合中,如果在,进行写操作
{
if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
break;
writen(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
close(sock);
}
客户端的全部代码:
/// echolic.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#define ERR_EXIT(m) \
do{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
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)
nread = 0;// 继续读取数据
else
return -1;
}
else if( nread == 0) // 对方关闭或已经读到eof
break;
bufp +=nread;
nleft -= nread;
}
return count-nleft;
}
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;
else
return -1;
}
else if( nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/// recv()只能读取套接字,而不能读取一般文件描述符
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while(1)
{
int ret = recv(sockfd,buf,len,MSG_PEEK);//MSG_PEEK接收缓冲区的数据,但是并没有清除
if( ret == -1 && errno == EINTR)
continue;
return ret;
}
}
// 读到'\n' 就返回,加上'\n'一行最多为maxline个字符
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp =(char *) buf;
int nleft = maxline;
//int count=0;
while(1)
{
// recv_peek读取缓冲区的字符个数,并放入到bufp缓存里面
ret = recv_peek(sockfd,bufp,nleft);
if(ret < 0)
return ret;// 表示失败
else if(ret == 0)
return ret; // 表示对方关闭连接了
nread = ret;
// 判断接收到字符是否有'\n'
int i;
for(i=0; i<nread; ++i)
{
if(bufp[i] == '\n')
{
// readn读取数据,这部分缓冲会被清空的
ret = readn(sockfd,bufp,i+1);
if(ret != (i+1))
exit(EXIT_FAILURE);
return ret; //+ count;
}
}
if( nread > nleft)
exit(EXIT_FAILURE);
nleft -= nread;
ret = readn(sockfd,bufp,nread);
if(ret != nread)
exit(EXIT_FAILURE);
bufp += nread;// 下一次指针偏移
//count += nread;
}
return -1;
}
/*
void echo_cli(int sock)
{
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while( fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
{
writen(sock,sendbuf,strlen(sendbuf));// 需要的注意的的时sizeof(sendbuf)和strlen(recvbuf)不一样的,容易出现混淆
int ret = readline(sock,recvbuf,1024);///最后一个参数为缓冲区的最大值
if( ret == -1 )
ERR_EXIT("readline");
else if(ret == 0)
{
printf("client close \n");
break;
}
fputs(recvbuf,stdout);
memset(recvbuf,0,sizeof(recvbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
close(sock);
}
*/
核心修改代码
void echo_cli(int sock)
{
fd_set rset;// fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄
FD_ZERO(&rset);
int nready;
int maxfd;
int fd_stdin = fileno(stdin); //取得参数stream指定的文件流所使用的文件描述符
if (fd_stdin > sock)
maxfd = fd_stdin;
else
maxfd = sock;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (1)
{
FD_SET(fd_stdin, &rset);// 输入流文件描述符添加到文件描述符中
FD_SET(sock, &rset);// sock文件描述符添加到集合中
nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示检测到可读事件
if (nready == -1)// 负值:select错误
ERR_EXIT("select error");
if (nready == 0)/// 0:等待超时,没有可读写或错误的文件
continue;
/ 正值:某些文件可读写或出错
if (FD_ISSET(sock, &rset)) // 判断sock 是否在集合中,如果在,进行读操作
{
int ret = readline(sock, recvbuf, sizeof(recvbuf)); //按行读取
if (ret == -1)
ERR_EXIT("read error");
else if (ret == 0) //服务器关闭
{
printf("server close\n");
break;
}
fputs(recvbuf, stdout);
memset(recvbuf, 0, sizeof(recvbuf));
}
if (FD_ISSET(fd_stdin, &rset))// 判断fd_stdin 是否在集合中,如果在,进行写操作
{
if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
break;
writen(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
close(sock);
}
int main()
{
int sock;
if( (sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
ERR_EXIT("sock err");
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if( connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("connect error");
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen) < 0)
ERR_EXIT("getsockname err");
printf("ip=%s ,port = %d \n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
echo_cli(sock);
return 0;
}