2014年2月18日 星期二

Socket

http://pws.niu.edu.tw/~ttlee/os.101.1/day/socket/

Socket的由來:
Socket 是一組具體的 UNIX 系統呼叫 80 年代美國的研究單位 研究在UNIX中接收 TCPIP 軟體的問題 並使其亦適應於其他場合 所以研究者們創造一種新的通訊介面 盡可能使用現有的UNIX系統呼叫 然後為支援那些不易被整合於現有函式庫的 TCPIP 函數 新定義了一些系統呼叫函數 這便是承接口介面(Socket Interface)至今已被廣泛的認可與應用, 成為一種標準了
什麼是TCP/IP:
TCP/IP(Transmission Control Protcol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的



各個層介紹:
l   應用層 (Application Layer)
各種網路應用如 telnetFtpWWWEmailNewsBBS等。
l   傳輸層 (Transport Layer)
負責網路連接之建立、分割/組合傳送訊息、提供使用節點間資料流量的控制、決定提供網路資料傳送的服務品質等。提供可靠、有效率的連接給網路應用節點使用。
l   網路層 (IP Network Layer)
承接傳輸層傳送的封包,依所欲傳送的位址自動Route轉送路徑、轉換不同協定的封包格式、監控網路流量狀況,動態建構網路整體拓樸架構,動態提供最佳的Route資料傳送路徑。Internet網路層協定為 IP(Internet Protocol)
l   鏈接層 (Network Access Layer)
承接網路層傳送的封包,做更細的資料框(Frame)的切割/組合、檢出/更正錯誤的傳送資料、運用ACK 判斷資料的正常傳送與控制傳送速度,及負責傳送由0 1組成的原始網路資料

協議的關係圖:

Socket是什麼呢?
Socket是應用層與TCP/IP協議通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議隱藏在Socket介面後面,對用戶來說,一組簡單的介面就是全部,Socket去組織數據,以符合指定的協議。

Socket在哪裡?





Socket的建立:

為了建立Socket,程式可以調用Socket函數,該函數返回一個類似文件描述符的控制碼。

socket 函數原型為:
  int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM SOCK_DGRAMSocket介面還定義了原始SocketSOCK_RAW),允許程式使用低層協議;protocol通常賦值"0"Socket()調用返回一個整型socket描述符,你可以在面的調用使用它。
Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。Socket執行體為你管理描述符表。
兩個網絡程式之間的一個網絡連接包括五種資訊:通信協議、本地協議位址、本地主機埠、遠端主機位址和遠端協議埠。Socket數據結構中包含這五種資訊。

Socket的配置:

通過socket調用返回一個socket描述符,在使用socket進行網絡傳輸以前,必須配置該socket
面向連接的socket客戶端通過調用Connect函數在socket數據結構中保存本地和遠端資訊。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用bind函數來配置本地資訊。
Bind函數將socket與本機上的一個埠相關聯,隨你就可以在該埠監聽服務請求。Bind函數原型為:
  int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
  Sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP位址及埠號等資訊的sockaddr類型的指針;addrlen常被設置為sizeof(struct sockaddr)
  struct sockaddr結構類型是用來保存socket資訊的:
  struct sockaddr {
   unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
  sa_family一般為AF_INET,代表InternetTCP/IP)地址族;sa_data則包含該socketIP位址和埠號。
  另外還有一種結構類型:
  struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 埠號 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充以保持與struct sockaddr同樣大小 */
  };
  這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()memset()函數將其置為零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味著如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向 sockaddr_in的指針轉換為指向sockaddr的指針;或者相反。
  使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被佔用的埠號:
  my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的埠號 */
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置為0,函數會自動為你選擇一個未佔用的埠來使用。同樣,通過將my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數是需要將sin_portsin_addr轉換成為網絡字節優先順序;而sin_addr則不需要轉換。
  計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對在內部是以低位元字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
  下麵是幾個字節順序轉換函數:
htonl():把32位值從主機字節序轉換成網絡字節序
htons():把16位值從主機字節序轉換成網絡字節序
ntohl():把32位值從網絡字節序轉換成主機字節序
ntohs():把16位值從網絡字節序轉換成主機字節序
  Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置為相應的錯誤號。需要注意的是,在調用bind函數時一般不要將埠號置為小1024的值,因為11024是保留埠號,你可以選擇大1024中的任何一個沒有被佔用的埠號。


連結建立:
面向連接的客戶程式使用Connect函數來配置socket並與遠端服務器建立一個TCP連接,其函數原型為:
  int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfdsocket函數返回的socket描述符;serv_addr是包含遠端主機IP位址和埠號的指針;addrlen是遠端地質結構的長度。Connect函數在出現錯誤時返回-1,並且設置errno為相應的錯誤碼。進行客戶端程式設計無須調用bind(),因為這種情況下只需知道目的機器的IP位址,而客戶通過哪個埠與服務器建立 連接並不需要關心,socket執行體為你的程式自動選擇一個未被佔用的埠,並通知你的程式數據什時候到打斷口。
    Connect函數啟動和遠端主機的直接連接。只有面向連接的客戶程式使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啟動一個連接,它只是被動的在 協議埠監聽客戶的請求。
    Listen函數使socket處被動的監聽模式,並為該socket建立一個輸入數據隊列,將到達的服務請求
保存在此隊列中,直到程式處理它們。
  int listen(int sockfd int backlog);
    SockfdSocket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連 接請求將在隊列中等待accept()它們(參考下文)。Backlog對隊列中等待服務的請求的數目進行了限制, 大多數系統缺省值為20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯資訊。當出現錯誤時listen函數返回-1,並置相應的errno錯誤碼。  
accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列,服務器就調用accept函數,然後睡眠並等待客戶的連接請求。
  int accept(int sockfd, void *addr, int *addrlen);    
    sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的資訊(某台主機從某個埠發出該請求);addrten通常為一個指向值為
sizeof(struct sockaddr_in)
的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。
  首先,當accept函數監視的socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯系起來,收到服務請求的初始socket仍可以繼續在以前的 socket上監聽, 同時可以在新的socket描述符上進行數據傳輸操作。

數據傳輸:
Send()recv()這兩個函數用面向連接的socket上進行數據傳輸。
  Send()函數原型為:
  int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;Len是以字節為單位的數據的長度;flags一般情況下置為0(關該參數的用法可參照man手冊)。
  Send()函數返回實際上發送出的字節數,可能會少你希望發送的數據。在程式中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
  recv()函數原型為:
  int recv(int sockfd,void *buf,int len,unsigned int flags);
  Sockfd是接受數據的socket描述符;buf 是存放接收數據的緩沖區;len是緩沖的長度。Flags也被置為0
Recv()返回實際上接收的字節數,當出現錯誤時,返回-1並置相應的errno值。
Sendto()recvfrom()用在無連接的數據報socket方式下進行數據傳輸。由本地socket並沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);   該函數比send()函數多了兩個參數,to表示目地機的IP位址和埠號資訊,而tolen常常被賦值為
sizeof (struct sockaddr)Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1
  Recvfrom()函數原型為:
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
  from是一個struct sockaddr類型的變量,該變量保存源機的IP位址及埠號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。
Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno
如果你對數據報socket調用了connect()函數時,你也可以利用send()recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源位址資訊。
結束傳輸
  當所有的數據操作結束以,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
 close(sockfd);
  你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
  int shutdown(int sockfd,int how);
  Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
  0-------不允許繼續接收數據
  1-------不允許繼續發送數據
   
2-------不允許繼續發送和接收數據,
    均為允許則調用close ()
  shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno



















下面兩隻程式在 linux 利用 socket 來傳送檔案,目前設定為讀取本機端的 TEST.MP3 送到本機端的 GET.MP3

 Server.C
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/stat.h>

