粉丝不过w

2.1 字节序,地址转换

        2.1.1字节序概述

               字节序概念:

                        多字节数据的存储顺序

               分类:

                   小端格式:低位字节数据存储在低地址

                  大端格式:高位字节数据存储在低地址

               注意:

                         LSB:低地址

                        MSB:高地址

 

        确定主机字节序程序:

#include <stdio.h> 
int main(int argc, char *argv[])
 { 
    union 
    { 
        short s; 
        char c[sizeof(short)]; 
    }un; 
    un.s = 0x0102; 
    if((un.c[0] == 1) && (un.c[1] == 2))
    { 
        printf("un.s = %x H\n", un.s);
        printf("un.c[0] = %d\n",un.c[0]);
        printf("un.c[1] = %d\n",un.c[1]);
        printf("big-endian\n"); 
    } 
    else if((un.c[0] == 2) &&(un.c[1] == 1)) 
    {
        printf("un.s = %x H\n", un.s);
        printf("un.c[0] = %d\n",un.c[0]);
        printf("un.c[1] = %d\n",un.c[1]);
        printf("little-endian\n"); 
    } 
    return 0; 
}

    特点:

  •             网络协议指定通讯字节序——大端
  •            多字节数据处理时考虑字节序
  •            运行在主机的进程互相通信,不用考虑字节序
  •             异构计算机通信,需要转换字节序为网络字节序

 

2.1.2 htonl函数

/*
 *功能:
 *  将32位主机字节序数据转换成网络字节序数据
 *参数:
 *  hostint32:待转换的32位主机字节序数据
 *返回值:
 *  成功:返回网络字节序的值
*/
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);

 

2.1.3 htons函数

/*
 *功能:
 *  将16位主机字节序数据转换网络字节序数据
 *参数:
 *  uint16_t:unsigned short int
 *  hostint16:待转换16位主机字节序数据
 *返回值:
 *  成功:返回网络字节序的值
*/
#include <arpa/inet.h>
uint16_t htons(uint16_t hostint16);

 

2.1.4 ntohl函数

/*
 *功能:
 *  将32位网络字节序数据转换为主机字节序数据
 *参数:
 *  uint32_t:usigned int
 *  netint32_t:待转换32位网络字节序数据
 *返回值:
 *  成功:返回主机字节序的值
*/

#include <arpa/inet.h>
uint32_t ntohl(uint32_t netint32);

 

2.1.5 ntohs函数

/*
 *功能:
 *  将16位网络字节序数据转换主机字节序数据
 *参数:
 *  uint16_t:unsigned short int
 *  netint16:待转换的16位网络字节序数据
 *返回值:
 *  成功:返回主机字节序的值
*/

#include <arpa/inet.h>
uint16_t ntohs(uint16_t netint16);

 

示例:

#include <stdio.h> 
#include <arpa/inet.h>

int main(int argc, char *argv[]) 
{ 
    int a = 0x01020304;
    short int b = 0x0102; 

    printf("htonl(0x%08x) = 0x%08x \n",a,htonl(a)); 
    printf("htons(0x%04x) = 0x%04x \n",b,htons(b)); 

    return 0; 
}

      结果:

2.1.6 地址转换函数

2.1.6.1 inet_pton函数

        字符串IP地址转整型数据

/*
 *功能:
 *  将点分十进制数串转换32位无符号整型
 *参数:
 *  family :协议族
 *  strptr:点分十进制数串
 *  addrptr:32位无符号整型的地址
 *返回值:
 *  成功: 1
 *  失败: 其他
*/
#include <arpa/inet.h>
int inet_pton(int family,const char *strptr,void *addrptr);

 

例:

#include <stdio.h> 
#include <arpa/inet.h> 

int main(int argc, char *argv[]) 
{ 
    char ip_str[] = "172.20.226.11"; 
    unsigned int ip_uint = 0; 
    unsigned char *ip_p = NULL; 

    inet_pton(AF_INET,ip_str,&ip_uint); 
    printf("ip_uint = %d\n",ip_uint); 

    ip_p = (unsigned char *)&ip_uint; 
    printf("ip_uint = %d.%d.%d.%d\n",*ip_p ,*(ip_p + 1) ,*(ip_p +2) ,*(ip_p + 3)); 

    return 0; 
}

 

2.1.6.2 inet_ntop函数

          整型数据转换字符串格式IP地址

/*
 *功能:
 *  将 32 位无符号整数转换成点分十进制数串
 *参数:
 *  family:    协议族
 *  addrptr:   32 位无符号整数
 *  strptr:    点分十进制数串
 *  len:       strptr 缓冲区长度
 *  len 的宏定义
 *    #define INET_ADDRSTRLEN  16  //for  ipv4
 *    #define INET_ADDRSTRLEN  46  //for  ipv6
 *return:
 *  成功:字符串的首地址
 *  失败:NULL
*/
#include <arpa/inet.h>
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);

例:

#include <stdio.h> 
#include <arpa/inet.h> 

