【UNIX网络编程】| 【01】基本套接字编程(套接字地址结构、值-结果参数、字节序基本函数)

【UNIX网络编程】| 【01】基本套接字编程(套接字地址结构、值-结果参数、字节序基本函数),第1张

文章目录
    • 1、简介
        • 1.1 IPv4套接字地址结构
        • 1.2 通用套接字地址结构
        • 1.3 IPv6套接字结构
        • 1.4 新的通过套接字地址结构
        • 1.5 套接字结构的比较
    • 2、值-结果参数
        • 2.1 从进程到内核传递
        • 2.2 从内核到进程
    • 3、字节排序函数
    • 4、字节 *** 纵函数
    • 5、inet_aton、inet_addr和inet_ntoa函数
    • 6、inet_pton和inet_ntop函数
    • 7、sock_ntop和相关函数
    • 8、readn、writen和readline函数

1、简介
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数;
每个协议族都定义它自己的套接字地址结构;均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾;
1.1 IPv4套接字地址结构
// 即网际套接字地址结构
#include 


struct in_addr{
	in_addr_t s_addr;		// 32位IPv4地址的网络字节序
};	

// POSIX规范只需要中间三个字段即可
// 该结构的大小至少位16字节
struct sockaddr_in {
	uint8_t sin_len;			// 无需			 
	sa_family_t sin_family;		// 协议族如:AF_INET
	in_port_t sin_port;			// 16位TCP或UDP端口号
	struct in_addr sin_addr;	
	char sin_zero[8];			// 无需
};
【注意】:
	- 在适用时IPv4地址和port总是以网络字节序来存储;
	- 套接字结构仅在给定主机上适用,并不在主机之间传递;

1.2 通用套接字地址结构
参数传递中,该结构总是以引用形式传递;
【为了解决如何声明传递指针的数据类型上的问题】:
	- 要求对函数任何调用都指向特定于协议的套接字地址结构的指针进行强制转换;
		如:
			struct sockaddr_in serv;
			bind(sfd, (struct sockaddr*)&serv, sizeof(serv));
#include 

struct sockaddr {
	uint8_t sa_len;
	sa_family_t sa_family;
	char sa_data[14];		// 协议专用地址
};
1.3 IPv6套接字结构
	struct in6_addr{
		uint8_t s6_addr[16];	// 128位IPv6地址
	};
#define SIN6_LEN	
struct sockaddr_in6{
	uint8_t sin6_len;			// 长度
	sa_family_t sin6_family;	// AF_INET6
	in_port_t sin6_port;		// 端口
	uint32_t sin6_flowinfo;		// 流信息
	struct in6_addr sin6_addr;	// IPv6地址
	uint32_t sin6_scope_id;		// 范围 ID
}; 
【注意】: 
- 若系统支持SIN6_LEN,则需要定义sin6_len;
- 该结构字段先后顺序做过编排,若该结构本身是64位对齐,则128位的sin6_addr字段也是64位对齐;
- sin6_flowinfo该字段分为两个字段:
	低序20位是流标;
	高序12位保留;
- sin6_scope_id最常见的是链路局部地址的接口索引;
1.4 新的通过套接字地址结构
struct sockaddr_storage{
	uint8_t ss_len;			// 结构体长度
	sa_family_t ss_family;	// 协议族
};

与旧通用套接字的差别

- sockaddr_storage能够满足最苛刻的对齐要求;
- sockaddr_storage足够大,能容纳系统支持的任何套接字地址结构;
1.5 套接字结构的比较

2、值-结果参数 2.1 从进程到内核传递
从进程到内核传递套接字地址结构函数有3个:bind、connect、sendto;
	- 此类函数分别有两个参数,某个套接字地址结构的指针,另一个是该结构的整数大小;
	struct sockaddr_in serv;
	connect(sockfd, (struct sockaddr*)&serv, sizeof(serv));