int main()
{
 int sockfd, new_fd, numbytes, sin_size;
 char buf[1000000];
 struct sockaddr_in my_addr;
 struct sockaddr_in their_addr;
 struct stat filestat;
 FILE *fp;
   //TCP socket
  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
            perror("socket");
            exit(1);
}
  //Initail, bind to port 2323
 my_addr.sin_family = AF_INET;
 my_addr.sin_port = htons(2324);
 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 bzero( &(my_addr.sin_zero), 8 );
//binding
 if ( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1 ){
         perror("bind");
         exit(1);
  }  
 //Start listening
 if ( listen(sockfd, 10) == -1 ){
          perror("listen");
          exit(1);
 }  
 //Get file stat
 if ( lstat("TEST.MP3", &filestat) < 0){
          exit(1);
 } printf("The file size is %lu\n", filestat.st_size);
  fp = fopen("TEST.MP3", "rb");
 //Connect
 if ( (new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size)) == -1 ){
           perror("accept"); exit(1);
 }
//Sending file
 while(!feof(fp)){
 numbytes = fread(buf, sizeof(char), sizeof(buf), fp);
 printf("fread %d bytes, ", numbytes);
 numbytes = write(new_fd, buf, numbytes);
 printf("Sending %d bytes\n",numbytes);
 }

 close(new_fd);
 close(sockfd);
 return 0;
 }

解釋一下 Sending file 的部份:

除非你打算把程式碼寫死成為只能傳送特定大小的檔案,否則,為了讓程式可以傳送任意大小的檔案,我們會在 server 端每次讀取一部分檔案出來傳送,一直讀到檔案結尾為止,所以我們需要一個 while 迴圈,迴圈終止的條件是『fread』讀到檔案結尾。

