网络编程(三)网络字节序与主机字节序、通信程序模板

1.网络字节序与主机字节序

在Linux网络编程中,经常碰到网络字节序与主机字节序的相互转换。说到网络字节序与主机字节序需要清晰了解以下几个概念。

字节序,顾名思义,指字节在内存中存储的顺序。比如一个int32_t类型的数值占用4个字节,这4个字节在内存中的排列顺序就是字节序。字节序有两种:

  • (1)小端字节序(Little endinan),数值低位存储在内存的低地址,高位存储在内存的高地址;

  • (2)大端字节序(Big endian),数值高位存储在内存的低地址,低位存储在内存的高地址。

下面以32位位宽数值0x12345678为例,小端字节序与大端字节序具体的存储区别如下所示:

  • 主机字节序,即CPU存储数据时采用的字节顺序。不同的CPU设计时采用的字节序是不同的,谈到字节序的问题,必然牵涉到两大CPU派系。 那就是Motorola的PowerPC系列CPU和Intel的x86与x86_64(该指令集由AMD率先设计推出)系列CPU。 PowerPC系列采用大端字节序(big endian)方式存储数据,而x86与x86_64系列则采用小端字节序(little endian)方式存储数据。 平常大多数PC与服务器如果使用的是Intel与AMD CPU,一般都是小端字节序(little endian)

  • 网络字节序,是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。 网络字节顺序采用大端字节序(big endian)排序方式。

如何具体判断本机的主机字节序呢?参考如下代码:

//@ret:返回0小端字节序,返回1大端字节序
int dGetHostByteOrder()
{
    uint32_t a = 0x12345678;  
    uint8_t *p = (uint8_t *)(&a);  
    if(*p==0x78)
    {
        return 0
    }
    else
    {
        return 1;
    }
}

2.网络字节序与主机字节序的相互转换

2.1常用系统调用

Linux socket网络编程中,经常会使用下面四个C标准库函数进行字节序间的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);        //把uint32_t类型(4字节)从主机序转换到网络序, host to network long
uint16_t htons(uint16_t hostshort);        //把uint16_t类型(2字节)从主机序转换到网络序,host to network short
uint32_t ntohl(uint32_t netlong);        //把uint32_t类型从网络序转换到主机序
uint16_t ntohs(uint16_t netshort);        //把uint16_t类型从网络序转换到主机序
4位数值的转换

现在如果需要对64位类型数据进行主机字节序与网络字节序的转换,没有现成系统API可用,可以通过下面两种方法进行转换:

2.2.1使用移位
//主机序转网络序
unsigned long long htonll(unsigned long long val)
{
    if(__BYTE_ORDER == __LITTLE_ENDIAN)  
    {
         return (((unsigned long long )htonl((int)((val << 32) >> 32))) << 32) | (unsigned int)htonl((int)(val >> 32));  
    }  
    else if (__BYTE_ORDER == __BIG_ENDIAN)  
    {  
         return val;  
    }  
}  

//网络序转主机序
unsigned long long ntohll(unsigned long long val)  
{  
    if (__BYTE_ORDER == __LITTLE_ENDIAN)
    {
        return (((unsigned long long )ntohl((int)((val << 32) >> 32))) << 32) | (unsigned int)ntohl((int)(val >> 32));  
    }  
    else if (__BYTE_ORDER == __BIG_ENDIAN)  
    {  
        return val;  
    }
 }
2.2.2使用联合体union

根据联合体的特性:联合中所有成员引用的是内存中相同的位置,其长度为最长成员的长度。

typedef struct {  
    unsigned int u32_h;  
    unsigned int u32_l;  
}Int64_t;  

typedef union {  
    unsigned long long u64;  
    Int64_t st64;  
}Convert64_t;

//主机序转网络序
unsigned long long htonll(unsigned long long val)
{  
    if (__BYTE_ORDER == __LITTLE_ENDIAN)
    {
        Convert64_t box_in, box_out;  

        box_in.u64 = val;  
        box_out.st64.u32_h = htonl(box_in.st64.u32_l);  
        box_out.st64.u32_l = htonl(box_in.st64.u32_h);  
        return box_out.u64;
    }
    else if (__BYTE_ORDER == __BIG_ENDIAN)  
    {  
        return val;
    }
}