int main() 
{ 
    unsigned char ip[] = {172,20,223,75};
    char ip_str[16] = "NULL";

    inet_ntop(AF_INET,(unsigned int *)ip,ip_str,16);
    printf("ip_str = %s\n",ip_str); 

    return 0; 
}

 

2.2 UDP介绍 编程流程

2.2.1 UDP概述

        UDP协议

             面向无连接的用户数据报协议,在传输数据前不用建立连接,目的主机的传输层收到UDP报文,不用做任何确认

       UDP特点

  •    比TCP速度快
  •    简单请求/应答应用程序用UDP
  •    海量数据传输不用UDP
  •    广播和多播应用用UDP

         UDP应用

              DNS(域名解析),NFS(网络文件系统),RTP(流媒体)

 

2.2.2 网络编程接口socket

       网络通信要解决的是不同主机进程间的通信

  •     网络间进程标识问题
  •     多重协议的识别问题

          socket应用

                提供不同主机的进程间通信

         socket特点

  •       socket 为套接字
  •         文件描述符,代表一个通信通道的一个端点
  •         read, write,close函数对套接字进行网络数据的收取和发送等操作

 

2.2.3 UDP编程 C/S架构

2.3UDP编程-创建套接字

 

2.3.1 创建socket套接字

/*
 *function:
 *  创建用于网络通信的socket套接字(描述符)
 *parameter:
 *  family:    协议族   (AF_INET       AF_INET6     PACKET)
 *  type:      套接字类 (SOCK_STREAM   SOCK_DGRAM   SOCK_RAM)
 *  protocol:  协议类别 (0             IPPROTO_TCP  IPPROTO_UDP)
 *return:
 *  套接字
 *note:
 *  创建套接字,系统不分配端口
 *  创建套接字默认属性为主动,主动发起服务的请求;当服务器,修改为被动
*/

#include <sys/socket.h> 

int socket(int family,int type,int protocol);

  

2.3.2 创建UDP套接字demo

/*
 *note:
 *  AF_INET:     IPv4协议
 *  SOCK_DGRAM:  数据报套接字
 *  0:           family 和 type 组合的系统默认值
*/

int sockfd = 0; 
sockfd = socket(AF_INET,SOCK_DGRAM,0); 

if(sockfd < 0) 
{ 
    perror("socket"); 
    exit(-1); 
}

 

2.4 UDP编程-发送,绑定,接收数据

2.4.1 IPv4套接字地址结构

#include <netinet/in.h> 

struct in_addr 
{ 
    in_addr_t s_addr;            //4字节 
}; 

struct sockaddr_in 
{ 
    sa_family_t     sin_family;      //2字节 
    in_port_t       sin_port;        //2字节 
    struct in_addr  sin_addr;        //4字节 
    char            sin_zero[8];     //8字节 
};

 

2.4.2 通用套接字地址结构

      为了不同格式地址传入套接字,地址强制转换为通用套接字地址格式

/*
 *note:
 *  以上3个结构在Linux系统中已定义
 */
#include <netinet/in.h> 

struct sockaddr
{ 
    sa_family_t  sa_family;     //2字节 
    char         sa_data[14];   //14字节 
};

 

2.4.3 俩种地址结构使用场合

   定义源地址和目的地址结构时,用struct sockaddr_in

例:

struct sockaddr_in my_addr;

   函数传入地址结构要用 struct sockaddr 进行强制转换

例:

bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));

 

2.4.4 发送数据——sendto函数

/*
 *function:
 *  向to结构体指针指IP,发送UDP数据
 *parameter:
 *  sockfd: 套接字
 *  buf:    发送数据缓冲区
 *  nbytes: 发送数据缓冲区的大小
 *  flags:  0
 *  to:     指向目的主机地址结构体的指针
 *  addrlen:to指向的内容长度
 *note:
 *  通过to和addrlen确定目的地址
 *  可发送0长度的UDP数据包
 *return:
 *  成功:发送数据的字符数
 *  失败: -1
*/
ssize_t sendto(int sockfd, 
               const void *buf, 
               size_t nbytes, 
               int flags, 
               const struct sockaddr *to, 
               socklen_t addrlen);

 

2.4.5 向“网络调试助手”发送消息

 

/*
 *与网络调试助手配合使用
 * send data to UDP server 192.168.43.76:8080!
 * sb	
 * cpucode
 * ^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> 

int main(int argc, char *argv[]) 
{ 
    unsigned short port = 8080; 
    char *server_ip = "192.168.43.76"; 
    int sockfd;  
    struct sockaddr_in dest_addr; 
    /* 指定server信息 */
    if(argc > 1)                    //服务器ip地址
    {  
        server_ip = argv[1]; 
    } 
    if(argc > 2)                    //服务器端口
    { 
        port = atoi(argv[2]); 
    } 
                           
    sockfd = socket(AF_INET,SOCK_DGRAM,0);        //创建UDP套接字
    if(sockfd < 0) 
    { 
        perror("socket"); 
		exit(-1); 
    } 

    /* 填充目的server的信息 */
    bzero(&dest_addr,sizeof(dest_addr)); 
    dest_addr.sin_family = AF_INET; 
    dest_addr.sin_port = htons(port); 

    inet_pton(AF_INET,server_ip,&dest_addr.sin_addr); 
    printf("send data to UDP server %s:%d!\n",server_ip,port); 

    while(1)                                         //发送数据到server
    { 
        char send_buf[512] = " "; 

        fgets(send_buf,sizeof(send_buf),stdin);       //获取输入
        send_buf[strlen(send_buf)-1] = '\0'; 

        sendto(sockfd, 
               send_buf, 
               strlen(send_buf), 
               0, 
               (struct sockaddr *)&dest_addr,
               sizeof(dest_addr)); 
    } 
    close(sockfd); 

    return 0; 
}

 