2.2 从内核到进程
该类函数有:accept、recvfrom、getsockname、getpeername;
	- 该类函数有两个参数:指向某个套接字地址结构的指针和指向表示结构大小的整数变量的指针;
	struct sockaddr_un cli;
	socklen_t len = sizeof(cli);
	getpeername(ufd, (struct sockaddr*)&cli, &len);
【参数3的含义】:
	当函数被调用时,传入用于告诉内核该结构的大小,故内核在写该结构时不会越界;
	当函数返回值,结构大小又是一个结果,告诉进程内核在该结构中存储了多少信息;
	若地址长度固定,则返回固定长度(IPv4为16、IPv6为28);
	此类函数还有select、getsocketopt、热recvmsg、ifconf、sysctl;

3、字节排序函数

【网络协议】| 【01】网络字节序大端、小端

大端、小端没有标准可循,都有系统使用;把给定系统所用是字节称为主机字节序;
系统能够在复位或运行时,在大端和小端之间进行切换;

【网络协议】:发送协议栈和接收协议栈中必须就这些多字节阶段各个字节的传送顺序达成一致;
	- 网际协议使用大端字节序来传送这些字节;
/** 查看当前系统使用大端还是小端 */
  8 #include <iostream>
  9 
 10 using namespace std;
 11 
 12 
 13 void test() {
 14     union {
 15         short s;    
 16         char c[sizeof(short)];
 17     }un;
 18     /*  
 19      * short为两字节
 20      * 0x0102:
 21      *  若为大端:0x0102
 22      *  若为小端:0x0201
 23      * */       
 24     un.s = 0x0102;  // 查看它的两个连续字节c[0]和c[1]的地址,来确定字节序
 25     if(sizeof(short) == 2) {
 26         if(un.c[0] == 1 && un.c[1] == 2)
 27             cout << "big-endian" << endl; 
 28         else if(un.c[0] == 2 && un.c[1] == 1)
 29             cout << "little-endian" << endl;
 30         else
 31             cout << "unknown" << endl;
 32     }else {
 33         cout << "sizeof(short) = " << sizeof(short) << endl;
 34     }   
 35     exit(0);
 36 }
 37 
 38 int main(int argc, char* argv[])
 39 {
 40     test();
 41 
 42     return 0;
 43 }

如何在主机字节序和网络字节序之间相互转换

#include 

