1. 程式人生 > >linux高階程式設計day10 筆記 (轉)

linux高階程式設計day10 筆記 (轉)

一.TCP的程式設計模型
 回顧:
  UDP模型的UML圖
  TCP模型的UML圖
 案例1:
  TCP的伺服器(在案例中使用瀏覽器作為客戶程式)  
 socket建立伺服器的檔案描述符號緩衝
 bind把IP地址與埠設定到檔案描述符號中
 listen負責根據客戶連線的不同IP與埠,負責生成對應的檔案描述符號及其資訊
 accept一旦listen有新的描述符號產生就返回,否則阻塞。
//tcpserver.c#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in
.h>
#include <arpa/inet.h>
main()
{
    int serverfd;
    int cfd;
    int a;
    struct sockaddr_in sadr;
    struct sockaddr_in cadr;
    socklen_t len;
    int r;
    char buf[1024];
    //1.socket    serverfd=socket(AF_INET,SOCK_STREAM,0);
    if(serverfd==-1) printf("1:%m\n"),exit(-1);
    printf("建立伺服器socket成功!\n");
    //
2.bind    sadr.sin_family=AF_INET;
    sadr.sin_port=htons(9999);
    inet_aton("192.168.180.92",&sadr.sin_addr);
    r=bind(serverfd,
            (struct sockaddr*)&sadr,sizeof(sadr));
    if(r==-1) printf("2:%m\n"),exit(-1);
    printf("伺服器地址繫結成功!\n");
    
    //3.listen    r=listen(serverfd,10);
    if
(r==-1) printf("3:%m\n"),exit(-1);
    printf("監聽伺服器成功!\n");
    
    //4.accept    len=sizeof(cadr);
    cfd=accept(serverfd,
            (struct sockaddr*)&cadr,&len); //每接受一個新的連線,就會返回一個新的檔案描述符,來分辨是哪個連線。    printf("有人連線:%d,IP:%s:%u\n",
            cfd,inet_ntoa(cadr.sin_addr),
            ntohs(cadr.sin_port));        
    
    //5.處理代理客戶描述符號的資料    while(1)
    {
        r=recv(cfd,&a,4,MSG_WAITALL);        
        if(r>0)
        {
            //buf[r]=0;            printf("::%d\n",a);
        }
        
        if(r==0)
        {
            printf("連線斷開!\n");
            break;
        }
        if(r==-1)
        {
            printf("網路故障!\n");
            break;
        }
    }
    close(cfd);
    close(serverfd);
}

案例2:
   每個客戶的代理描述符號的通訊

二.TCP通訊特點(相對於UDP)
 案例3:
  有連線:主要連線後,傳送資料不用指定IP與埠
  資料無邊界:TCP資料流,非資料報文.
  描述符號雙工:
  資料準確:TCP協議保證資料時完全正確
 案例4:
  使用TCP傳送資料注意:
    不要以為固定長的資料,一定接收正確,要求使用MSG_WAITALL(必須等待得到指定快取長度recv才返回)
  
 案例5:
  TCP資料傳送的分析:
    定長資料:
      基本資料int short long float  double
      結構體資料struct
      建議使用MSG_WAITALL
    
    不定長資料:
      字串資料以及檔案資料等不固定長度的資料怎麼傳送?   
      制定資料包:
        頭:大小固定(資料大小)
        體:大小變化(資料)  
 案例6:
   使用TCP傳送檔案
   定義檔案資料包.
     int 資料大小;
     char[]資料
       
   傳遞檔名
   傳遞資料(迴圈)
   傳遞0長度的資料表示檔案結束
程式碼如下:


//demo1Client.c
//傳送端的程式碼#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
main()
{
    //1. 建立socket
    
//2. 連線到伺服器
    
//3. 開啟檔案
    
//4. 傳送檔名
    
//5. 迴圈傳送檔案
    
//6. 讀取到檔案尾,傳送0資料包    int sfd; //socket描述符    int ffd; //檔案描述符    int size;   //讀取和傳送檔案的長度    int r;  //函式返回值    
    int  len;  //要傳送的檔名的長度    char buf[128]; //資料的快取    
    struct sockaddr_in dr;  //網路地址    char filename[]="udp_a.c";  //檔名
    
    
//1.建立socket    sfd=socket(AF_INET,SOCK_STREAM,0);
    if(sfd==-1) 
        printf("1:%m\n"),exit(-1);
    printf("socket成功!\n");
    //2.連線到伺服器    dr.sin_family=AF_INET;
    dr.sin_port=htons(9988);
    inet_aton("192.168.180.92",&dr.sin_addr);
    r=connect(sfd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) 
        printf("2:%m\n"),close(sfd),exit(-1);    
    printf("connect成功!\n");
    //3.開啟檔案    ffd=open(filename,O_RDONLY);
    if(ffd==-1) 
        printf("3:%m\n"),close(sfd),exit(-1);
    printf("open檔案成功!\n");
    //4.傳送檔名    len=strlen(filename);    
    r=send(sfd,&len,sizeof(len),0);//傳送檔名長度(告訴流,檔名佔多長)    r=send(sfd,filename,len,0);//傳送檔名     if(r==-1)
    printf("4:%m\n"),close(ffd),close(sfd),exit(-1);
    printf("傳送檔名成功!\n");
    //5.迴圈傳送資料    while(1)
    {
        size=read(ffd,buf,128);
        if(size==-1) break//read錯誤,跳出迴圈        if(size==0) break;  //讀到檔案尾,跳出迴圈        if(size>0)
        {
            //先發送資料的長度,再發送資料
            
//傳送資料長度            r=send(sfd,&size,sizeof(size),0);
            if(r==-1) break;
            r=send(sfd,buf,size,0);//傳送資料            if(r==-1) break;
        }
    }
    //6.讀取到檔案尾,傳送0資料包    size=0;
    r=send(sfd,&size,sizeof(size),0);
    close(ffd);
    close(sfd);
    printf("OK!\n");
}
//demo1server.c
//接收伺服器的程式碼#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
main()
{
    //1. 建立伺服器socket
    
//2. 繫結IP地址與埠
    
//3. 監聽
    
//4. 接收連線
    
//5. 接收檔名
    
//6. 建立檔案
    
//7. 迴圈接收檔案資料    
    int sfd, cfd, ffd;
    int r;
    int len;
    char buf[128];  //傳送端定義的快取大小是128,這裡最好不要小於128    char filename[100];
    struct sockaddr_in dr;
    //1. 建立伺服器socket    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd == -1)
        printf("1:%m\n"), exit(-1);
    printf("socket伺服器建立成功!\n");
    //2. 繫結IP地址與埠    dr.sin_family = AF_INET;
    dr.sin_port = htons(9988);
    inet_aton("192.168.180.92", &dr.sin_addr); //dr.sin_addr.s_addr = inet_addr("192.168.180.92");注意區別    r = bind(sfd, (struct sockaddr*)&dr, sizeof(dr));
    if(r == -1)
        printf("2:%m"), close(sfd), exit(-1);
    printf("繫結地址成功!\n");
    //3. 監聽    r = listen(sfd, 10);
    if(r == -1)
        printf("3:%m\n"), close(sfd), exit(-1);
    printf("監聽成功!\n");
    //4. 接收連線    cfd = accept(sfd, 0, 0); //這裡我們不關心傳送端的IP等資訊,所以後面兩個引數都為0。這裡返回一個新的描述符,代表接收的連線    if(cfd == -1)
        printf("4:%m\n"), close(sfd), exit(-1);
    printf("開始接收檔案!\n");
    //5. 接收檔名    r = recv(cfd, &len, sizeof(len), MSG_WAITALL);  //接收檔名的長度    r = recv(cfd, filename, len, MSG_WAITALL);  //根據檔名長度,接收檔名    filename[r] = 0;  //在檔名後面加結束符
    
//6. 建立檔案    ffd = open(filename, O_CREAT|O_RDWR, 0666);   //如果檔案存在,直接覆蓋.不要和傳送檔案放在同一個目錄執行,會覆蓋傳送檔案    if(ffd == -1)
        printf("6:%m\n"), close(sfd), close(cfd), exit(-1);
    printf("建立檔案成功!\n");
    //7. 迴圈接收檔案資料    while(1)
    {
        r = recv(cfd, &len, sizeof(len), MSG_WAITALL);
        if(len == 0)
            break//長度為0,表示檔案傳送完畢的訊號        r = recv(cfd, buf, len, MGS_WAITALL);
        r = write(ffd, buf, len);
    }
    close(ffd);
    close(cfd);
    close(sfd);
    printf("接收資料完畢!\n");
}

