大小端及tcp程序

来源:互联网 发布:json txt格式转换器 编辑:程序博客网 时间:2024/05/16 19:50

背景知识:

        大端:低字节放在高位,高字节放在低位

        小端:低字节放在低位,低字节放在高位

        网络序为大端模式

        如果发送端与接收端的CPU大小端类型一致时,不需要进行数据字节序的转换;

              如果不一致,则至少小端CPU上的程序需要进行字节序(大小端)的转换(因为网络序是大端模式,故大端CPU与网络序之间可以不考虑转换)

前提:

        服务器程序运行在小端CPU(x86-PC)上,负责接收数据

        客户端程序运行在大端CPU(MPIS)上,负责发送数据


服务器接收程序:

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

#define portnumber 3333                     //端口号,服务器与客户端须一致

typedef struct {
    char          aa;
    int              bb;
    char          cc;
    short         dd;
    char          ee[10];
} tcptest;

int main(int argc, char *argv[])
{
    int sockfd,new_fd;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int sin_size;
    int nbytes;

    /* 用于进行字符串测试 */
    char buffer[1024];   

    /* 用于进行单个整形数据的测试 */
    int sunh;   

    /* 用于进行结构体测试 */
    tcptest *tcptt;
    tcptt = (tcptest *)malloc(sizeof(tcptest));


    /* 服务器端开始建立sockfd描述符 */
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP(tcp协议的流式套接字)
    {
        fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
        exit(1);
    }

    /* 服务器端填充 sockaddr结构 */
    bzero(&server_addr,sizeof(struct sockaddr_in));              // 初始化,置0
    server_addr.sin_family=AF_INET;                             // Internet :ip协议

    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // (将本机器上的long数据转化为网络上的long数据)和任何主机通信

                                                                                                   //INADDR_ANY 表示可以接收任意IP地址的数据,即绑定到所有的IP

                                                                                                  //此处因为为INADDR_ANY,故可以不进行字节序转换
    //server_addr.sin_addr.s_addr=inet_addr("192.168.1.1");  //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
    server_addr.sin_port=htons(portnumber);                   // (将本机器上的short数据转化为网络上的short数据)端口号
                                                                                         //此处必须进行字节序(大小端)的转换,否则服务器与客户端连接会失败
    /* 捆绑sockfd描述符到IP地址 */
    if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
    {
        fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
        exit(1);
    }


    /* 设置允许连接的最大客户端数 */
    if(listen(sockfd,5)==-1)
    {
        fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
        exit(1);
    }

    while(1)
    { 

         /* 服务器阻塞,直到客户程序建立连接 */
        new_fd=accept(sockfd,NULL,NULL);
           if((new_fd<0)&&(errno==EINTR))
                  continue;
           else if(new_fd<0)
            {
                printf("Accept Error:%s\n\a",strerror(errno));
                continue;
            }


     /* 接收数据 */
#if 0

       /* 用于进行字符串测试 */
        if((nbytes=read(new_fd,buffer,1024))==-1)
        {
            fprintf(stderr,"Read Error:%s\n",strerror(errno));
            exit(1);
        }        
        buffer[nbytes]='\0';
        printf("Server received %s\n",buffer);

                             //结果证明,字符串数据在大小端不同的CPU之间传输时,可以不用转换,因为字符串是按字节排序的32位处理器的话,以4字节为单位进行大小端转换的

                             //测试结果:与用户输入的字符串一致或big-little endian test

   

       /* 用于进行单个整形数据的测试 */
        if((nbytes=read(new_fd,&sunh,sizeof(int)))==-1)
        {
            fprintf(stderr,"Read Error:%s\n",strerror(errno));
            exit(1);
        }   
        printf("Server received %x\n",ntohl(sunh));     //接收正常:0x12345678    //ntohl实现网络序到主机序转换
        printf("Server received %x\n",sunh);               //接收不正常:0x78563412

                            //结果证明,int型的数据在大小端不一致的CPU间传输时,必须进行数据字节序的转换,否则会出现错误

                            //因为客户端发送程序运行在大端CPU上,与网络序一致,故可以不用进行转换;

                            //而服务器接收程序运行在小端CPU上,与网络序相反,要想实现正常通讯,必须使用ntohl函数进行大小端的转换

#endif

        /* 用于进行结构体测试 */
        if((nbytes=read(new_fd,tcptt,sizeof(tcptest)))==-1)
        {
             fprintf(stderr,"Read Error:%s\n",strerror(errno));
             exit(1);
        }        
        printf("Server received %x\n",tcptt->aa);
        printf("Server received %x\n",tcptt->bb);
        printf("Server received %x\n",tcptt->cc);
        printf("Server received %x\n",tcptt->dd);
        printf("Server received %s\n",tcptt->ee);
                           /*

                                       打印结果为:

                                       Server received 7
                                       Server received 77563412
                                       Server received 1b
                                       Server received 1757
                                       Server received sunhao27
                                       说明结构体在大小端不同的CPU之间传输时, 如果按照结构体成员的名字进行数据的读取,

                                       与单个的int型或者字符串传输结果一致(如tcptt->bb和tcptt->ee)(只不过是将多个不同类型放到同一个结构体中罢了),

                                       如果要实现tcptt->bb的正常读取,需要使用printf("Server received %x\n",ntohl(tcptt->bb));语句。

                            */



        /* 这个通讯已经结束 */
        close(new_fd);
        /* 循环下一个 */
    }

    /* 结束通讯 */
    close(sockfd);
    exit(0);
}




