[APUE]再讀之高級IO

1. 非阻塞IO。

阻塞IO有如下情況

。 數據不存在,  則讀操作永遠阻塞。典型的爲 管道操作。

。 數據不能被立即接受,寫這樣的文件會被阻塞。

。 打開文件會被阻塞。(典型爲調制解調器。只寫方式打開FIFO,要等待一個讀進程)

。 對已經加上強制性鎖的文件進行讀寫。

。 ioctl 操作

。 某些進程間通信函數。 比如semophore的p,v 操作。

管道阻塞的demo,子進程等待5s後再寫,父進程阻塞5秒讀數據。

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
int main(int argc,char* argv[])
{

    int fd[2];
    char* buf ="message from child\n";
    char rbuf[1024];
    if(pipe(fd)<0)
    {
        printf("create pipe error\n");
        exit(-1);
    }

    pid_t pid;
    pid = fork();
    if(pid<0)
    {
        fprintf(stderr,"fork error: %s\n", strerror(errno));
        exit(-1);
    }
    else if(pid==0)
    {
        close(fd[0]);
        sleep(5);
        int len = strlen(buf);
        if(write(fd[1],buf, strlen(buf))!= len)
        {
            fprintf(stderr,"write error: %s\n", strerror(errno));
            exit(-1);
        }
    }
    else
    {
        close(fd[1]);
        int n = read(fd[0],rbuf,1024);
        if(n==-1)
        {
            fprintf(stderr,"read error: %s\n", strerror(errno));
            exit(-1);
        }
        rbuf[n] = 0;
        printf(rbuf);
    }
}

fifo 阻塞的demo.父進程sleep 5 s. 子進程5秒後纔有數據可讀,之前一直阻塞。

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char* argv)
{
    char errorbuf[1024];
    memset(errorbuf,1024,0);
    if(mkfifo("/tmp/fifowaittest", S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)==-1 && errno!=EEXIST)
    {
        fprintf(stderr,"error %s\n", strerror(errno));
        exit(-1);
    }

    int pid =fork();
    if(pid<0)
    {
        snprintf(errorbuf,1024,"fork error, error is: %d, reason is %s\n", errno, strerror(errno));
        printf("%s",errorbuf);
    }
    else if (pid==0)
    {
        int fd;
        int n;
        char rdbuf[1024];
        fd = open("/tmp/fifowaittest",O_RDONLY);
        if((n=read(fd,rdbuf,1024))<0)
        {
            printf("read error. error is :%d,reason is %s\n", errno,strerror(errno));
            return -1;
        }
        rdbuf[n]=0;
        printf(rdbuf);
    }
    else
    {
        sleep(5);
        int fd;
        char wrbuf[]="message from parent\n";
        fd = open("/tmp/fifowaittest",O_WRONLY);
        if (write(fd,wrbuf,strlen(wrbuf))!=strlen(wrbuf))
        {
            printf("write error. error is :%d,reason is %s\n", errno,strerror(errno));
            return -1;
        }
        int status;
        if (wait(&status)==-1)
        {
            printf("wait error. error is :%d,reason is %s\n", errno,strerror(errno));
            return -1;
        }
        printf("exit status is %d\n",status);
    }

}

兩種方法指定非阻塞。

。open 函數時指定O_NONBLOCK標誌

。已經打開的用fcntl設置O_NONBLOCK標誌。

非阻塞實例,當stdout爲文件時,並沒有錯誤,並一次性寫完。當終端爲stderr時,出現大量錯誤,並多次重寫。

 

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

char buf[10000];

int setFileFlag(int fd, int setFlag)
{
    int flag =  fcntl(fd,F_GETFL,0);
    fcntl(fd, F_SETFL, flag |  setFlag);
    return 1;
}

int main()
{
    int  n,nwrite;
    n = read(STDIN_FILENO, buf, sizeof(buf));
    fprintf(stderr,"read %d bytes\n",n);
    setFileFlag(STDOUT_FILENO,O_NONBLOCK);
    char* ptr;
    for(ptr=buf;n>0;)
    {
        errno=0;
        nwrite= write(STDOUT_FILENO,ptr, n);
        fprintf(stderr,"\n n = %d, write %d bytes,errno is %d,reason is %s\n", n,nwrite,errno,strerror(errno));
        if (nwrite>0)
        {
            n= n- nwrite;
            ptr = ptr+nwrite;
        }
    }

}