PS:UDP面向無連線,TCP面向連線,所以推薦UDP不用connect,直接sendto, 而TCP則先連線,然後send,而不是sendto。

三.TCP伺服器程式設計模式
  TCP的伺服器端維護多個客戶的網路檔案描述符號.
  對伺服器多個客戶描述符號同時做讀操作,是不可能.需要多工模型完成.
  多工模型?
  1.多程序
  2.IO的非同步模式(select模式/poll模式)
  3.多執行緒模式  
  4.多程序池
  5.執行緒池

四.綜合應用--多程序應用
  1.怎樣使用多程序
  2.多程序的缺陷,以及怎麼解決

小例子:用TCP寫一個聊天程式
   客戶端
     2.1.建立socket
     2.2.連線伺服器
     2.3.建立CURSES介面
     2.4.建立子程序
     2.5.在父程序中,輸入,傳送聊天資訊
     2.6.在子程序中,接收伺服器傳遞其他客戶聊天資訊


//chatclient.c
//聊天程式客戶端#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <curses.h>
#include <signal.h>
WINDOW*winfo,*wmsg;
int fd;
int r;
struct sockaddr_in dr;
int isover=1;

int initSocket();   //初始化:建立描述符,繫結IPvoid initUI();   //初始化curses介面void destroy();  //清理:釋放UI, 關閉網路void handle(int s)
{
    int status;
    wait(&status);
    destroy();
    exit(-1);    
}
main()
{    
    //printf("網路初始化成功!\n");    initUI();
    r=initSocket();    
    if(r==-1) exit(-1);
    signal(SIGCHLD,handle);
    if(fork())
    {
        //父程序,輸入,傳送        char buf[256];
        while(1)
        {
            mvwgetstr(wmsg,1,1,buf);
            //buf[r]=0;            send(fd,buf,strlen(buf),0);            
            //wclear(wmsg);
            
//box(wmsg,0,0);            refresh();
            wrefresh(wmsg);
            wrefresh(winfo);
        }
    }
    else
    {
        
        //子程序,接收,顯示        char buf[256];
        int line=1;
        while(1)
        {            
            r=recv(fd,buf,255,0);
            if(r==-1) break;
            if(r==0) break;
            buf[r]=0;
            mvwaddstr(winfo,line,1,buf);
            line++;            
            if(line>=(LINES-3))
            {
                wclear(winfo);
                line=1;
                box(winfo,0,0);                
            }
            
            
            wmove(wmsg,1,1);
            touchwin(wmsg);
            refresh();
            wrefresh(winfo);            
            wrefresh(wmsg);
        }
        exit(-1);
    }
        
    destroy();
}
void destroy()
{
    close(fd);
    endwin();
}
void initUI()
{
    initscr();
    winfo=derwin(stdscr,(LINES-3),COLS,0,0);
    wmsg=derwin(stdscr,3,COLS,LINES-3,0);
    keypad(stdscr,TRUE);
    keypad(wmsg,TRUE);
    keypad(winfo,TRUE);
    box(winfo,0,0);
    box(wmsg,0,0);
    refresh();
    wrefresh(winfo);
    wrefresh(wmsg);
}

