zoukankan      html  css  js  c++  java
  • volcanol的工控博客

    一、网络通信简介

      第一部分内容,暂时没法描述,内容实在太多,待后续专门的系列文章。
     
    二、linux网络通信
        在linux中继承了Unix下“一切皆文件”的思想, 在linux中要实现网络通信需要创建相关的网络文件;linux中
    用相关的系统调用创建相关的网络文件。
     
    1、网络服务器实现(基于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.
    ",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);
    2、网络客户端实现(基于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.
    ",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
    ",sock_buf);
    
        close(sock_buf_fd);
        close(socket_fd);
        return 0;
    }

        client.c   当作客户程序

    //本文件用来测试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 
    / # 
    过程是:
                1、服务器给客户端发送:   "socket network serives test. " 字符串
                2、客户端接收服务器端发送来对数据
                3、客户端将接收到数据发送到服务器端
                4、服务器端接收客户端发送过来的数据,并将接收到的数据打印
     
     
    示例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 .
    ",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
    ");
            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
    ");
            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
    ");
            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_ 应用程序多线程

  • 相关阅读:
    关于python中的随机种子——random_state
    python的随机森林模型调参
    用graphviz可视化决策树
    莫烦python教程学习笔记——保存模型、加载模型的两种方法
    莫烦python教程学习笔记——validation_curve用于调参
    莫烦python教程学习笔记——learn_curve曲线用于过拟合问题
    莫烦python教程学习笔记——利用交叉验证计算模型得分、选择模型参数
    莫烦python教程学习笔记——数据预处理之normalization
    windows 和rhel,centos双系统安装
    linux下增加swap分区
  • 原文地址:https://www.cnblogs.com/volcanol/p/3476177.html
Copyright © 2011-2022 走看看