2. 記錄鎖。

int fcntl(int fileds,int cmd,...)

cmd 爲F_GETLK,F_SETLK或者F_SETLKW.第三個參數爲指向flock結構的指針。
struck flock{
short l_type; /*F_RDLCK,F_WRLCK,or F_UNLCK*/
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}

l_start 和l_whence lseek一樣。區域的長度,由l_len表示。

l_len爲0,則鎖儘可能大的長度。

l_start設置爲0, i_whence設置爲SEEK_SET,l_len爲0, 則可鎖所有文件。

讀寫鎖:如果區域被上讀鎖,其他讀鎖依然可以加。但寫鎖不可以。上了寫鎖,則其他任何鎖都不可以加。


加鎖和測試鎖的例子:

#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int lock_reg(int fd, int cmd, int type, off_t offset,int whence,off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_start = offset;
    lock.l_whence = whence;
    lock.l_len = len;
    return (fcntl(fd,cmd,&lock));
}

pid_t lock_test(int fd,int type, off_t offset, int whence,off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_start = offset;
    lock.l_whence = whence;
    lock.l_len= len;
    if(fcntl(fd,F_GETLK,&lock)<0)
        return -1;
    if(lock.l_type==F_UNLCK)
        return 0;
    return lock.l_pid;
}

int main()
{
    int fd = open("/tmp/log/violation.file.test.log",O_RDWR);
    pid_t pid = fork();
    if(pid<0)
    {
        printf("fork error");
        return -1;
    }
    else  if(pid>0)
    {
        lock_reg(fd,F_SETLK,F_WRLCK,0,SEEK_SET,0);
        printf("parent pid is %d\n",getpid());
        sleep(10);
    }
    else
    {
        sleep(5);
        pid_t p=lock_test(fd,F_WRLCK,0,SEEK_SET,0);
        printf("file locked by %d\n", p);
    }
}

鎖與進程和文件兩方面都相關。進程關閉後,鎖將自動釋放。dup 出一個fd,然後關閉dup 出來的fd,鎖也將會被釋放。

fork不能繼承鎖,exec可以繼承鎖。

關閉dup 出來後fd,鎖釋放的例子。

#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int lock_reg(int fd, int cmd, int type, off_t offset,int whence,off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_start = offset;
    lock.l_whence = whence;
    lock.l_len = len;
    return (fcntl(fd,cmd,&lock));
}

pid_t lock_test(int fd,int type, off_t offset, int whence,off_t len)
{
    struct flock lock;
    lock.l_type = type;
    lock.l_start = offset;
    lock.l_whence = whence;
    lock.l_len= len;
    if(fcntl(fd,F_GETLK,&lock)<0)
        return -1;
    if(lock.l_type==F_UNLCK)
        return 0;
    return lock.l_pid;
}

int main()
{
    int fd = open("/tmp/log/violation.file.test.log",O_RDWR);
    pid_t pid = fork();  
    if(pid<0)  
    {  
        printf("fork error");  
        return -1;  
    }  
    else  if(pid>0)  
    {  
        lock_reg(fd,F_SETLK,F_WRLCK,0,SEEK_SET,0);  
        printf("child pid is %d\n",getpid());  
        sleep(5);  
        int fd2= dup(fd);
        close(fd2);
        sleep(10);  
        printf("parent end\n");  
    }  
    else  
    {  
        sleep(2);  
        pid_t p=lock_test(fd,F_WRLCK,0,SEEK_SET,0);  
        printf("file locked by %d\n", p);  
        sleep(6);  
        p=lock_test(fd,F_WRLCK,0,SEEK_SET,0);  
        printf("file locked by %d\n", p);  
    }  
}


3. 利用文件鎖強制進程進行單例運行。

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#define PID_FILE "/var/run/single.pid"

