1. 阻塞与非阻塞
阻塞方式block,进程执行到这一函数时必须等待事件发生,如果没发生,就一直阻塞函数不能返回
非阻塞 non-block
进程或线程执行不必等待事件发生一旦执行肯定返回以不停返回返回值来反应函数执行情况
Select就是这样监视描述符的变化
2.select模型
两结构体:
Structfd_set存放描述符,文件句柄的聚合
建立文件联系四个宏
FD_ZERO(fd_set*fd_set);//清空fdset与所有文件句柄的联系
FD_SET(intfd,fd_set *fdset);//建立文件句柄与fdset的联系
FD_CLR(intfd,fd_set *fdset); 清除文件句柄fd与fdset的联系
FD_ISSET(intfd,fd_set*fdset);//检查fdset联系的文件句柄fd是否读写,>0表示可读写
Structtimeval 两个成员 tv_sec表示秒数,tv_usec表示毫秒数
Select函数原型
Int select(intnfds,fd_set *rdfds,fd_set *wtfds,fd_set *exfds,struct timeval *timeout);
A.其中rdfds(wtfds)读集,度过有一个文件可读,select返回一个大于0的值表示文件可读超出timeout时间select返回0,发生错误返回负值,
可传入NULL表示不关心文件读写变化
Exfds异常文件集有特殊情况发生select返回
B.timeval*out 若果设置为NULL变为阻塞方式
设置为0完全非阻塞
C.返回值大于0文件可读或可写
返回值为0,out of time
返回值小于0,select错误
network.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define ERR_EXIT(m) \
do { \
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd, void *usrbuf, size_t n);
ssize_t writen(int fd, void *usrbuf, size_t n);
ssize_t recv_peek(int sockfd, void *usrbuf, size_t n);
ssize_t readline(int sockfd, void *usrbuf, size_t maxline);
network.c
#include "network.h"
ssize_t readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n; //表示还需要读取的字节数
ssize_t nread;
char *bufp = usrbuf; //控制read函数存放的位置
while(nleft > 0)
{
if((nread = read(fd, bufp, nleft)) == -1)
{
if(errno == EINTR) //interupt
nread = 0; //continue; 中断需要再次读取
else
return -1; // ERROR
}else if(nread == 0) // EOF
break;
nleft -= nread;
bufp += nread;
}
return (n - nleft);
}
ssize_t writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwrite;
char *bufp = usrbuf;
while(nleft > 0)
{
//nwrite == 0也属于错误
if((nwrite = write(fd, bufp, nleft)) <= 0)
{
if(errno == EINTR)
nwrite = 0;
else
return -1; // -1 和 0
}
nleft -= nwrite;
bufp += nwrite;
}
return n; //这里不是 n- nleft 必须是n
}
//recv_peek选项完成一次正确的读取过程。
ssize_t recv_peek(int sockfd, void *buf, size_t len) {
int nread;
while (1) {
//这个过程只成功调用一次
nread = recv(sockfd, buf, len, MSG_PEEK);
if (nread < 0 && errno == EINTR) { //被中断则继续读取
continue;
}
if (nread < 0) {
return -1;
}
break;
}
return nread;
}
ssize_t readline(int sockfd, void *buf, size_t maxline) {
int nread; //一次IO读取的数量
int nleft; //还剩余的字节数
char *ptr; //存放数据的指针的位置
int ret; //readn的返回值
int total = 0; //目前总共读取的字节数
nleft = maxline-1;
ptr = buf;
while (nleft > 0) {
//这一次调用仅仅是预览数据
//并没有真的把数据从缓冲区中取走
ret = recv_peek(sockfd, ptr, nleft);
//注意这里读取的字节不够,绝对不是错误!!!
if (ret <= 0) {
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; ++i) {
if (ptr[i] == '\n') {
//这里才是真正的读取过程
ret = readn(sockfd, ptr, i + 1);
if (ret != i + 1) {
return -1;
}
total += ret;
ptr += ret;
*ptr = 0;
return total; //返回此行的长度 '\n'包含在其中
}
}
//如果没有发现\n,这些数据应全部接收
ret = readn(sockfd, ptr, nread);
if (ret != nread) {
return -1;
}
nleft -= nread;
total += nread;
ptr += nread;
}
*ptr = 0;
return maxline-1;
}
client.c
#include "network.h"
#include <sys/select.h>
#include <sys/epoll.h>
void do_service(int peerfd)
{
char recvbuf[1024]={0};
char sendbuf[1024]={0};
//init
fd_set readset;
FD_ZERO(&readset);
//防止重定向不用STDIN_FILENO
int stdin_fd=fileno(stdin);
int maxfd=(stdin_fd>peerfd)?stdin_fd:peerfd;
int nready;
while(1)
{
FD_ZERO(&readset);
FD_SET(stdin_fd,&readset);
FD_SET(peerfd,&readset);
nready=select(maxfd+1,&readset,NULL,NULL,NULL);
if(nready==-1)
{
if(errno==EINTR)
continue;
ERR_EXIT("select");
}
if(FD_ISSET(stdin_fd,&readset))
{
fgets(sendbuf,1024,stdin);
writen(peerfd,sendbuf,strlen(sendbuf));
}
if(FD_ISSET(peerfd,&readset))
{
int ret= readline(peerfd,recvbuf,1024);
if(ret==-1)
{
ERR_EXIT("readline");
}
else
if(ret==0)
{
shutdown(peerfd,SHUT_WR);
return;
}
printf("recv data : %s",recvbuf);
}
}
}
int main(int argc, const char *argv[])
{
int peerfd=socket(AF_INET,SOCK_STREAM,0);
if(peerfd==-1)
ERR_EXIT("socket");
struct sockaddr_in peeraddr;
peeraddr.sin_family=AF_INET;
peeraddr.sin_port=htons(8989);
peeraddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(peerfd,(struct sockaddr *)&peeraddr,sizeof(peeraddr))==-1)
ERR_EXIT("CONNECT");
do_service(peerfd);
close(peerfd);
return 0;
}
1.使用select编写客户端程序的一般步骤:
初始化参数,包括readset、maxfd、nready
while(1)
{
1.先重新设置readset
2.执行select调用,包括检查返回值
3.依次检查stdin和peerfd,如果是前者就从键盘读取数据,如果是后者,就使用readline接收网络数据。
}
3.poll模型
client.c
#include "network.h"
#include <poll.h>
void do_service(int peerfd)
{
char recvbuf[1024]={0};
char sendbuf[1024]={0};
//init
struct pollfd client[2];//listen 2 fd
client[0].fd=fileno(stdin);
client[0].events =POLLIN; //listen read events
client[1].fd=peerfd;
client[1].events=POLLIN;
int maxi =1; //max subscript of array client[1]
int nready;// join poll echo value
while(1)
{
// int poll(struct pollfd *fds,nfds_t nfds,int timeout);
nready=poll(client,maxi+1,-1); // -1 said permanent waiting
if(nready==-1)
{
if(errno==EINTR)
continue;
ERR_EXIT("poll");
}
else if(nready ==0)
{
continue;
}
//check stdin and peerfd according to priority
if(client[0].revents & POLLIN)
{
if(fgets(sendbuf,1024,stdin)==NULL)
{
shutdown(peerfd,SHUT_WR);
client[0].fd=-1; //do not listen stdin;
}
else
{
writen(peerfd,sendbuf,strlen(sendbuf));
}
}
if(client[1].revents&POLLIN)
{
int ret=readline(peerfd,recvbuf,1024);
if(ret==-1)
ERR_EXIT("readline");
else if(ret==0)
{
printf("server close\n");
close(peerfd);
break;
}
printf("recv data: %s",recvbuf);
}
}
}
int main(int argc, const char *argv[])
{
int peerfd=socket(AF_INET,SOCK_STREAM,0);
if(peerfd==-1)
ERR_EXIT("socket");
struct sockaddr_in peeraddr;
peeraddr.sin_family=AF_INET;
peeraddr.sin_port=htons(8989);
peeraddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(peerfd,(struct sockaddr *)&peeraddr,sizeof(peeraddr))==-1)
ERR_EXIT("CONNECT");
do_service(peerfd);
close(peerfd);
return 0;
}
使用poll编写客户端的一般步骤:
准备数组(这里为2),填入相应的fd以及events。还有maxi、nready
while(1)
{
1.执行poll,以及检查返回值
2.检查两个fd,通过revents字段
}