//网络序转主机序
unsigned long long ntohll(unsigned long long val)  
{
    if (__BYTE_ORDER == __LITTLE_ENDIAN)
    {
        Convert64_t box_in, box_out;  

        box_in.u64 = val;  
        box_out.st64.u32_h = ntohl(box_in.st64.u32_l);  
        box_out.st64.u32_l = ntohl(box_in.st64.u32_h);  
        return box_out.u64;
    }
    else if(__BYTE_ORDER == __BIG_ENDIAN)
    {
        return val;
    }
}
2.2.3使用编译器内置函数
#ifdef WIN32
#define ntohll(x)     _byteswap_uint64 (x)
#define htonll(x)     _byteswap_uint64 (x)
#else
#if __BYTE_ORDER == __BIG_ENDIAN
#define ntohll(x)       (x)
#define htonll(x)       (x)
#else 
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ntohll(x)     __bswap_64 (x)
#define htonll(x)     __bswap_64 (x)
#endif 
#endif  
#endif

程序模板

_cmpublic.h

#ifndef _cmpublic_H
#define _cmpublic_H

#include <stdio.h>
#include <utime.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
#include <math.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <locale.h>
#include <dirent.h>
#include <termios.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <list>
#include <vector>
#include <deque>
#include <algorithm>

// 采用stl标准库的命名空间std
using namespace std;

#endif

Writen()函数和Readn()函数

_freecplus.h

// 从已经准备好的socket中读取数据。
// sockfd:已经准备好的socket连接。
// buffer:接收数据缓冲区的地址。
// n:本次接收数据的字节数。
// 返回值:成功接收到n字节的数据后返回true,socket连接不可用返回false。
bool Readn(const int sockfd,char *buffer,const size_t n);

// 向已经准备好的socket中写入数据。
// sockfd:已经准备好的socket连接。
// buffer:待发送数据缓冲区的地址。
// n:待发送数据的字节数。
// 返回值:成功发送完n字节的数据后返回true,socket连接不可用返回false。
bool Writen(const int sockfd,const char *buffer,const size_t n);

_freecplus.cpp

bool Readn(const int sockfd, char *buffer, const size_t n) {
    int nLeft, nread, idx;

    nLeft = n;
    idx = 0;

    while (nLeft > 0) {
        if ((nread = recv(sockfd, buffer + idx, nLeft, 0)) <= 0) return false;

        idx += nread;
        nLeft -= nread;
    }

    return true;
}

bool Writen(const int sockfd, const char *buffer, const size_t n) {
    int nLeft, idx, nwritten;
    nLeft = n;
    idx = 0;
    while (nLeft > 0) {
        if ((nwritten = send(sockfd, buffer + idx, nLeft, 0)) <= 0) return false;

        nLeft -= nwritten;
        idx += nwritten;
    }

    return true;
}

TcpWrite()函数和TcpRead()函数

_freecplus.h

// 接收socket的对端发送过来的数据。
// sockfd:可用的socket连接。
// buffer:接收数据缓冲区的地址。
// ibuflen:本次成功接收数据的字节数。
// itimeout:接收等待超时的时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool TcpRead(const int sockfd,char *buffer,int *ibuflen,const int itimeout=0);

// 向socket的对端发送数据。
// sockfd:可用的socket连接。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen=0);

_freecplus.cpp

bool TcpRead(const int sockfd, char *buffer, int *ibuflen, const int itimeout) {
    if (sockfd == -1) return false;

    if (itimeout > 0) {
        fd_set tmpfd;

        FD_ZERO(&tmpfd);
        FD_SET(sockfd, &tmpfd);

        struct timeval timeout;
        timeout.tv_sec = itimeout;
        timeout.tv_usec = 0;

        int i;
        if ((i = select(sockfd + 1, &tmpfd, 0, 0, &timeout)) <= 0) return false;
    }

    (*ibuflen) = 0;

    if (Readn(sockfd, (char *) ibuflen, 4) == false) return false;

    (*ibuflen) = ntohl(*ibuflen);  // 把网络字节序转换为主机字节序。

    if (Readn(sockfd, buffer, (*ibuflen)) == false) return false;

    return true;
}