uint16_t htons(uint64_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t htohs(uint16_t host16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

/**
其中h代表host,n代表network,l表示long,s表示long;
目前,将l视为32位的值,s视为16位的值,尽管l位但,在该函数 *** 作中仍为32位;
*/

位序

它表示按照在线缆上出现的顺序排列的4个字节,最左边的位是最早出现的最高有效位;
	- 注意位序的编号从0开始,分配给最高有效位的编号为0;
4、字节 *** 纵函数
用来处理套接字结构,为了 *** 纵如IP地址此类的字段,由于该字段可能包含值为0的字节,并非C字符串;
#include 

void bzero(void *dest, size_t nbytes);
/**
@func: 将目标字符串中指定数目的字节置为0;
	一般用于将套接字地址结构初始化为0;
*/
void bcopy(const void *src, void *dest, size_t nbytes);
/**
@func: 将指定数目的字节从源字符串移到目标字符串;
*/

int bcmp(const void *ptr1, const void *prt2, size_t nbytes);
/**
@func: 比较两个任意的字符串;
*/

ANSI C函数

#include 

void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/**
memcpy类似bcopy,当源字符串和目标字符串重叠时,bcopy才能正确处理;
*/
5、inet_aton、inet_addr和inet_ntoa函数
以下为地址转换函数,在ASCII字符串与网络字节序的二进制之间转换网际地址;
	- inet_aton、inet_addr和inet_ntoa在点分十进制数串与它长度为32位的网络字节序二进制间转换IPv4地址;
	- 其中inet_aton、inet_ntoa对于IPv4和IPv6都适用;
#include 

int inet_aton(const char *strptr, struct in_addr *addrptr);
/**
@func: 将strptr所值C字符串转换为32位的网络字节序二进制值;并通过addrptr来存储;
return: 成功返回1,失败返回0; 
*/

in_addr_t inet_addr(const char *strptr);
/**
@func: 与上述转换、返回值相同;
【注意】:不能处理串为255.255.255.255由于被用来指示函数失败;
	但目前已被弃用,使用inet_aton;
*/

char *inet_ntoa(struct in_addr inaddr);
/**
@func: 将32位的网络字节序二进制IPv4地址转换为相应的点分十进制数串;
	返回值指向字符串在静态内存中,该函数不可重入;
*/
6、inet_pton和inet_ntop函数
此类函数适用于IPv4和IPv6,其中p代表表达为ASCII字符串,n代表数值存放到套接字结构中的二进制值;
#include 

int inet_pton(int family, const char *strptr, void *addrptr);
/**
@func: 尝试从表达格式转换到数值格式,由strptr,通过addrptr指针存放二进制结果;
@param family: 可为AF_INET和AF_INET6,若该参数错误,则返回EAFNOSUPPORT;
@param addrptr: 不可为空指针,必须为目标存储单元分配内存并指定其大小;
return: 成功返回1;
*/
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
/**
@func: 从数值格式转换到表达格式;
@param len: 目标存储单元的大小,避免函数溢出其调用者的缓冲区;
	可使用宏定义,助于指定该大小;
	#include 
	
	#define INET_ADDRSTRLEN 16		// IPv4
	#define INET6_ADDRSTRLEN 46		// IPv6
*/

简化版

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int inet_pton_t(int family, const char *strptr, void *addrptr) {
    if(family == AF_INET) {
        struct in_addr in_val;
        if(inet_aton(strptr, &in_val)) {
            memcpy(addrptr, &in_val, sizeof(struct in_addr));
            return 1;
        }
        return 0;
    }
    errno = EAFNOSUPPORT;
    return -1;
}


const char *inet_ntop_t(int family, const void *addrptr, char *strptr, size_t len) {
    const u_char *p = (const u_char*)addrptr;
    if(family == AF_INET) {
        char temp[INET_ADDRSTRLEN];
        snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
        if(strlen(temp) >= len) {
            errno = ENOSPC;
            return NULL;
        }
        strcpy(strptr, temp);
        return strptr;
    }
    errno = EAFNOSUPPORT;
    return NULL;
}


int main(int argc, char* argv[])
{
    const char *str = "192.1.3.2";
    char buf[128];
    inet_pton_t(AF_INET, str, buf);
    char ret[128];
    inet_ntop_t(AF_INET, buf, ret, 128);

    cout << ret << endl;

    return 0;
}
7、sock_ntop和相关函数
如果适用inet_ntop,那么将要求调用者传递一个指向某个二进制地址的指针,该地址通常包含一个套接字地址结构
要求调用者将该结构的格式和地址族;

为了解决上述问题,我们将自定义sock_ntop指向某个套接字地址结构的指针位参数,查看该结构的内部,调用适当的函数;

sock_ntop实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef HAVE_SOCKADDR_DL_STURCT
#include 
#endif

char *sock_ntop(const struct sockaddr *sa, socklen_t salen){
    char portstr[8];
    static char str[128];

    switch (sa->sa_family) {
        case AF_INET: { /* IPv4 */
            struct sockaddr_in *sin = (struct sockaddr_in *) sa;
            if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str) == NULL)) {
                return NULL;
            }
            if (ntohs(sin->sin_port) != 0) {
                snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
                strcat(str, portstr);
            }
            return str;
        }
#ifdef IPV6
            case AF_INET6 : {   /* IPv6 */
                struct sockaddr_in6 *sin6 = (struct sockaddr_in6) as;
                str[0] = '[';
                if(inet_ntop(AF_INET6, &sin6->sin6_addr, str+1, sizeof(str)-1) == NULL)
                    return NULL;
                if(ntohs(sin6->sin6_port) != 0){
                    snprintf(portstr,  sizeof(portstr), "]:%d", ntohs(sin6->sin6_port));
                    strcat(str, portstr);
                    return str;
                }

                return str+1;
            }
#endif

#ifdef AF_UNIX
            case AF_UNIX : {    /* unix */
                struct sockaddr_un *unp = (struct sockaddr_un*)sa;
                if(unp->sun_path[0] == 0)
                    strcpy(str, "(no pathname bound)");
                else
                    snprintf(str, sizeof(str), "%s", unp->sun_path);
                return str;
            }
#endif

#ifdef HAVE_SOCKADDR_DL_STRUCT
            case AF_LINK: {
                struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa;
                if(sdl->sdl_nlen > 0)
                    snprintf(str, sizeof(str), "%*s (index %d)",
                             sdl->sdl_nlen, &sdl->sdl_data[0], sdl->sdl_index);
                else
                    snprintf(str, sizeof(str), "AF_LINKm index=%d", sdl->sdl_index);
                return str;
            }
#endif
        default: {
            snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d",
                     sa->sa_family, salen);
            return str;
        }
    }
    return NULL;
}

