一、Unix的I/O
一个Unix文件就是一个m个字节的序列:B0,B1,B2,B3,…,Bk,…Bm-1 。所有的I/O设备,如网络、磁盘和终端,都被抽象模型化为文件,而所有的输入和输出都被当作对应文件读和写来执行。这种将设备优雅地映射为文件的方式,运行Unix内核引出一个简单、低级的应用接口,叫做Unix I/O.
1.1 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符
。内核记录有关打开这个文件的所有信息,应用程序只需要记住这个描述符。
Unix在创建每个进程开始时都有三个打开文件(三个文件描述符):标准输入(0),标准输出(1),错误输出(2).
1.2 改变当前的文件位置。对于每个打开的文件,内核保持着一种文件位置k,初始为0.这个文件位置是从文件开头起始位置的字节偏移量。
1.3 读写文件:
读操作:就是从文件拷贝n>0个字节到存储器,从当前文件位置k开始,然后将k增加到k+n。当文件为m字节,而k>=m是执行读操作会触发一个称为 end-of-file(EOF) 的条件,应用程序能检测到这个条件。
写操作:就是从存储器拷贝n>0个字节到一个文件,从当前文件位置k开始更新
4 关闭文件:当应用完成了对文件的访问后,它就通知内核关闭这个文件
二、共享文件
前面是一个进程操作文件时,内核和进程的交互。就像虚拟地址一样,为了更加高效的使用文件,引入了文件共享。操作方式就是通过I/O
描述符表:每个进程都有它独立的描述符表(属于进程,内核维护),它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表的一个表项。
文件表:打开文件的集合是由一张文件表来表示,所有的进程的 描述表
映射共享这张表(由内核控制,每个文件表唯一)。每个文件表的表项组成包括有:当前的文件位置、引用计数(当前指向该表项的文件描述符表项数),以及一个指向V-node表中对应表现的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它引用计数为零。
v-node表(文件的缓存表):同文件表一样,系统管理,每个 文件表
映射一个v-node表,每个文件唯一,作为磁盘上的文件在主存上的缓存,这是一个磁盘文件的内存缓存地址块,很多软件都可以直接不使用这个表,而直接刷新到磁盘。
文件描述符表和虚拟存储器一样,进程独有,内核维护,高效使用资源。文件表和物理存储器一样,所有进程共享,内核维护。
三、磁盘I/O
3.1 普通的I/O
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
char c;
int fdRead = open("foo.txt",O_RDONLY,0);//打开一个新文件,返回一个描述符
int fdWrite = open("foo1.txt",O_WRONLY,0);//打开一个新文件,返回一个描述符
while(read(fd1,&c,1)!=0)//一个字节一个自己读取到c
{
int sum = writ(fd2,&c,1);
}
close(fd1);
close(fd2);
return 0;
}
3.2 健壮的RIO操作
//csapp.h
void rio_readinitb(rio_t *rp,int fd);
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);
//csapp.c
ssize_t rio_readn(int fd,void *usrbuf,size_t n)
{
size_t nleft =n;
ssize_t nread;
char *bufp =usrbuf;
while(nleft>0)
{
if((nread = read(fd,bufp,nleft))<=0)
{
if(errno EINTR)
nread=0;
else
return -1;
}
else if (nread 0)
break;
nleft -=nread;
bufp +=nread;
}
return (n-nleft);
}
ssize_t rio_write(int fd,void *usrbuf,size_t n)
{
size_t nleft=n;
ssize_t nwritten;
char *bufp =usrbuf;
while()
{
if((nwritten =write(fd,bufp,nleft))>0)
{
if(errnoEINTR)
nwritten = 0;
else
return -1;
}
nleft -=nwritten;
bufp +=nwritten;
}
return n;
}
//main.c
#inclue "csapp.h"
int main(int argc,char **argv)
{
int n;
rio_t rio;
char buf[MAXLINE];
Rio_readinitb(&rio,STDIN_FILENO);
while((n = Rio_readlineb(&rio,buf,MAXLINE))!=0)
Rio_writen(STDOUT_FILENO,buf,n);
}
四、网络I/O
服务器:
socket:创建一个描述符socketfd
bind:即将端口绑定在socketfd
listen:将socketfd从一个主动套接字(应该是有一个文件表、磁盘空间和其相关联)转化为一个 监听套接字<没有真实的磁盘空间> listenfd(没有新建)
accpet:接受一个请求,打开一个 新
的已连接描述符connfd.
客服端:
socket:创建一个描述符socketfd
connect:发送一个请求到listenfd,返回一个clientfd(没有新建)
服务器和客服端就是 clientfd和connfd之间通信
4.1 服务器
//listen.c
int Open_listenfd(int port)
{
int listenfd,optval=1;
struct sockaddr_in serveraddr;
if((listenfd = socket(AF_INET,SOCK_STREAM,0))<0)//开的一个文件描述符
return -1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(const void *)&optval ,sizeof(int))<0)//修改文件描述符的选项值
return -1;
bzero((char *)) &serveraddr,sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port=htons((unsigned short)port);
if(bind(listenfd,(SA *)&serveraddr,sizeof(serveraddr))<0)//将port绑定到文件描述符
return -1
if(listen(listenfd,LISTENQ)<0)
return -1;
return listenfd;
}
//ehco.c
#include "csapp.h"
void echo(int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio,connfd);
while((n=Rio_readlineb(&rio,buf,MAXLINE))!=0){
printf("server received %d bytes
",n);
Rio_write(connfd,buf,n);
}
}
//echoserver.c
#include "csapp.h"
void echo(int connfd);
int main(int argc,char **argv)
{
int listenfd,connfd,port,clientlen;
struct sockaddr_in clientaddr;
struct hostent *hp;
char *haddrp;
if(argc!=2){
fprintf(stderr,"usage:%s <port>
",arg[0]);
exit(0);
}
port = atoi(argv[1]);
listenfd = Open_listendfd(port);
while(1){
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd,(SA *)&clientaddr,&clientlen);
hp = Gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr,
sizeof(clientaddr.sin_addr.s_addr),AF_INET);
haddrp = inet_ntoa(clientaddr.sin_addr);
printf("server connected to %s (%s)
",hp-<h_name,haddrp);
echo(connfd);
Close(connfd);
}
exit(0);
}
4.2 客服端
//openClient.c
int Open_clientfd(int port)
{
int clientfd;
struct hostnet *hp;
struct sockaddr_in serveraddr;
if((clientfd = socket(AF_INET,SOCK_STREAM,0))<0)//开的一个文件描述符
return -1;
if((hp=gethostbyname(hostname))NULL)//
return -2;
bzero((char *)) &serveraddr,sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bcopy((char *)hp->h_addr_list[0],
(char *)&serveraddr.sin_addr.saddr,hp->h_length);
serveraddr.sin_port =htons(port);
if(connect(clientfd,(SA * )&serveraddr,sizeof(serveraddr))<0)
return -1;
return clientfd;
}
//main.c
int main(int argc,char **argv)
{
int clientfd,portl
char *host,buf[MAXLINE];
rio_t rio;
if(argc!=3){
fprintf(stderr,"usage:%s<host> <port>
",argv[0]);
exit(0);
}
host = argv[1];
port = atoi(argv[2]);
clientfd = Open_clientfd(host,port);
Rio_readinitb(&rio,clentfd);
while(Fgets(buf,MAXLINE,stdin)!=NULL){
Rio_write(clientfd,buf,strlen(buf));
Rio_readlineb(&rio,buf,MAXLINE);
Fputs(buf,stdout);
}
Close(clientfd);
exit(0)
}