【linux草鞋應用編程系列】_5_ Linux網絡編程

1、網絡通訊簡介html

  第一部份內容,暫時無法描述,內容實在太多,待後續專門的系列文章。
 
2、linux網絡通訊
    在linux中繼承了Unix下「一切皆文件」的思想, 在linux中要實現網絡通訊須要建立相關的網絡文件;linux中
用相關的系統調用建立相關的網絡文件。
 
一、網絡服務器實現(基於TCP/IP)
    要實現一個網絡服務器,則按照如下步驟完成
(1)建立網絡套接字文件
    socket( )系統調用用來建立網絡套接字。其原型以下:
NAME
       socket - create an endpoint for communication    //功能: 用來建立一個終端的連接

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int socket(int domain,     //這個參數用來選擇網絡協議族,
                  int type,       //用來指定套接類型,
                  int protocol);  //這個參數通常指定爲0 除非用於建立原始協議族時才設置爲其餘值
返回值:
        成功返回套接字文件描述符,失敗返回-1;
 
        建立網絡套接字文件的代碼以下:
#include <sys/socket.h>

int socketfd ;

socketfd=socket(PF_INET,SOCK_STREAM,0);
if(-1 == socketfd)
{
    perror("service");
    return socketfd;
}
(2)、綁定本地端口
    經過系統調用  bind( )實現服務器與本地端口的綁定, 其原型以下所示:
NAME
       bind - bind a name to a socket    //爲套接字指定名稱
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
       int   bind( int   sockfd,    //套接字文件描述符
                       const  struct  sockaddr  *my_addr,    //通用的套接字地址指針
                       socklen_t   addrlen);   //第二個參數佔用的字節數  sizeof(my_addr)
DESCRIPTION
       bind() gives the socket sockfd the local address my_addr.   my_addr  is
       addrlen bytes long.  Traditionally, this is called 「assigning a name to
       a socket.」  When a socket is created with socket(2),  it  exists  in  a
       name space (address family) but has no name assigned.
返回值:
                綁定成功返回0; 失敗返回-1;
     
     關於第二個參數還須要說明: 
            函數原型中指定的是一個通用的指針,而在應用具體的協議的時候,這個參數須要傳遞與具體協議相關
的指針。 這個通用結構體類型的定義以下:
struct sockaddr的定義以下:
/*  
 *  1003.1g requires sa_family_t and that sa_data is char.
 */ 
struct sockaddr {
    sa_family_t sa_family;  /* address family, AF_xxx   */
    char        sa_data[14];    /* 14 bytes of protocol address */
};
          例如: 當用TCP/IP協議的時候,就須要使用 struct sockaddr_in 類型結構體變量的指針。
          struct sockaddr_in 定義以下:
struct sockaddr_in {
  sa_family_t       sin_family; /* Address family */   //socket 網絡協議族類型
  __be16        sin_port;   /* Port number */  //要綁定的本地端口號
  struct in_addr    sin_addr;   /* Internet address*/   //本機的IP地址

  /* Pad to size of `struct sockaddr'. */
  unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
            sizeof(unsigned short int) - sizeof(struct in_addr)];  // 第四個域一般不用賦值
};
要點:
        .sin_port 的賦值,須要進行類型轉換,
        .sin_addr是一個struct in_addr 結構體類型,這個結構體賦值也須要注意。      
        struct   in_addr  的定義以下:
/* Internet address. */
struct in_addr {
    __be32  s_addr;
};
typedef  __u32 __bitwise __be32;     //32位的無符號整數
    由於咱們一般用 192.168.0.12 這種格式來表示網絡的IP地址,而struct sockaddr_in 的sin_addr.s_addr 是一個
32位的無符號整數表示的IP地址,所以須要將 192.168.0.12 這種格式進行轉換才能進行賦值。
    下面的函數族用來在 192.168.0.12 和 32bit 的網絡IP地址之間的轉換。