1.先用 fread fp 開啟的檔案讀到 buf 裡面,讀取 sizeof(char) 大小,sizeof(buf) 次。為避免不同平臺可能會有不同 char 大小問題,一定要用 sizeof(char)來寫比較保險。
2.印出目前讀到多少bytes
3. write buf 裡面的資料寫入上面已經連線好了的 new_fd 這個檔案描述子,第三個參數是用來限制要寫入多少 bytes 過去,因為不見得每次都可以把 buf 填滿,例如每次都讀出1M來傳送,則最後通常會有不足1M的剩餘檔案部份需要傳送,所以不能寫 sizeof(buf) ,所以利用 fread 的回傳值來判斷要從 buf 裡面讀出多少來傳送,讀太多的話會送垃圾資訊過去。把傳送過去的大小的值回傳到numbytes裡。
4.印出送了多少 bytes 到遠端。
Client.C

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main(int argc, char* argv[])
{
               int sockfd, numbytes;
               char buf[1000000];
               struct sockaddr_in address;
               FILE *fp;
               //TCP socket
               if ( ( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 ){
                               perror("socket");
                               exit(1);
               }
               //Initial, connect to port 2323
               address.sin_family = AF_INET;
               address.sin_port = htons(2324);
               address.sin_addr.s_addr = inet_addr("127.0.0.1");
               bzero( &(address.sin_zero), 8 );
               //Connect to server
               if ( connect(sockfd, (struct sockaddr*)&address, sizeof(struct sockaddr)) == -1){
                               perror("connect");
                               exit(1);
               }
               //Open file
               if ( (fp = fopen("GET.MP3", "wb")) == NULL){
                               perror("fopen");
                               exit(1);
               }
               //Receive file from server
               while(1){
                               numbytes = read(sockfd, buf, sizeof(buf));
                               printf("read %d bytes, ", numbytes);
                               if(numbytes == 0){
                                              break;
                               }
                               numbytes = fwrite(buf, sizeof(char), numbytes, fp);
                               printf("fwrite %d bytes\n", numbytes);
               }
               fclose(fp);
               close(sockfd);
               return 0;
}
解釋一下 Receive file from server 的部份:
所以這邊跟 server 都同樣會需要一個 while 迴圈,控制迴圈結束的條件是『接收到大小為零』,這代表檔案已經傳送完了!
1. read 將上面已經連線了的 sockfd 檔案描述子的內容讀取到 buf 裡面,每次最多寫入 sizeof(buf) bytes。將回傳值存在 numbytes 這個變數裡,下兩行的寫檔會需要這個數值。
2.印出本次 read 接收到了多少 bytes
3.如果接收到的 bytes 為零的話就結束迴圈,因為我們認為這樣的條件代表檔案傳送完了。
4. fwrite buf 的資料寫入 fp ,每次寫入 sizeof(char) bytes,寫入 numbytes 次,寫入太多的話會寫入垃圾資訊,所以要用剛才 read 的回傳值來控制要寫入多少 bytes
5.印出本次 fwrite 寫入了多少 byets

使用socketLinux上的C語言檔傳輸順序伺服器和客戶

伺服器端程式的編譯
gcc -o file_server file_server.c
用戶端程式的編譯
gcc -o file_client file_client.c
伺服器程式和用戶端程應當分別運行在2台電腦上.
伺服器端程式的運行,在一個電腦的終端執行
./file_server用戶端程式的運行,在另一個電腦的終端中執行
./file_client運行伺服器程式的電腦的IP位址
根據提示輸入要傳輸的伺服器上的檔,該檔在伺服器的運行目錄上。在實際編程和測試中,可以用2個終端代替2個電腦,這樣就可以在一台電腦上測試網路程式,
伺服器端程式的運行,在一個終端執行
./file_server
用戶端程式的運行,在另一個終端中執行
./file_client 127.0.0.1
說明: 任何電腦都可以通過127.0.0.1訪問自己. 也可以用電腦的實際IP位址代替127.0.0.1
//////////////////////////////////////////////////////////////////////////////////////
// file_server.c 檔傳輸順序伺服器示例
//////////////////////////////////////////////////////////////////////////////////////
//本檔是伺服器的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero

#define HELLO_WORLD_SERVER_PORT    6666
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    //
設置一個socket位址結構server_addr,代表伺服器internet位址,
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr)); //
把一段記憶體區的內容全部設置為0
    server_addr.sin_family = AF_INET; //
位址資料族系,同樣設定為AF_INET
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
//主機IP位址 in_addr資料格式
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
//主機開啟的通訊埠號 htons() 寫入
    //創建用於internet的流協議(TCP)socket,server_socket代表伺服器socket
    int server_socket = socket(PF_INET,SOCK_STREAM,0);
    if( server_socket < 0)
    {
        printf("Create Socket Failed!");
        exit(1);
    }
     //
socketsocket位址結構聯繫起來
    if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    {
        printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
        exit(1);
    }
    //server_socket用於監聽
    if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
    {
        printf("Server Listen Failed!");
        exit(1);
    }
    while (1) //
伺服器端要一直運行
    {
        //
定義用戶端的socket位址結構client_addr
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);
        //接受一個到server_socket代表的socket的一個連接
        //
如果沒有連接請求,就等待到有連接請求--這是accept函數的特性
        //accept
函數返回一個新的socket,這個socket(new_server_socket)用於同連接到的客戶的通信
        //new_server_socket
代表了伺服器和用戶端之間的一個通信通道
        //accept
函數把連接到的用戶端資訊填寫到用戶端的socket位址結構client_addr
        int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
        if ( new_server_socket < 0)
        {
            printf("Server Accept Failed!\n");
            break;
        }
        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
        if (length < 0)
        {
            printf("Server Recieve Data Failed!\n");
            break;
        }
        char file_name[FILE_NAME_MAX_SIZE+1];
        bzero(file_name, FILE_NAME_MAX_SIZE+1);
        strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