int main()
{
    int fd =  open(PID_FILE,O_WRONLY|O_CREAT,0644); 
    if(fd<0)
    {
        printf("open pid file error\n");
        exit(-1);
    }

    struct flock lock;
    lock.l_type = F_WRLCK; 
    lock.l_start= 0; 
    lock.l_whence= SEEK_SET; 
    lock.l_len= 0; 
    int result = fcntl(fd,F_SETLK,&lock);
    if(result<0 && (errno==EACCES || errno==EAGAIN))
    {
        printf("We have running process,exit\n");
        exit(0);
    }

    if(ftruncate(fd,0)<0)
    {
        printf("truncate file error\n");
        exit(-1);
    }
    char buf[64];
    sprintf(buf, "%d\n",getpid());

    if(write(fd,buf,strlen(buf))!=strlen(buf))
    {
        printf("write error\n");
        exit(-1);
    }

    int val;
    if((val= fcntl(fd,F_GETFD,0))<0)
    {
        printf("fcntl error\n");
        exit(-1);
    }
    val = val | FD_CLOEXEC;
    if(fcntl(fd,F_SETFD,val)<0)
    {
        printf("fcntl set fd error\n");
        exit(-1);
    }
    

    //do something
    sleep(100);
    exit(0);



}


4. 建議性鎖和強制性鎖

建議性鎖需要合作進程配合,不然起不到鎖的作用。

強制性鎖,通過關閉文件的組執行權限,並且打開組設置設置ID爲來實現。

#include <stdio.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>

int main()
{
    int fd;
    fd = open("tmplock",O_RDWR|O_CREAT | O_TRUNC ,0644);
    if(fd<0)
    {
        printf("open file error\n");
        return -1;
    }

    if(write(fd,"abcde",5)!=5)
    {
        printf("write error\n");
        return -1;
    }
    
    struct stat statbuf;
    if(fstat(fd,&statbuf)<0)
    {
        printf("fstat error\n");
        return -1;
    }

    if( fchmod(fd,(statbuf.st_mode & ~S_IXGRP)|S_ISGID)<0)
    {
        printf("fchmod error\n");
        return -1;
    }

    pid_t pid = fork();
    if(pid<0)
    {
        printf("fork procees error\n");
        return -1;
    }
    else if(pid>0)
    {
        struct flock lock;
        lock.l_start =0;
        lock.l_whence =SEEK_SET;
        lock.l_type = F_WRLCK;
        lock.l_len = 0;
        if(fcntl(fd,F_SETLK,&lock)<0)
        {
            printf("write lock error\n");
            exit(-1);
        }
        if (waitpid(pid,NULL,0)<0)
        {
            printf("wait pid error\n");
            exit(-1);
        }
        printf("parent end\n");
    }
    else
    {
        sleep(2);
        //set fd non blocking
        int flag=fcntl(fd,F_GETFL,0);
        flag = flag | O_NONBLOCK;
        fcntl(fd,F_SETFL,flag);
        //try set read lock
        struct flock lock;
        lock.l_start =0 ;
        lock.l_whence =SEEK_SET ;
        lock.l_type = F_RDLCK ;
        lock.l_len= 0;
        if(fcntl(fd,F_SETLK,&lock)!=-1)
        {
            printf("mandary lock do not work\n");
            exit(-1);
        }
        printf("mandary lock do work.errno =%d,error = %s\n",errno,strerror(errno));
        
        if(lseek(fd,0,SEEK_SET)<0)
        {
            printf("lseek error\n");
            exit(-1);
        }
        char buf[64];
        if(read(fd,buf,2)<0)
        {
            printf("read error.mandary lock work\n");
        }
        else
        {
            buf[2]=0;
            printf("mandary lock error,buf=%s\n",buf);
        }
        printf("child end\n");
    }
    exit(0);

}


5. 流

流提供用戶進程和內核之間傳遞消息的一個全雙工通道。


6. IO 多路複用 select函數

int select(int maxfd, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,struct timeval *tvptr)
tvptr 爲null時候,表示永遠等待。