NAME
       inet_aton,    inet_addr,    inet_network,   inet_ntoa,   inet_makeaddr,
       inet_lnaof, inet_netof - Internet address manipulation routines

SYNOPSIS
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>

       int inet_aton(const char *cp, struct in_addr *inp);
       in_addr_t inet_addr(const char *cp);  //將字符串"192.168.0.12"轉換爲32bit的IP地址
       in_addr_t inet_network(const char *cp);
       char *inet_ntoa(struct in_addr in); //將32bit的IP地址轉換爲 "192.168.0.12"字符串
       struct in_addr inet_makeaddr(int net, int host);
       in_addr_t inet_lnaof(struct in_addr in);
       in_addr_t inet_netof(struct in_addr in);
    綁定本地端口的示例代碼以下:
#include <sys/socket.h>
#include <netinet/in.h>

struct sockaddr_in bind_addr;
socklen_t addr_len;

//綁定本地端口,即爲socketfd命名
bind_addr.sin_family = AF_INET;
bind_addr.sin_port= htons (6000);   //通信端口,服務器端和客戶機端必須同樣
bind_addr.sin_addr.s_addr= inet_addr("192.168.0.101");   //本機的IP地址
addr_len=sizeof(struct sockaddr_in);
ret = bind(socketfd,(struct sockaddr*)&bindaddr, addr_len);
if(-1==ret)
{
    perror("bind");
    return ret;
}
(3)監聽本地端口
    在綁定本地端口後,就須要對本地的端口進行監聽,用以檢測是否有客戶機向服務器發送服務的請求。用
系統調用 listen( )創建監聽。 其原型以下
LISTEN(2)                  Linux Programmer’s Manual                 LISTEN(2)
NAME
       listen - listen for connections on a socket
SYNOPSIS
       #include <sys/socket.h>

       int listen(int sockfd,    //要監聽的網絡套接字文件描述符
                  int backlog); //能夠服務的最大客戶端數目,或者說監聽隊列的長度
返回值:
        成功返回0 ;失敗返回-1;
 
示例代碼以下:
#include  <sys/socket.h>

socketfd = listen(socketfd, 10); 
 
(4) 接受服務請求並建立本地緩衝文件
    當監聽到服務請求後,就須要對服務請求作出響應,同時還須要建立一個用來緩存數據信息的文件, 經過 accept( ) 
系統調用來對從監聽隊列取出服務請求,同時建立一個緩存數據信息的文件。
    accept( )系統調用的原型以下:
ACCEPT(2)                  Linux Programmer’s Manual                 ACCEPT(2)
NAME
       accept - accept a connection on a socket
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int accept( int sockfd,      //要監聽的網絡套接字文件的描述符
                   struct sockaddr *addr,   //輸出參數,用來存放接收到的客戶機的網絡地址信息
                   socklen_t *addrlen);     //輸出參數,用來存放客戶機的網絡地址信息的長度
返回值:
            成功返回數據緩衝文件描述符,失敗返回 -1 。
    第二參數須要注意,由於函數原型使用的一個通用的數據類型,而實際應用中須要根據網絡協議的不一樣,須要採用不一樣
的數據類型指針。
    例如這裏使用  TCP/IP 所以須要傳遞一個  struct sockaddr_in 結構體變量的指針。
    
下面爲響應服務並建立數據緩衝文件的Exp:
    #include    <sys/socket.h>

    struct sockaddr_in client_addr;
    socklen_t  client_addr_len;

    //響應監聽隊列的請求,並建立緩存數據文件
    client_addr_len = sizeof(struct sockaddr_in);
    sockbuf_fd=accept(socketfd,(struct sockaddr*)&client_addr, &client_addr_len);
    if(-1 == sockbuf_fd)
    {
        perror("accept");
        return sockbuf_fd; 
    }
(5) 接受和發送數據
     accept( )系統調用從請求服務隊列取得一個服務請求,同時爲這個服務請求創建一個緩存數據信息的文件,同時