bool TcpWrite(const int sockfd, const char *buffer, const int ibuflen) {
    if (sockfd == -1) return false;

    fd_set tmpfd;

    FD_ZERO(&tmpfd);
    FD_SET(sockfd, &tmpfd);

    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    if (select(sockfd + 1, 0, &tmpfd, 0, &timeout) <= 0) return false;

    int ilen = 0;

    // 如果长度为0,就采用字符串的长度
    if (ibuflen == 0) ilen = strlen(buffer);
    else ilen = ibuflen;

    int ilenn = htonl(ilen);  // 转换为网络字节序。

    char strTBuffer[ilen + 4];
    memset(strTBuffer, 0, sizeof(strTBuffer));
    memcpy(strTBuffer, &ilenn, 4);   // memcpy 既能处理文本数据和二进制数据,strcpy只能处理文本数据
    memcpy(strTBuffer + 4, buffer, ilen);

    if (Writen(sockfd, strTBuffer, ilen + 4) == false) return false;

    return true;
}

服务端CTcpServer类

_freecplus.h

// socket通信的服务端类
class CTcpServer {
private:
    int m_socklen;                    // 结构体struct sockaddr_in的大小。
    struct sockaddr_in m_clientaddr;  // 客户端的地址信息。
    struct sockaddr_in m_servaddr;    // 服务端的地址信息。
public:
    int m_listenfd;   // 服务端用于监听的socket。
    int m_connfd;     // 客户端连接上来的socket。
    bool m_btimeout;   // 调用Read和Write方法时,失败的原因是否是超时:true-超时,false-未超时。
    int m_buflen;     // 调用Read方法后,接收到的报文的大小,单位:字节。

    CTcpServer();  // 构造函数。

    // 服务端初始化。
    // port:指定服务端用于监听的端口。
    // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
    bool InitServer(const unsigned int port);

    // 阻塞等待客户端的连接请求。
    // 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
    bool Accept();

    // 获取客户端的ip地址。
    // 返回值:客户端的ip地址,如"192.168.1.100"。
    char *GetIP();

    // 接收客户端发送过来的数据。
    // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
    // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
    // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
    bool Read(char *buffer, const int itimeout = 0);

    // 向客户端发送数据。
    // buffer:待发送数据缓冲区的地址。
    // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
    // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
    bool Write(const char *buffer, const int ibuflen = 0);

    // 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
    void CloseListen();

    // 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
    void CloseClient();

    ~CTcpServer();  // 析构函数自动关闭socket,释放资源。
};

_freecplus.cpp

CTcpServer::CTcpServer() {
    m_listenfd = -1;
    m_connfd = -1;
    m_socklen = 0;
    m_btimeout = false;
}

bool CTcpServer::InitServer(const unsigned int port) {
    if (m_listenfd > 0) {
        close(m_listenfd);
        m_listenfd = -1;
    }

    if ((m_listenfd = socket(AF_INET, SOCK_STREAM, 0)) <= 0) return false;

    // WINDOWS平台如下
    //char b_opt='1';
    //setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&b_opt,sizeof(b_opt));

    // Linux如下
    int opt = 1;
    unsigned int len = sizeof(opt);
    setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, len);

    memset(&m_servaddr, 0, sizeof(m_servaddr));
    m_servaddr.sin_family = AF_INET;
    m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    m_servaddr.sin_port = htons(port);
    if (bind(m_listenfd, (struct sockaddr *) &m_servaddr, sizeof(m_servaddr)) != 0) {
        CloseListen();
        return false;
    }

    if (listen(m_listenfd, 5) != 0) {
        CloseListen();
        return false;
    }

    m_socklen = sizeof(struct sockaddr_in);

    return true;
}

bool CTcpServer::Accept() {
    if (m_listenfd == -1) return false;

    if ((m_connfd = accept(m_listenfd, (struct sockaddr *) &m_clientaddr, (socklen_t * ) & m_socklen)) < 0)
        return false;

    return true;
}

