六.计算机系统之外部通信

一、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)
}
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...