返回建立的緩存數據信息的文件描述符,所以能夠和訪問普通文件同樣來訪問緩存數據信息文件; 對緩存數據信息
文件進行讀就至關於接收數據,對緩存數據信息文件進行寫就至關於發送數據。
 
Exp:
    //發送數據
    ret=write(sock_buf_fd,"socket network serives test.\n",29);
    if(-1 == ret)
    {
        perror("send data");
        return ret;
    }

    //接收數據
    memset(sock_buf,0,sizeof(sockbuf));
    ret=read(sock_buf_fd,sock_buf,sizeof(sock_buf));
    if(-1 == ret)
    {
        perror("read socket");
        return ret;
    }
(6)關閉文件
    在調用 socket( ) 和accept( ) 時候都會打開一個網絡套接字相關的文件,所以須要關閉;關閉文件調用close( )完成便可。
Exp:
 close(sock_buf_fd);
 close(socket_fd);
二、網絡客戶端實現(基於TCP/IP)
     要經過網絡進行通信,在網絡客戶端也須要能夠分幾步進行:
(1) 建立網絡套接字文件
     建立套接字的方法,與服務器端同樣,且使用的網絡族協議和套接字類型與服務端同樣。
 Exp:
    int  socket_fd;
    
    socket_fd = socket(  PF_INET, 
                         SOCK_STREAM,
                         0 );
(2) 請求創建鏈接
      在客戶端爲了獲取服務端的服務,必須申請創建與服務器的鏈接。在客戶端經過 connect( )請求創建服務鏈接, 原型
以下:
CONNECT(2)                 Linux Programmer’s Manual                CONNECT(2)
NAME
       connect - initiate a connection on a socket
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int  connect( int  sockfd,    //創建的套接字文件
                         const  struct sockaddr *serv_addr,     //指定要鏈接到服務的地址信息結構體的指針
                         socklen_t  addrlen);    //參數2 的長度
返回值:
            成功返回0 ;失敗返回-1 。
    要點:
           這個函數定義的時候,採用的是一個通用的指針;在應用程序中,須要傳遞指定通信協議的數據結構的指針。
 
請求創建鏈接到示例代碼以下:
#include <sys/socket.h>
#include <netinet/in.h>

int  ret;
struct  sockaddr_in  srv_addr;


srv_addr.sin_family =AF_INET;
srv_addr.sin_port= htons(6000);
srv_addr.sin_addr.s_addr= inet_addr("192.168.0.101");
ret =connect(socket_fd , (struct sockaddr*)&srv_addr , sizeof(struct sockaddr_in) )
(3) 接收和發送數據
    在創建通信鏈接後,就能夠進行數據的接收和發送了,能夠和服務器端同樣進行數據的讀寫。
示例代碼:
   memset(buf, 0 , sizeof(buf) );    //將緩衝區域清空的緣由是定義的buf裏面存在一些其餘數據,若是不清空,數據將錯誤
    ret=read(socket_fd,buf,sizeof(buf));
    if(ret<0)
    {
        perror("read");
        return ret;
    }

    ret=write(socket_fd,buf,strlen(buf));    //若是在讀以前不清空 buf ,那麼這個地方 strlen(buf) 就要改成 ret ;
    if(ret<0)
    {
        perror("write");
        return ret;
    }
要點:
        不清空、或者 不將 strlen(buf) 改成 ret 的執行結果爲:
[root@localhost tcpip]# ./service 
socket network serives test.
�@V@�R@PP@�R@qr@.       //打印出現亂碼。
 