int initSocket()
{
    fd=socket(AF_INET,SOCK_STREAM,0);
    if(fd==-1) return -1;
        
    dr.sin_family=AF_INET;
    dr.sin_port=htons(9989);
    dr.sin_addr.s_addr=inet_addr("192.168.180.92");
    r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1)
    {
        close(fd);
        return -1;
    }
    return 0;  //fd是全域性變數,不用返回。初始化成功,返回0}
//chatserver.c
//聊天程式伺服器端#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mman.h>
int sfd;
int *fds;//存放所有客戶代理描述符號int idx=0;//客戶在陣列中下標struct sockaddr_in dr;
int r;
main()
{
    //1. 建立伺服器socket
    
//2. 繫結地址
    
//3. 監聽
    
//4. 迴圈接收客戶連線
    
//5. 建立一個子程序
    
//6. 子程序任務:接收客戶資料並且廣播
    
    
    
//1.建立伺服器 socket    fds=mmap(0,4*100,PROT_READ|PROT_WRITE,
        MAP_ANONYMOUS|MAP_SHARED,0,0);
    bzero(fds,sizeof(fds));
    sfd=socket(AF_INET,SOCK_STREAM,0);
    if(sfd==-1) printf("1:%m\n"),exit(-1);    
    printf("socket OK!\n");
    //2.繫結地址    dr.sin_family=AF_INET;
    dr.sin_port=htons(9989);
    dr.sin_addr.s_addr=inet_addr("192.168.180.92");
    r=bind(sfd,(struct sockaddr*)&dr,sizeof(dr));
    if(r==-1) printf("2:%m\n"),exit(-1);
    printf("bind ok!\n");
    //3.監聽    r=listen(sfd,10);
    if(r==-1) printf("3:%m\n"),exit(-1);
    printf("listen ok!\n");
    //4.迴圈接收客戶連線    while(1)
    {
        fds[idx]=accept(sfd,0,0);
        if(fds[idx]==-1) break;
        printf("有客戶連線:%d\n",fds[idx]);
        //5.建立一個子程序        if(fork())
        {
            idx++;
            continue;
        }
        else
        {        
            //6.子程序任務:接收客戶資料並且廣播            char buf[256];
            int i;
            printf("開始接收客戶資料:%d\n",fds[idx]);
            while(1)
            {
                //接收客戶資料                r=recv(fds[idx],buf,255,0);
                printf("%d\n",r);
                if(r==0)
                {
                    printf("有客戶退出\n");
                    close(fds[idx]);
                    fds[idx]=0;
                    break;                    
                }
                if(r==-1)
                {
                    printf("網路故障\n");
                    close(fds[idx]);
                    fds[idx]=0;
                    break;
                }
                buf[r]=0;
                printf("來自客戶的資料:%s\n",buf);
                //廣播                for(i=0;i<100;i++)
                {
                    if(fds[i]>0)
                    {
                        send(fds[i],buf,r,0);
                    }    
                }
            }
            exit(0);
        }
    }
    close(sfd);    
}

總結:
   建立socket
   繫結地址
   監聽
   迴圈接收客戶連線
   為客戶建立子程序
   在子程序接收該客戶的資料,並且廣播

總結:
  1.TCP的四大特點
  2.TCP的資料接收:固定長與變長資料的接收
  3.TCP的伺服器多程序處理
     問題:多程序由於程序資源結構獨立.
         新程序的檔案描述符號的環境在老程序無法訪問?

作業:
  思考:
    有什麼程式設計技巧可以解決程序的檔案描述符號的一致?
    
  作業:
    完成TCP的聊天程式.
      1.資料能執行
      2.處理僵死程序
      3.伺服器退出,客戶也能正常結束
      4.客戶退出,伺服器也能夠正確結束客戶連線.