char *CTcpServer::GetIP() {
    return (inet_ntoa(m_clientaddr.sin_addr));
}

bool CTcpServer::Read(char *buffer, const int itimeout) {
    if (m_connfd == -1) return false;

    if (itimeout > 0) {
        fd_set tmpfd;

        FD_ZERO(&tmpfd);
        FD_SET(m_connfd, &tmpfd);

        struct timeval timeout;
        timeout.tv_sec = itimeout;
        timeout.tv_usec = 0;

        m_btimeout = false;

        int i;
        if ((i = select(m_connfd + 1, &tmpfd, 0, 0, &timeout)) <= 0) {
            if (i == 0) m_btimeout = true;
            return false;
        }
    }

    m_buflen = 0;
    return (TcpRead(m_connfd, buffer, &m_buflen));
}

bool CTcpServer::Write(const char *buffer, const int ibuflen) {
    if (m_connfd == -1) return false;

    fd_set tmpfd;

    FD_ZERO(&tmpfd);
    FD_SET(m_connfd, &tmpfd);

    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    m_btimeout = false;

    int i;
    if ((i = select(m_connfd + 1, 0, &tmpfd, 0, &timeout)) <= 0) {
        if (i == 0) m_btimeout = true;
        return false;
    }

    int ilen = ibuflen;
    if (ilen == 0) ilen = strlen(buffer);

    return (TcpWrite(m_connfd, buffer, ilen));
}

void CTcpServer::CloseListen() {
    if (m_listenfd > 0) {
        close(m_listenfd);
        m_listenfd = -1;
    }
}

void CTcpServer::CloseClient() {
    if (m_connfd > 0) {
        close(m_connfd);
        m_connfd = -1;
    }
}

CTcpServer::~CTcpServer() {
    CloseListen();
    CloseClient();
}

客户端CTcpClient类

_freecplus.h

// socket通信的客户端类
class CTcpClient {
public:
    int m_sockfd;    // 客户端的socket.
    char m_ip[21];    // 服务端的ip地址。
    int m_port;      // 与服务端通信的端口。
    bool m_btimeout;  // 调用Read和Write方法时,失败的原因是否是超时:true-超时,false-未超时。
    int m_buflen;    // 调用Read方法后,接收到的报文的大小,单位:字节。

    CTcpClient();  // 构造函数。

    // 向服务端发起连接请求。
    // ip:服务端的ip地址。
    // port:服务端监听的端口。
    // 返回值:true-成功;false-失败。
    bool ConnectToServer(const char *ip, const int port);

    // 接收服务端发送过来的数据。
    // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
    // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
    // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
    bool Read(char *buffer, const int itimeout = 0);

    // 向服务端发送数据。
    // buffer:待发送数据缓冲区的地址。
    // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
    // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
    bool Write(const char *buffer, const int ibuflen = 0);

    // 断开与服务端的连接
    void Close();

    ~CTcpClient();  // 析构函数自动关闭socket,释放资源。
};

_freecplus.cpp

CTcpClient::CTcpClient() {
    m_sockfd = -1;
    memset(m_ip, 0, sizeof(m_ip));
    m_port = 0;
    m_btimeout = false;
}