下面是完整的一個能夠經過TCP/IP 進行通訊的示例代碼:
Exp:  service.c   用於看成服務程序
//本文件用來測試網絡通訊
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc,char* argv[])
{
    int socket_fd;
    int ret;
    struct sockaddr_in bind_addr;
    struct sockaddr_in client_addr;
    socklen_t addr_len;
    int sock_buf_fd;
    char sock_buf[1024];
    
    //建立網絡套接字
    socket_fd=socket(PF_INET,SOCK_STREAM,0);
    if(-1 == socket_fd)
    {
        perror("service");
        return socket_fd;
    }

    //綁定本地端口,即爲socket_fd命名
    bind_addr.sin_family = PF_INET;
    bind_addr.sin_port= htons (6000);
    bind_addr.sin_addr.s_addr= inet_addr("192.168.0.101");
    addr_len=sizeof(struct sockaddr_in);
    ret = bind(socket_fd,(struct sockaddr*)&bind_addr, addr_len);

    //監聽端口
    ret=listen(socket_fd, 10);
    if(-1 == ret)
    {
        perror("listen");
        return ret;
    }

    //響應監聽隊列的請求,並建立緩存數據文件
    sock_buf_fd=accept(socket_fd,(struct sockaddr*)&client_addr,&addr_len);
    if(-1 == sock_buf_fd)
    {
        perror("accept");
        return sock_buf_fd;
    }

    //發送數據
    ret=write(sock_buf_fd,"socket network serives test.\n",29);
    if(-1 == ret)
    {
        perror("send data");
        return ret;
    }

    //接收數據
    memset(sock_buf,0,sizeof(sock_buf));
    ret=read(sock_buf_fd,sock_buf,sizeof(sock_buf));
    if(-1 == ret)
    {
        perror("read socket");
        return ret;
    }
    printf("%s\n",sock_buf);

    close(sock_buf_fd);
    close(socket_fd);
    return 0;
}

    client.c   看成客戶程序linux

//本文件用來測試linux網絡編程

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc,char*argv[])
{
    int ret;
    int socket_fd;
    struct sockaddr_in srv_addr;
    socklen_t addr_len;
    char buf[1024];

    //建立網絡套接字文件
    socket_fd=socket(PF_INET,SOCK_STREAM,0);
    if(-1 == socket_fd )
    {
        perror("socket");
        return socket_fd;
    }

    //申請創建與服務器的鏈接
    srv_addr.sin_family=PF_INET;
    srv_addr.sin_port=htons(6000);
    srv_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); //服務器IP地址
    addr_len=sizeof(struct sockaddr_in);
    ret=connect(socket_fd,(struct sockaddr *)&srv_addr, addr_len);
    if(-1==ret)
    {
        perror("connect");
        return -1;
    }

    memset(buf,0,sizeof(buf));
    ret=read(socket_fd,buf,sizeof(buf));
    if(ret<0)
    {
        perror("read");
        return ret;
    }
    
    ret=write(socket_fd,buf,strlen(buf));
    if(ret<0)
    {
        perror("write");
        return ret;
    }

    close(socket_fd);
    return 0;
}
執行的效果以下:
服務器端:
[root@localhost tcpip]# ./service  //在服務器端運行 service 程序,執行後再等待客戶程序請求服務
socket network serives test.       //在客戶端執行 ./client 後 打印出這個信息
 
[root@localhost tcpip]#    
客戶端:
/ # ./client 
/ # 
過程是:
             一、服務器給客戶端發送:   "socket network serives test.\n" 字符串
            二、客戶端接收服務器端發送來對數據
            三、客戶端將接收到數據發送到服務器端
            四、服務器端接收客戶端發送過來的數據,並將接收到的數據打印
 
 
示例1: 簡單文件傳輸程序
  虛擬機爲服務器:  IP爲 192.168.0.101
  友善之臂的2440開發板:  IP爲  192.168.0.99 
  測試爲從服務器上下載一個文件到開發板,測試成功能夠在開發板的系統上查看到下載到開發板的程序。
    下面爲頭文件: ftp.h
//本文件是用於簡單文件傳輸程序的頭文件
#if !defined(__FTP_H__)
#define  __FTP_H__

#define  TEL_LEN      512    //報文數據長度爲512字節