// 包裹函数,对sock_ntop进行错误处理
char *Sock_ntop(const struct sockaddr *sa, socklen_t salen) {
    char *ptr;

    if((ptr == sock_ntop(sa, salen)) == NULL) {
        printf("sock_ntop error");
        exit(1);
    }
    return ptr;
}
8、readn、writen和readline函数
字节流套接字上调用read和write的字节数可能比请求的数量少,不同于通常的文件;
导致该现象可能是内核中用于套接字的缓冲区可能已达极限,此时需要再次调用write或read;

==> 为了不让实现返沪iyig不足的字节计数值,改写使用writen代替write函数;
/**
 * @brief: 读取文件,指定长度并返回读取了多少字符
 * @param fd: 文件描述符
 * @param buf: 读取内容容器
 * @param n: 读取文件的个数
 * */
ssize_t Readn(int fd, void *buf, size_t n) {
    size_t nleft = n;   // 还剩多少字符
    size_t nread;       // 读取多少字符
    char *ptr = (char*)buf; // 装载读取内容

    while(nleft > 0) {
        if((nread = read(fd, ptr, nleft)) < 0){
            /* 读取失败 */
            if(errno == EINTR)
                nread = 0;
            else
                return -1;
        }else if(nread == 0)
            break;
        nleft -= nread;     // 剩余多少字符未读取
        ptr += nread;       // 指针偏移
    }
    return n-nleft;
}

ssize_t Written(int fd, const void *buf, size_t n) {
    size_t nleft = n;
    ssize_t nwritten;
    const char *ptr = (char *)buf;
    while (nleft > 0) {
        if((nwritten = write(fd, ptr, nleft)) <= 0){
            if(nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static int read_cnt;
static char* read_ptr;
static char read_buf[128];

/** 每次最多读取128个字符 */
static ssize_t my_read(int fd, char *ptr) {
    if(read_cnt <= 0){
    again:
        if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if(errno == EINTR)
                goto again;
            return -1;
        }else if(read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;
    return 1;
}

ssize_t Readline(int fd, void *buf, size_t maxlen) {
    ssize_t n, rc;
    char C, *ptr;
    ptr = (char*)buf;
    for(n = 1; n < maxlen; ++n) {
        if((rc = my_read(fd, &C)) == 1) {
            *ptr++ = C;
            if(C == '\n') break;
        }else if(rc == 0) {
            *ptr = 0;
            return n-1;
        }else {
            return -1;
        }
    }
    *ptr = 0;
    return n;
}

/** 查看当前文本行是否收到新的数据 */
ssize_t readlinebuf(void **vptrptr) {
    if(read_cnt)
        *vptrptr = read_ptr;
    return read_cnt;
}


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1498878.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-25
下一篇 2022-06-25

发表评论

登录后才能评论

评论列表(0条)

保存