一個select 接受socket的例子,只是demo作用,不能在產品中使用(因爲還是千瘡百孔的^_^)。編譯運行後,調用wget http://127.0.0.1:6666/,服務器端就可以看到過來的http請求了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 4096
#define CONN_LIMIT 100
int main(int argc,char** argv)
{
    int listenfd,connfd;
    struct sockaddr_in servaddr;
    char buff[4096];
    int n;
    if((listenfd= socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        printf("create socket error:%s, errno=%d\n", strerror(errno),errno);
        exit(-1);
    }
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
    servaddr.sin_port= htons(8888);
    if((bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)))==-1)
    {
        printf("bind socket error:%s, errno=%d\n",strerror(errno),errno);
        exit(-1);
    }
    if (listen(listenfd,10)==-1)
    {
        printf("listen socket error:%s, errno=%d\n",strerror(errno),errno);
        exit(-1);
    }
    fd_set readfds,testfds;
    FD_ZERO(&readfds);
    FD_SET(listenfd,&readfds);
    int result;
    struct sockaddr_in client_address; 
    int nread;
    int fd;
    printf("===waiting for clients' request===\n");
    while(1)
    {
        testfds = readfds;
        result = select(CONN_LIMIT,&testfds,(fd_set*)0,(fd_set*)0,(struct timeval*)0);
        if(result<1)
        {
            printf("select error. reason is %s,errno = %d\n", strerror(errno),errno);
            exit(-1);
        }
        for(fd=0;fd<CONN_LIMIT;fd++)
        {
            if(!FD_ISSET(fd,&testfds))
                continue;
            if(fd==listenfd)
            {
                if((connfd = accept(listenfd,(struct sockaddr*)NULL,NULL))==-1)
                {
                    printf("accept socket error.reason: %s, errno=%d", strerror(errno),errno); 
                    continue;
                }
                else
                {
                    FD_SET(connfd, &readfds);
                    printf("adding client on fd %d/n",connfd);
                }
                continue;
            }
            n = recv(fd,buff,MAXLINE,0);
            buff[n]='\0';
            printf("recv msg from client:%s\n",buff);
            close(fd);
            FD_CLR(fd, &readfds); 
        }
    }
    close(listenfd);
}


7. poll 的例子。

#include <stropts.h>
#include <poll.h>
int poll(struct pollfd array[],unsigned long nfds,int timeout)
poll和select 不一樣,poll鎖構造一個pollfd數組,poofd定義了fd我們所要關心的條件。

struct pollfd

{

int fd;

short events;

short revents;

}


poll 的簡單服務器demo:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>

#define MAX_CONNECTION 200
#define MAX_BUFF 4096
char buff[MAX_BUFF];

int main()
{
    int listenfd,connfd;
    struct sockaddr_in servaddr;
    if(  (listenfd=socket(AF_INET,SOCK_STREAM,0))==-1    )
    {
        printf("create socket error:%s,errno=%d\n",strerror(errno),errno);
        exit(-1);
    }
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family =AF_INET;
    servaddr.sin_addr.s_addr =htonl(INADDR_ANY);
    servaddr.sin_port = htons(8888);
    if( bind(listenfd,(struct sockaddr*)&servaddr, sizeof(servaddr))==-1   )
    {
        printf("bind socket error:%s,errno=%d\n",strerror(errno),errno);
        exit(-1);
    }
    if (listen(listenfd,10)==-1)
    {
        printf("listen socket error:%s,errno=%d\n",strerror(errno),errno);
        exit(-1);
    }

    struct pollfd fds[MAX_CONNECTION ];
    memset(fds,0,sizeof(fds));
    fds[0].fd = listenfd;
    fds[0].events = POLLIN;
    int i=0;
    for(i=1; i<MAX_CONNECTION ; i++)
    {
        fds[i].fd = -1;
    }
    int timeout=3000;
    int sockMax = 0;
    int ret;
    while(1)
    {
        ret = poll(fds,sockMax+1,timeout);
        if(ret<0)
        {
            printf("select error\n");
            break;
        }
        else if(ret==0)
        {
            printf("timeout,continue\n");
            continue;
        }

        if(fds[0].revents & POLLIN)
        {
            connfd = accept(listenfd,NULL,NULL);
            if(connfd==-1)
            {
                printf("accept socket error.reason:%s,errno:%d",strerror(errno),errno);
                continue;
            }
            for(i=1;i<MAX_CONNECTION ;i++)
            {
                if(fds[i].fd<0)
                {
                    fds[i].fd =connfd;
                    break;
                }
            }
            if(i==MAX_CONNECTION )
            {
                printf("connections overflow\n");
                return -1;
            }
            fds[i].events = POLLIN;
            if(i>sockMax)
               sockMax = i;
			continue;
        }
		for(i=1; i<=sockMax; i++)
		{
			if(fds[i].fd < 0)
                continue;
			if (fds[i].revents & (POLLIN | POLLERR))
			{
				int n = recv(fds[i].fd,buff,MAX_BUFF,0);
				buff[n]='\0';
				printf("recv msg from client:%s\n",buff);
				close(fds[i].fd);
				fds[i].fd = -1;
			}
		}
    }
    
}