bool CTcpClient::ConnectToServer(const char *ip, const int port) {
    if (m_sockfd != -1) {
        close(m_sockfd);
        m_sockfd = -1;
    }

    strcpy(m_ip, ip);
    m_port = port;

    struct hostent *h;
    struct sockaddr_in servaddr;

    if ((m_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return false;

    if (!(h = gethostbyname(m_ip))) {
        close(m_sockfd);
        m_sockfd = -1;
        return false;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(m_port);  // 指定服务端的通讯端口
    memcpy(&servaddr.sin_addr, h->h_addr, h->h_length);

    if (connect(m_sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) != 0) {
        close(m_sockfd);
        m_sockfd = -1;
        return false;
    }

    return true;
}

bool CTcpClient::Read(char *buffer, const int itimeout) {
    if (m_sockfd == -1) return false;

    if (itimeout > 0) {
        fd_set tmpfd;

        FD_ZERO(&tmpfd);
        FD_SET(m_sockfd, &tmpfd);

        struct timeval timeout;
        timeout.tv_sec = itimeout;
        timeout.tv_usec = 0;

        m_btimeout = false;

        int i;
        if ((i = select(m_sockfd + 1, &tmpfd, 0, 0, &timeout)) <= 0) {
            if (i == 0) m_btimeout = true;
            return false;
        }
    }

    m_buflen = 0;
    return (TcpRead(m_sockfd, buffer, &m_buflen));
}

bool CTcpClient::Write(const char *buffer, const int ibuflen) {
    if (m_sockfd == -1) return false;

    fd_set tmpfd;

    FD_ZERO(&tmpfd);
    FD_SET(m_sockfd, &tmpfd);

    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    m_btimeout = false;

    int i;
    if ((i = select(m_sockfd + 1, 0, &tmpfd, 0, &timeout)) <= 0) {
        if (i == 0) m_btimeout = true;
        return false;
    }

    int ilen = ibuflen;

    if (ibuflen == 0) ilen = strlen(buffer);

    return (TcpWrite(m_sockfd, buffer, ilen));
}

void CTcpClient::Close() {
    if (m_sockfd > 0) close(m_sockfd);

    m_sockfd = -1;
    memset(m_ip, 0, sizeof(m_ip));
    m_port = 0;
    m_btimeout = false;
}

CTcpClient::~CTcpClient() {
    Close();
}

简单服务端和客户端示例

_server.cpp

#include "_freecplus.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Using:./demo48 port\nExample:./demo48 5005\n\n");
        return -1;
    }

    CTcpServer TcpServer;   // 创建服务端对象。

    if (TcpServer.InitServer(atoi(argv[1])) == false) // 初始化TcpServer的通信端口。
    {
        printf("TcpServer.InitServer(%s) failed.\n", argv[1]);
        return -1;
    }

    if (TcpServer.Accept() == false)   // 等待客户端连接。
    {
        printf("TcpServer.Accept() failed.\n");
        return -1;
    }

    printf("客户端(%s)已连接。\n", TcpServer.GetIP());

    char strbuffer[1024];  // 存放数据的缓冲区。

    while (true) {
        memset(strbuffer, 0, sizeof(strbuffer));
        //if (TcpServer.Read(strbuffer,300)==false) break; // 接收客户端发过来的请求报文。
        if (TcpServer.Read(strbuffer, 10) == false) break; // 接收客户端发过来的请求报文。
        printf("接收:%s\n", strbuffer);

        strcat(strbuffer, "ok");      // 在客户端的报文后加上"ok"。
        printf("发送:%s\n", strbuffer);
        if (TcpServer.Write(strbuffer) == false) break;     // 向客户端回应报文。
    }

    printf("客户端已断开。\n");    // 程序直接退出,析构函数会释放资源。
}

_client.cpp

#include "_freecplus.h"

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Using:./demo47 ip port\nExample:./demo47 172.21.0.3 5005\n\n");
        return -1;
    }

    CTcpClient TcpClient;   // 创建客户端的对象。

    if (TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false) // 向服务端发起连接请求。
    {
        printf("TcpClient.ConnectToServer(\"%s\",%s) failed.\n", argv[1], argv[2]);
        return -1;
    }

    char strbuffer[1024];    // 存放数据的缓冲区。

    for (int ii = 0; ii < 30; ii++)   // 利用循环,与服务端进行5次交互。
    {
        memset(strbuffer, 0, sizeof(strbuffer));
        snprintf(strbuffer, 50, "%d:这是第%d个超级女生,编号%03d。", getpid(), ii + 1, ii + 1);
        printf("发送:%s\n", strbuffer);
        if (TcpClient.Write(strbuffer) == false) break;    // 向服务端发送请求报文。

        memset(strbuffer, 0, sizeof(strbuffer));
        if (TcpClient.Read(strbuffer, 20) == false) break;  // 接收服务端的回应报文。
        printf("接收:%s\n", strbuffer);

        sleep(1);
    }

    // 程序直接退出,析构函数会释放资源。
}
  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值