#define  FILE_EXIST   0x0
#define  FILE_NOT_EXIST 0x1
#define  END_OF_FILE  0x2
#define  VALID_DATA   0x3

typedef struct tel_gram
{
    unsigned char type;
    char  data[TEL_LEN];
    
}tel_t;

typedef struct pthread_type
{
    struct sockaddr_in *sock_in_addr;
    int    sock_buf_fd ;

}pthread_type_t;

#endif
    下面爲服務器: service.c
//本文件用來實現一個簡單的文件傳輸服務
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include "ftp.h"
#include <fcntl.h>

int transfer(pthread_type_t *arg)
{
    int fd;
    int ret;
    int size;
    int sock_buf_fd;
    char file_name[512];
    tel_t telgram;
    struct sockaddr_in* cli_addr;

    //輸出客戶機信息
    cli_addr=arg->sock_in_addr;
    printf("the connet client is %s .\n",inet_ntoa(cli_addr->sin_addr.s_addr) );
#if 0
    //首先檢測連接是否正常
    sock_buf_fd=arg->sock_buf_fd;
    ret=recv( sock_buf_fd,  //接收數據的socket數據緩衝文件
              &telgram,     //存粗接收數據的緩衝去地址
              sizeof(tlegram.type), //要接收的數據長度
              0); //接收標誌
    if(ret != sizeof(tlegram.type))
    {
        printf("network establelish is not well. will exit\n");
        return 1;
    }
    send(sock_buf_fd,&telgram,sizeof(tlegram.type),0);
#endif

    //接受文件名
    sock_buf_fd=arg->sock_buf_fd;
    ret=recv(sock_buf_fd, file_name,512,0);
    if(-1==ret)
    {
        perror("recv file name");
        return 1;
    }
    //切換到默認路徑,默認文件存放路徑爲/ftp目錄
    chdir("/ftp");
    if( access(file_name, F_OK) )
    {
        telgram.type=FILE_NOT_EXIST;
        //若是文件不存在就發送信息給客戶端
        send(sock_buf_fd,&telgram,sizeof(telgram.type),0);
        return 1;
    }

    //打開要傳送的文件
    fd=open(file_name,O_RDONLY);
    if(-1==fd)
    {
        return -1;
    }

    while(1)
    {
        telgram.type=VALID_DATA;
        ret=read(fd, &telgram.data,TEL_LEN);
        size=sizeof(telgram);
        if(ret<TEL_LEN)
        {
            telgram.type=END_OF_FILE;
            size =ret + sizeof(telgram.type); 
        }
        send(sock_buf_fd,&telgram, size,0);
    }
          
    return 0;
}