//        int fp = open(file_name, O_RDONLY);
//        if( fp < 0 )
        FILE * fp = fopen(file_name,"r");
        if(NULL == fp )
        {
            printf("File:\t%s Not Found\n", file_name);
        }
        else
        {
            bzero(buffer, BUFFER_SIZE);
            int file_block_length = 0;
//            while( (file_block_length = read(fp,buffer,BUFFER_SIZE))>0)
            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
            {
                printf("file_block_length = %d\n",file_block_length);
                //
發送buffer中的字串到new_server_socket,實際是給用戶端
                if(send(new_server_socket,buffer,file_block_length,0)<0)
                {
                    printf("Send File:\t%s Failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
//            close(fp);
            fclose(fp);
            printf("File:\t%s Transfer Finished\n",file_name);
        }
        //
關閉與用戶端的連接
        close(new_server_socket);
    }
    //
關閉監聽用的socket
    close(server_socket);
    return 0;
}

//////////////////////////////////////////////////////////////////////////////////////
// file_client.c
檔傳輸用戶端程式示例
//////////////////////////////////////////////////////////////////////////////////////
//
本檔是客戶機的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero

#define HELLO_WORLD_SERVER_PORT    6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: ./%s ServerIPAddress\n",argv[0]);
        exit(1);
    }
    //設置一個socket位址結構client_addr,代表客戶機internet位址,
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr)); //
把一段記憶體區的內容全部設置為0
    client_addr.sin_family = AF_INET;    //internet
協議族
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY
表示自動獲取本機位址
    client_addr.sin_port = htons(0);    //0
表示讓系統自動分配一個空閒埠
    //
創建用於internet的流協議(TCP)socket,client_socket代表客戶機socket
    int client_socket = socket(AF_INET,SOCK_STREAM,0);
    if( client_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }
    //
把客戶機的socket和客戶機的socket位址結構聯繫起來
    if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
    {
        printf("Client Bind Port Failed!\n");
        exit(1);
    }
  //
設置一個socket位址結構server_addr,代表伺服器的internet位址,
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //
伺服器的IP位址來自程式的參數
    {
        printf("Server IP Address Error!\n");
        exit(1);
    }
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t server_addr_length = sizeof(server_addr);
    //
向伺服器發起連接,連接成功後client_socket代表了客戶機和伺服器的一個socket連接
    if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
    {
        printf("Can Not Connect To %s!\n",argv[1]);
        exit(1);
    }
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Server:\t");
    scanf("%s", file_name);
  
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
    //
向伺服器發送buffer中的資料
    send(client_socket,buffer,BUFFER_SIZE,0);
//    int fp = open(file_name, O_WRONLY|O_CREAT);
//    if( fp < 0 )
    FILE * fp = fopen(file_name,"w");
    if(NULL == fp )
    {
        printf("File:\t%s Can Not Open To Write\n", file_name);
        exit(1);
    }
 //從伺服器接收資料到buffer
    bzero(buffer,BUFFER_SIZE);
    int length = 0;
    while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
    {
        if(length < 0)
        {
            printf("Recieve Data From Server %s Failed!\n", argv[1]);
            break;
        }
//        int write_length = write(fp, buffer,length);
        int write_length = fwrite(buffer,sizeof(char),length,fp);
        if (write_length<length)
        {
            printf("File:\t%s Write Failed\n", file_name);
            break;
        }
        bzero(buffer,BUFFER_SIZE);  
    }
    printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
    fclose(fp);
    //
關閉socket
    close(client_socket);
    return 0;
}

實作範例: Client端從Server端接收文字檔 t

ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t
執行./sss (serve)

等待(client)
以傳送文字檔t client

(Server)…..
sss
















ls查詢client端底下有哪些檔名的檔案(沒有要接收的文字檔t)
執行./ccc 127.0.0.1 (client)

Please Input File Name On Server
請輸入要接收的檔名

等待(Server)
接受文字檔t 存入client

ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t

確認與(Server)的文字檔t 無誤

(Client)…..
ccc