首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

套接字,英文为socket,是一种双向的通信端口。位于网络中的主机通过连接的套接字提供的接口进行数据传输。本节将主要介绍使用套接字进行编程的一些基本概念。

13.1.1  套接字与端口

套接字是一种使用标准UNIX文件描述符(file descriptor)与其他程序通信的方式。套接字可以看作是处于不同主机之间的两个程序的通信连接端点。一方面程序将要传输的信息写入套接字中,而另一方面则通过读取套接字内的数据来获得传输的信息。

图13.1  套接字通信示意图

图13.1所示为使用套接字进行通信的示意图。假设存在两台主机A与B,在主机A中存在进程C,主机B中存在进程D,当进程C需要将数据送到进程D时,首先将数据写到套接字中,而进程D可以通过读取套接字来获得进程C发送的信息。

在网络中,不同计算机是通过IP地址来区分的,也就是说,要将数据由主机A发送到主机B,只要知道主机B的IP地址就可以确定数据要发送的目的地。但是,在主机A与B中不可能只有进程C和进程D两个进程。主机B在收到主机A发送来的数据后,如何才能确定该数据是发送给进程D?因此,还需要某种标识信息,用于描述网络通信数据发往的进程。TCP/IP协议提出了协议端口的概念,用于标识通信的进程。

当进程与某个端口绑定后,操作系统会将收到的给该端口的数据送往该进程。与文件描述符类似,每个端口都有被称为端口号的整数类型的标识符,该标识符用于区分不同的端口。不同协议可以使用相同的端口号进行数据传输。例如,TCP使用了344的端口号,UDP同样可以使用344端口号进行数据传输。

端口号为一个16位的无符号整数,其取值范围为0~65535。低于256的端口被作为系统的保留端口号,主要用于系统进程的通信,不在这一范围的端口号被称为自由端口号,可以由进程自由使用。

13.1.2  套接字编程相关数据结构

在开发使用套接字进行通信的程序时,常会用到sockaddr数据结构或sockaddr_in数据结构。sockaddr数据结构用于保存套接字的地址信息,具体定义如下:

struct sockaddr{

    unsigned short sa_family;

    char sa_data[14]

};

l     sa_family:用于指定地址族,如果是TCP/IP通信,该值取PF_INET。

l     sa_data:用于保存套接字的IP地址和端口号信息。

而sockaddr_in数据结构与sockaddr类似,该结构体的定义如下:

struct sockaddr_in {

   short int sin_family;

   unsigned short int sin_port;

   struct in_addr sin_addr;

   unsigned char sin_zero[8];

  };

l     sin_family:用于指定地址族。

l     sin_port:套接字通信的端口号。

l     sin_addr:通信的IP地址。

l     sin_zero[8]:用以填充0,保持与struct sockaddr同样大小。

由于sockaddr数据结构与sockaddr_in数据结构的大小是相同的,指向sockaddr_in的指针可以通过强制转换,转换成指向sockaddr结构的指针。

13.1.3  套接字类型

常用的TCP/IP协议的3种套接字类型如下所示。

l     流套接字(SOCK_STREAM):流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。

l     数据报套接字(SOCK_DGRAM):数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据包套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

l     原始套接字(SOCK_RAW):原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

13.1.4  big-endian与little-endian

不同体系的CPU在内存中的数据存储往往存在着差异。例如,Intel的x86系列处理器将低序字节存储在起始地址,而一些RISC架构的处理器,如IBM的370主机使用的PowerPC或Motorola公司生产的CPU,都将高序字节存储在起始位置。这两种不同的存储方式被称为little-endian和big-endian。

文本框:  
图13.2  big-endian与little-endian方式
数据存储示例

little-endian是x86系列CPU的数据存储方式,即将低序的部分存储在前面。而big-endian是将高序部分存储在前面。例如,要存储0xF432,little-endian将以32F4存储,而使用big-endian与此相反,将存储为F432,如图13.2所示。

程序p13.1.c讲解了如何判断系统是使用big-endian还是little-endian实现数据存储的。程序中使用的方法如下所示。

(1)利用联合的特点。联合中的数据成员是共享存储空间的,所分配的空间为数据成员中最大所需的内存数。程序定义了名为endian_un的联合体,其中包含两个数据成员,一个是short类型的数据成员(在32位系统上,short类型的长度是2字节),一个是字符类型的字符数组,字符数组的元素个数为short类型的字节数。

程序将var赋值为0x0102。由于联合结构的特点,bits字符串数组中同样存储了0x0102这一数值。通过判断字符串中的低位和高位存储的内容,就可以知道系统是little-endian还是big-endian的。

(2)通过强制类型转换实现。程序中通过取flag变量的地址,获得起始空间的存储内容。如果起始空间存储的是数据的低位内容,则表示存储方式为little-endian,否则为big-endian。

程序的具体代码如下:

//p13.1.c 判断big-endian与little-endian

#include <stdio.h>

//使用类型的强制转换实现little-endian与big-endian的判断

int is_little_endian(void)

{

    unsigned short flag=0x4321;

    if(*(unsigned char*)&flag==0x21)

        return 1;

    else

        return 0;

}

int main(void)

{

    //利用联合的特点来判断little-endian与big-endian

    union endian_un{

        short var;

        char bits[sizeof(short)];

    };

    union endian_un flag;

    flag.var=0x0102;

    //判断低位和高位的存储内容,确定是何种方式

    if(sizeof(short)==2){

        if(flag.bits[0]==1 && flag.bits[1]==2)

            printf("judged by first method, big-endian\n");

        else if(flag.bits[0]==2 && flag.bits[1]==1)

            printf("judged by first method, little-endian\n");

        else

            printf("cannot determine the type\n");

    }

    if(is_little_endian())

        printf("judged by second method, little-endian\n");

    else

        printf("judged by second method, big-endian\n");

    return 0;

}

使用gcc编译p13.1.c,获得名为p13.1的可执行文件。执行该程序,具体输出如下。可以看到x86系统的内存数据存储方式为little-endian方式。

[program@localhost charter13]$ gcc -o p13.1 p13.1.c

[program@localhost charter13]$ ./p13.1

judged by first method, little-endian

judged by second method, little-endian

[program@localhost charter13]$

之所以介绍big-endian和little-endian,是因为这一数据存储方式不仅影响程序在不同硬件平台中的移植,而且在网络编程中也要考虑字节顺序的问题。为了避免兼容性的问题,网络中的数据传输都使用了从高到低的顺序存储方式。因此,如果要将数据从低位字节优先(little-endian)的机器上发往网络,必须首先进行转换。而big-endian的机器是不需要转换的。

Linux系统提供了htons、htonl、ntohs、ntoh这4个函数用于进行字节顺序的转换。其中,h是host的缩写,n表示network。最后一个字符如果是s,表示short类型,如果是l,表示为long类型。4个函数的具体定义如下:

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

l     htonl/htons:表示主机字节顺序转换成网络字节顺序,htonl函数和htons函数的区别在于参数长度存在差异。

l     ntohl/ntohs:表示网络字节顺序转换成主机字节顺序,ntohl函数和ntohs函数的区别在于参数长度存在差异。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论