2.4.6 绑定bind函数

         UDP网络收取数据的条件:

              确定IP地址

              确定port

       怎么完成条件:

            接收端:

                    bind函数,完成地址结构与 socket套接字的绑定,IP和port就固定

             发送端:

                   sendto函数,指定接收端的IP,port,发数据

/*
 *function:
 *  本地协议地址与sockfd绑定
 *parameter:
 *  sockfd: socket套接字
 *  myaddr: 特定协议的地址结构指针
 *  addrlen:地址结构的长度
 *return:
 *  成功:返回 0
 *  失败:其他
*/
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

2.4.7 bind示例

/*
 *note:
 *  INADDR_ANY 通配地址 为 0
 */
#include <sys/socket.h> 

int err_log = 0; 
unsigned short port = 8000; 
struct sockaddr_in my_addr; 

bzero(&my_addr,sizeof(my_addr)); 
my_addr.sin_family = AF_INET; 
my_addr.sin_port = htons(port); 
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 

err_log =bind(sockfd,(struct sockaddr *)&my_addr,sizeof(my_addr)); 

if(err_log != 0) 
{ 
    perror("bind"); 
    close(sockfd); 
    exit(-1); 
}

 

2.4.8 接收数据—recvfrom函数

/* 
 *function:
 *  接收UDP数据,把源地址信息保存在form指向的结构中 
 *note:
 *  通过from和addrlen参数存放数据来源信息 from和addrlen可以为NULL,表示不保存数据来源 
 *return:
 *  成功:接收的字符数 
 *  失败:-1 
*/ 
ssize_t recvfrom(int sockfd,             //套接字 
                 void *buf,              //接收数据缓冲区 
                 size_t nbytes,          //接收数据缓冲区的大小 
                 int flags,              //套接字标志 
                 struct sockaddr *from,  //源地址结构体指针,保存数据 
                 socklen_t *addrlen);    //from指向内容的长度

 

2.4.9 接收“网络调试助手”的数据

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

int main(int argc, char *argv[]) 
{ 
    unsigned short port = 8080;
    int sockfd;
    struct sockaddr_in my_addr; 	
    int err_log;

    if(argc > 1)                    //修改本程序的端口 
    { 
        port = atoi(argv[1]); 
    }

    sockfd =socket(AF_INET,SOCK_DGRAM,0); 
    if(sockfd < 0)                    //创建套接字 
    { 
        perror("socket"); 
        exit(-1); 
    }

  	//填充本程序的信息 
    bzero(&my_addr,sizeof(my_addr)); 

    my_addr.sin_family = AF_INET; 
    my_addr.sin_port = htons(port); 
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    printf("Binding server to port %d\n",port); 

    err_log = bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    if(err_log != 0) //绑定本程序要使用的信息 
    { 
        perror("bind"); 
        close(sockfd); 
        exit(-1); 
    }

    printf("receive data ...\n"); 
    while(1)                    //收取数据 
    { 
        int recv_len; 
        char recv_buf[512] = " "; 
        struct sockaddr_in client_addr; 
        char cli_ip[INET_ADDRSTRLEN] = " "; 
        socklen_t cliaddr_len = sizeof(client_addr);

        recv_len = recvfrom(sockfd, 
                            recv_buf, 
                            sizeof(recv_buf), 
                            0, 
                            (struct sockaddr *)&client_addr, 
                            &cliaddr_len);

        inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip,INET_ADDRSTRLEN); 

        printf("\nip:%s,port:%d\n",cli_ip ,ntohs(client_addr.sin_port));     
        printf("data(%d):%s\n",recv_len,recv_buf); 
    } 

    close(sockfd);

    return 0; 
}

 

2.5 UDP编程-client,server

2.5.1 C/S架构回顾

2.5.2 UDP客户端注意点

  •    本地IP:本地端口
  •    目的IP:目的端口
  •    客户端,只设置目的IP,目的端口
bzero(&dest_addr, sizeof(dest_addr)); 
dest_addr.sin_family = AF_INET; 
dest_addr.sin_port = htons(8080); 
inet_pton(AF_INET,"172.20.223.75",&dest_addr.sin_addr);

      客户端的本地IP,本地port是调用sendto的时候linux底层自动给客户端分配

     分配端口是随机,每次运行的port都不一样

 

2.5.3 UDP服务器注意点

  •     服务器要bind固定,是因为本地port要固定,不能随机
  •     服务器可以主动给客户端发数据
  •    客户端可以用bind,客户端的本地端口就固定,一般不这样做