linux网络编程之socket(一):socket概述和字节序、地址转换函数

一、什么是socket

socket可以看成是用户进程与内核网络协议栈的编程接口。 socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及以后要讲的UNIX Domain Socket。然而,各种网络协议的地址格式并不相同,如下图所示:

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,如下所示:

   struct sockaddr_in {                sa_family_t    sin_family; /* address family: AF_INET */                in_port_t      sin_port;   /* port in network byte order */                struct in_addr sin_addr;   /* internet address */

char sin_zero[8]; /* pad bytes,  set to zero is ok */            };

  /* Internet address. */            struct in_addr {                uint32_t       s_addr;     /* address in network byte order */

           };

IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un结构体表示。各种socket地址结构体的开头都是相同的,对于unix 的某些实现来说 前8位表示整个结构体的长度,后8位表示地址类型,而Linux就没有长度字段,前2个字节都是地址类型。IPv4、IPv6和UNIX Domain Socket的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。 这样,只要取得某种sockaddr 结构体的首地址,不需要知道具体是哪种类型的sockaddr 结构体,就可以根据地址类型字段确定结构体中 的内容。因此,socket API可以接受各种类型的 sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些函数的参数都用struct sockaddr *类型表示,即通用地址结构,如下所示:

struct sockaddr { sa_family_t  sin_family; char sa_data[14]; }; 

sin_family:指定该地址家族 sa_data:由sin_family决定它的形式。

在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;

/* initialize servaddr */

bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

二、网络字节序

字节序

大端字节序(Big Endian) 最高有效位(MSB:Most Significant Bit)存储于最低内存地址处,最低有效位(LSB:Lowest Significant Bit)存 储于最高内存地址处。

小端字节序(Little Endian) 最高有效位(MSB:Most Significant Bit)存储于最高内存地址处,最低有效位(LSB:Lowest Significant Bit)存 储于最低内存地址处。

主机字节序 不同的主机有不同的字节序,如x86为小端字节序,Motorola 6800为大端字节序,ARM字节序是可配置的。

网络字节序 网络字节序规定为大端字节序

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长 整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相 应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

下面写个小程序测试下主机的大小端:

/*************************************************************************
    > File Name: byteorder.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Fri 01 Mar 2013 04:16:08 PM CST
 ************************************************************************/

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

int main(void)
{
    unsigned int x = 0x12345678;
    unsigned char *p = (unsigned char *)&x;
    printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);

    unsigned int y = htonl(x);
    p = (unsigned char *)&y;
    printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);

    return 0;
}

输出为:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./byteorder  78 56 34 12 12 34 56 78

即本主机是小端字节序,而经过htonl 转换后为网络字节序,即大端。

三、地址转换函数

前面提到的 sockaddr_in 结构体中的成员struct in_addr sin_addr表示32位的IP地址。但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换。

字符串转in_addr的函数:

#include <arpa/inet.h>

int inet_aton(const char *strptr, struct in_addr *addrptr);

in_addr_t inet_addr(const char *strptr);

int inet_pton(int family, const char *strptr, void *addrptr);

注意,转换而成的32位数是网络字节序的。

in_addr转字符串的函数:

char *inet_ntoa(struct in_addr inaddr);

const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);

注意,传入的32位数也是网络字节序的。

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。

下面写个小程序演示一下:

/*************************************************************************
    > File Name: addr_in.c
    > Author: Simba
    > Mail: dameng34@163.com
    > Created Time: Fri 01 Mar 2013 04:16:08 PM CST
 ************************************************************************/

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

int main(void)
{

    unsigned int  addr = inet_addr("192.168.0.100"); //转换后是网络字节序(大端)
    printf("add=%u\n", ntohl(addr));

    struct in_addr ipaddr;
    ipaddr.s_addr = addr;
    printf("%s\n", inet_ntoa(ipaddr));

    return 0;
}

输出为:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./addr_in  add=3232235620 192.168.0.100

注意,在打印addr的时候先转换成主机字节序。

四、套接字类型

流式套接字(SOCK_STREAM) 提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。 数据报式套接字(SOCK_DGRAM) 提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。 原始套接字(SOCK_RAW)

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏草根专栏

.NET Core/.NET之Stream简介

之前写了一篇C#装饰模式的文章提到了.NET Core的Stream, 所以这里尽量把Stream介绍全点. (都是书上的内容) .NET Core/.NET的...

3264
来自专栏我的技术专栏

Socket编程实践(2) Socket API 与 简单例程

1194
来自专栏大内老A

WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher

在本系列的第一部分、第二部分中,我们对WCF的channel layer进行了深入的讨论。我们接下来继续讨论WCF的service mode layer。本篇文...

1717
来自专栏拂晓风起

C++ link2005 error 错误 解决方法汇总(一般重复定义,如果都是不就是 函数定义和实现没有分离)

1084
来自专栏ascii0x03的安全笔记

C/C++网络编程时注意的问题小结

1.网络编程在自己定义结构体实现协议的时候,一定要注意字节对齐这个问题。否则sizeof和强制转换指针的时候都会出现很难发现的bug。 什么是字节对齐自行百度。...

3519
来自专栏菩提树下的杨过

Flex4中使用WCF

虽然flex跟.net交互的首选是FluorineFx,但是如果在某些特定情况下(比如服务端是现成的,不允许修改,或者服务端开发方不懂FluorineFx为何物...

1849
来自专栏我的技术专栏

Socket编程(4)TCP粘包问题及解决方案

893
来自专栏Danny的专栏

System.Data.SqlClient.SqlException: 将截断字符串或二进制数据

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

1204
来自专栏c#开发者

General Database Adapter for Biztalk Server 2006 介绍

General Database Adapter for Biztalk Server 2006 介绍 目前该adapter分单向的Receive Adapte...

31411
来自专栏安恒网络空间安全讲武堂

Writeup丨国赛线上初赛解题最后一波~

1294

扫码关注云+社区