int main(int argc,char* argv[])
{
    int sock_fd;
    int sock_buf_fd;
    int ret;
    struct sockaddr_in srv_addr;  //服務器IP地址、端口信息
    struct sockaddr_in cli_addr;  //用來存儲客戶段地址信息
    socklen_t srv_addr_len;       //保存服務器IP地址、端口信息的結構體數據長度
    socklen_t cli_addr_len;       //保存客戶機IP地址、端口信息的結構體數據長度
    
    pthread_type_t pth_arg;

    //創建網絡套接字文件
    sock_fd=socket(PF_INET,      //使用TCP/IP協議族
                  SOCK_STREAM,  //使用字符流,TCP協議,須要創建穩定連接
                  0);           //非創建原始協議,必須傳 0
    if(-1 == sock_fd )
    {
        perror("create socket");
        return sock_fd;
    }

    //綁定本地端口,即爲套接字接口命名
    srv_addr.sin_family=PF_INET;   //指定綁定的協議
    srv_addr.sin_port=htons(6000); //指定通訊端口
    srv_addr.sin_addr.s_addr=inet_addr("192.168.0.101"); //指定服務起的IP
    srv_addr_len=sizeof(struct sockaddr_in);
    ret=bind( sock_fd,                          //要綁定的套接字文件
              (struct sockaddr *)&srv_addr,     //服務器網絡信息結構體
              srv_addr_len);                    //第二個參數的sizeof
    if(-1 == ret)
    {
        perror("socket bind");
        return ret;
    }

    //監聽端口
    ret=listen(sock_fd,   //要監聽的套解字,即要監聽的端口
               5);          //最大監聽隊列
              
    if(-1 == ret)
    {
        perror("socket liseten");
        return ret;
    }

    //查看請求,並創建數據緩存文件
    while(1)
    {
       sock_buf_fd=accept( sock_fd,  //輸入參數
                           (struct sockaddr*)&cli_addr, //輸出參數 客戶機IP
                           &cli_addr_len);   //第二個參數的sizeof
               
       pth_arg.sock_buf_fd=sock_buf_fd;  //數據緩存文件指針傳遞給線程
       pth_arg.sock_in_addr=&cli_addr;
       ret=transfer(&pth_arg);
       if(ret)
       {
           break ;
       }
    }

    close(sock_buf_fd);
    close(sock_fd);
    return 0;
}
 
    下面文件爲客戶端程序: client.c
   編譯命令爲:   arm-linux-gcc  client.c  -o  client
   若是有兩個虛擬機開啓來也可完成傳輸, 一個虛擬機也能完成這個代碼的測試。
//本文件實現簡單文件傳輸軟件的客戶端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "ftp.h"

int main(int argc,char* argv[])
{
    int fd;
    int ret;
    int sock_fd;
    struct sockaddr_in srv_addr;
    tel_t telgram;
    int size;

    //檢測程序執行時的參數是否正確
    //不正確輸出提示信息
    if(argc<2 || argc > 3)
    {
        printf("Usage: client filename\n");
        return 0;
    }

    //打開網絡套接字文件
    sock_fd=socket( PF_INET,   //與服器程序同樣
                    SOCK_STREAM, 
                    0 );   
    if(-1 == sock_fd)
    {
        perror("socket");
        return 0;
    }

    //申請與服務器創建鏈接
    srv_addr.sin_family=PF_INET;
    srv_addr.sin_port=htons(6000);
    srv_addr.sin_addr.s_addr=inet_addr("192.168.0.101");
    ret=connect(sock_fd,(struct sockaddr*)&srv_addr, sizeof(srv_addr));
    if(-1 == ret)
    {
        perror("connet to server");
        return 0;
    }

    //發送文件名給服務器
    send(sock_fd,argv[1],strlen(argv[1]),0);
    recv(sock_fd,&telgram,sizeof(telgram.type),0);
    if(FILE_NOT_EXIST == telgram.type)
    {
        printf("Not such a file in servie mathcine,will quit transfer\n");
        return 0;
    }

    //建立文件來接受數據
    fd=open(argv[1], O_WRONLY | O_CREAT | O_TRUNC);
    if(-1 == fd )
    {
        perror("create file");
        return 0;
    }

    while(1)
    {
        size=recv(sock_fd, &telgram, sizeof(telgram), 0);
        if(END_OF_FILE == telgram.type)
        {
            ret=write(fd,&telgram.data,size);
            if(ret<size)
            {
                //若是寫入的數據比讀到的數據少,表示寫入錯誤刪除文件
                unlink(argv[1]);
                perror("local file write");
            }
            break;
        }

        ret=write(fd, &telgram.data, size);
        if(ret<size)
        {
             //若是寫入的數據比讀到的數據少,表示寫入錯誤刪除文件
             unlink(argv[1]);
             perror("local file write");
             break ;
        }
    }

    close(fd);
    close(sock_fd);
    return 0;
}

【linux草鞋應用編程系列】_5_網絡編程編程

 本系列文章歡迎批評指正,歡迎指正錯誤。緩存

 本系列文章,未完待續.........服務器

【linux草鞋應用編程系列】_4_ 應用程序多線程網絡

相關文章
相關標籤/搜索