8. epoll

從linux2.6.2後,引入了一個性能更爲高效的epoll.

相對於select,有如下優勢:

1. 無需輪詢

2. epoll用共享內存的方式,避免了fd 的複製。

epoll的簡單demo:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define MAX_CONNECTION 100
#define MAX_LINE 4096

int bindListenSocket(int port)
{
    int listenfd;
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        printf("create socket error:%s,errno=%d\n", strerror(errno),errno);
        return -1;
    }
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family =AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
    {
        printf("bind socket error: %s,errno=%d\n",strerror(errno),errno);
        return -1;
    }
    if(listen(listenfd,10)==-1)
    {
        printf("listen socket error:%s,errno=%d\n",strerror(errno),errno);
        return -1;
    }

    return listenfd;

}

int setNoneBlock(int fd)
{
    int flag = fcntl(fd,F_GETFL,0);
    flag =flag| O_NONBLOCK;
    if(fcntl(fd,F_SETFL,flag)<0)
    {
        perror("fcntl to nonblock failed\n");
        return -1;
    }
    return 1;
}

int acceptAndAddFd(int listenfd,int epollfd)
{
    struct sockaddr_in remote_addr;
    int size = sizeof(struct sockaddr_in);
    int fd = accept(listenfd,(struct sockaddr*)&remote_addr,&size);
    if(fd==-1)
    {
        perror("accept socket error\n");
        return -1;
    }
    int ret = setNoneBlock(fd);
    if(ret==-1)
        return -1;
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;
    if(epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev)==-1)
    {
        perror("epoll add error for client fd");
        return -1;
    }
    return 1;
}

int handlefd(int fd,int epollfd)
{
    char buf[MAX_LINE];
    int n = recv(fd,buf,MAX_LINE,0);
    buf[n] = '\0';
    printf("recv msg from client:%s\n", buf);
    const char str[] = "God bless you!\n";
    if (send(fd, str,  sizeof(str),  0) == -1)
    {
        perror("send error\n");
        return -1;
    }
    close(fd);
    return 1;

}

int main()
{
    int listenfd =bindListenSocket(8080);
    if (listenfd==-1)
        return -1;
    int nfds;
    struct epoll_event ev, events[MAX_CONNECTION];
    int epollfd = epoll_create(MAX_CONNECTION);
    if(epollfd ==-1)
    {
        perror("create epoll fd error.");
        exit(-1);
    }
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;

    if(epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev)==-1)
    {
        perror("epoll add error.");
        exit(-1);
    }
    printf("serving...");
    int n =0;
    
    while(1)
    {
        nfds =epoll_wait(epollfd,events,MAX_CONNECTION,-1);
        if(nfds==-1)
        {
            perror("epoll error\n");
            exit(-1);
        }
        for(n=0;n<nfds;n++)
        {
            int interestingFd = events[n].data.fd;
            if(listenfd==interestingFd)
            {
                //accept
                acceptAndAddFd(listenfd,epollfd);
                continue;
            }
            //handle fd
            handlefd(interestingFd,epollfd );

        }
    }
    
}

8. readv 和writev

提供了分散區域集中寫, 分散區域集中讀的功能,demo.

#include <sys/uio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    struct iovec iov[2];
    char *buf1 = (char *)malloc(5);
    char *buf2 = (char *)malloc(1024);
    memset(buf1, 0, 5);
    memset(buf2, 0, 1024);
    iov[0].iov_base = buf1;
    iov[1].iov_base = buf2;
    iov[0].iov_len = 5;
    iov[1].iov_len = 1024;

    ssize_t nread, nwrite;
    nread = readv(STDIN_FILENO, iov, 2);
    if(nread == -1)
    {
        perror("readv error");
        return -1;
    }
    else
    {
        printf("readv:\n");
        printf("buf1 is: %s\t length is: %d\n",buf1, strlen(buf1));
        printf("buf2 is: %s\t length is: %d\n",buf2, strlen(buf2));
    }
    printf("writev:\n");
    nwrite = writev(STDOUT_FILENO, iov, 2);
    if(nwrite == -1)
    {
        perror("writev error");
        return -1;
    }
    free(buf1);
    free(buf2);
    exit(0);

}



發佈了45 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章