客户端发送程序:

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

#define portnumber 3333                            //端口号,服务器与客户端须一致


typedef struct {
    char          aa;
    int             bb;
    char          cc;
    short         dd;
    char          ee[10];
} tcptest;


int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in server_addr;
    struct hostent *host; 

    /* 用于进行字符串测试 */   

    char buffer[1024]; 


    /* 用于进行单个整形数据的测试 */
    int sunh=0x12345678;
    int SH;


    /* 用于进行结构体测试 */
    tcptest *tcptt;
    tcptt = (tcptest *)malloc(sizeof(tcptest));
    tcptt->aa=7;
    tcptt->bb=0x12345677;
    tcptt->cc=27;
    tcptt->dd=0x5717;
    strcpy(tcptt->ee, "sunhao27");


     /*可使用hostname查询host 名字 */
    if(argc!=2)
    {
        fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
        exit(1);
    }
    if((host=gethostbyname(argv[1]))==NULL)
    {
        fprintf(stderr,"Gethostname error\n");
        exit(1);
    }

    /* 客户程序开始建立 sockfd描述符 */
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)              // AF_INET:Internet;SOCK_STREAM:TCP
    {
        fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
        exit(1);
    }

    /* 客户程序填充服务端的资料 */
    bzero(&server_addr,sizeof(server_addr));            // 初始化,置0
    server_addr.sin_family=AF_INET;                      // IPV4
    server_addr.sin_port=htons(portnumber);  // (将本机器上的short数据转化为网络上的short数据)端口号
                                                                        //因为客户端程序运行在大端机器上,所以此处不使用htons函数转换也不会出错
    server_addr.sin_addr=*((struct in_addr *)host->h_addr); // IP地址
    
    /* 客户程序发起连接请求 */
    if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
    {
        fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
        exit(1);
    }

    /* 连接成功了,发送数据 */

 #if 0

    /* 用于进行字符串测试 */  

    printf("Please input char:\n");    //终端输入字符串,服务器接收的结果与用户输入的结果一致,说明字符串传输可以简单的理解为不受大小端的影响

    fgets(buffer,1024,stdin);
    //strcpy(buffer, "big-little endian test");
    write(sockfd,buffer,strlen(buffer));


     /* 用于进行单个整形数据的测试 */
    SH = sunh;     //因为客户端为大端CPU,所以可以不进行网络序的转换(转换与不转换的效果其实是一样的),

                          //如果客户端为小端CPU则需要SH = htonl(sunh);  进行字节序的转换
    write(sockfd,&SH,sizeof(int));


#endif

    /* 用于进行结构体测试 */

                            //与单个整形数据的测试一样,如果客户端运行在小端CPU上时,结果体中所有的int或short类型的变量都需要进行网络序的转换之后再进行发送
    write(sockfd,tcptt,sizeof(tcptest));


    /* 结束通讯 */
    close(sockfd);
    exit(0);
}



补充:

其实arm、mips等处理器一般既可以运行在大端模式也可以运行在小端模式(一般开发板上会有拨码开关),注意的是,将mips选择为大端或小端模式时,对应的编译器需要要加上与大端或小端对应的编译选项(详见man *-gcc等),否则程序不能执行。


原创粉丝点击