Socket编程

 
什么是socket
socket是使用 Unix 文件描述符 (fiel descriptor) 和其他程序通讯的方式。Unix 程序在执行任何形式的 I/O时, 程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。 但是,这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其他的东西。Unix 中所有的东西是文件。因此,想和 Internet 上别 的程序通讯的时候,将要通过文件描述符。
从哪里得到网络通讯的文件描述符呢,利用系统调用 socket() 。它返回套接口描述符(socket descriptor),然后你再通过它来调用 send() recv() 。如果它是个文件描述符,那么为什么不用一般的调用 read() write() 来通过套接口通讯。答案是可以,但是使用 send() recv() 让程序员更好的控制数据传输。
在我们的世界上,有很多种套接口。有 DARPA Internet 地址 (Internet 套接口),本地节点的路径名 (Unix 套接口),CCITT X.25 地址 (可以完全忽略 X.25 套接口)。 也许在Unix 机器上还有其他的。这里只讲第一种:Internet 套接口。
 
Internet 套接口的两种类型
一种是 "Stream Sockets",另外一种是 "Datagram Sockets"。数据报套接口有时也叫无连接套接口。流式套接口是可靠的双向通讯的数据流。如果向套接口安顺序输出“1,2”,那么它们将安顺序“1,2”到达另一边。它们也是无错误的传递的,有自己的错误控制。
 
telnet 就使用流式套接口。WWW 浏览器使用的 HTTP 协议也使用他们。流式套接口可以达到高质量的数据传输,它使用了“传输控制协议 (The Transmission Control Protocol)”,TCP 控制数据按顺序到达并且没有错误。TCP/IP中IP 是指 “Internet 协议”, IP 只是处理 Internet 路由而已。
 
数据报套接口为什么不可靠。如果发送一个数据报,它可能到达,它可能次序颠倒了。如果它到达,那么在这个包的内部是无错误的。数据报也使用 IP 作路由,但是不选择 TCP。它使用“用户数据报协议 (User Datagram Protocol)”。为什么是无连接,主要原因是因为它并不象流式套接口那样维持一个连接。只要建立一个包,在目标信息中构造一个 IP 头,然后发出去。不需要连接。应用程序有: tftp, bootp 等等。
如果数据丢失了这些程序如何正常工作。每个程序在 UDP 上有自己的协议。例如,ftp 协议每发出一个包,收到者发回一个包来说“我收到了!” (一个“命令正确应答”也叫“ACK” 包)。如果在一定时间内,发送方没有收到应答, 它将重新发送,直到得到 ACK。这一点在实现 SOCK_DGRAM 应用程序的时候非常重要。
 
网络理论
 数据封装 (Data Encapsulation)主要的内容是:一个包,先是被第一个协议包装(“封装”), 然后,整个数据被另外一个协议封装,然后下一个,一直重复下去,直到硬件(物理)层( Ethernet )。
 网络分层模型 (Layered Network Model)。 这种网络模型在描述网络系统上相对其他模型有很多优点。例如,写一个套接口,程序而不用关心数据的物理传输(串行口,以太网,连接单元接口 (AUI) 还是其他介质)。 因为底层的程序为我们处理。实际的网络硬件和拓扑对于程序员来说是透明的。
整个层次模型:
应用层 (Application)
表示层 (Presentation)
会话层 (Session)
传输层 (Transport)
网络层 (Network)
数据链路层 (Data Link)
物理层 (Physical)
物理层是硬件(串口,以太网等等)。应用层是和硬件层相隔最远的—它是用户和网络交互的地方。
把它应用到 Unix,结果是:
应用层 (Application Layer) (telnet, ftp, 等等)
传输层 (Host-to-Host Transport Layer) (TCP, UDP)
Internet 层 (Internet Layer) (IP 和路由)
网络访问层 (Network Access Layer) (网络层,数据链路层和物理层)
现在,可能看到这些层次如何协调来封装原始的数据了。
 
struct
套接口用到的各种数据类型。
首先是简单的一个:socket descriptor。它是int类型,仅仅是一个常见的 int
两种字节排列顺序:重要的字节在前面(有时叫 "octet"),或者不重要的字节在前面。 前一种叫“网络字节顺序 (Network Byte Order)”。有些机器在内部是按照这个顺序储存数据,而另外一些则不然。当某数据必须按照 NBO 顺序,那么要调用函数( htons() )来将它从本机字节顺序(Host Byte Order) 转换过来。如果没有提到 NBO, 那么就让它是本机字节顺序。
struct sockaddr . 这个数据结构为许多类型的套接口储存套接口地址信息:
    struct sockaddr {
        unsigned short    sa_family;    /* address family, AF_xxx       */
        char              sa_data[14]; /* 14 bytes of protocol address */
    };
sa_family 能够是各种各样的事情,但是在这是 " AF_INET "。 sa_data 为套接口储存目标地址和端口信息。为了对付 struct sockaddr ,程序员创造了一个并列的结构: struct sockaddr_in (in代表Internet)
    struct sockaddr_in {
        short int          sin_family; /* Address family               */
        unsigned short int sin_port;    /* Port number                  */
        struct in_addr     sin_addr;    /* Internet address             */
        unsigned char      sin_zero[8]; /* Same size as struct sockaddr */
    };
这个数据结构让可以轻松处理套接口地址的基本元素。注意 sin_zero (它被加入到这个结构,并且长度和 struct sockaddr 一样) 应该使用函数 bzero() memset() 来全部置零。这样,即使 socket() 想要的是 struct sockaddr * , 仍然可以使用 struct sockaddr_in。 注意 sin_family sa_family 一致并能够设置为 " AF_INET "。最后, sin_port sin_addr 必须是网络字节顺序 (Network Byte Order)。
再看这个数据结构: struct in_addr , 有这样一个联合 (unions):
    /* Internet address (a structure for historical reasons) */
    struct in_addr {
        unsigned long s_addr;
    };
如果声明 " ina " 是 数据结构 struct sockaddr_in 的实例,那么 " ina.sin_addr.s_addr " 就储存4字节的 IP 地址(网络字节顺序)。如果你系统使用的还是联合 struct in_addr ,还是可以放心4字节的 IP 地址是和上面说的一样(这是因为 #define 。)
 
Convert the Natives
网络字节顺序和本机字节顺序之间的转换。能够转换两种类型: short (两个字节)和 long (四个字节)。这个函数对于变量类型 unsigned 也适用。假设想将 short 从本机字节顺序转换为网络字节顺序。用 "h" 表示 "本机 (host)",接着是 "to",然后用 "n" 表示 "网络 (network)",最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。
htons() --"Host to Network Short"
htonl() --"Host to Network Long"
ntohs() --"Network to Host Short"
ntohl() --"Network to Host Long"
记住:在将数据放到网络上的时候,确信它们是网络字节顺序。
为什么 struct sockaddr_in 数据结构中, sin_addr sin_port 需要转换为网络字节顺序,而 sin_family 不需要呢? 答案是: sin_addr sin_port 分别封装在包的 IP 和 UDP 层。因此,他们必须要是网络字节顺序。 但是 sin_family 域只是被内核 (kernel) 使用来决定在数据结构中包含什么 类型的地址,所以它应该是本机字节顺序。也即 sin_family 没有发送到网络上,它们是本机字节顺序。
 
IP 地址
首先,假设用 struct sockaddr_in ina ,想将 IP 地址 "132.241.5.10" 储存到其中。要用的函数是 inet_addr() ,转换 numbers-and-dots 格式的 IP 地址到 unsigned long。这个工作可以这样来做:
    ina.sin_addr.s_addr = inet_addr("132.241.5.10");
注意: inet_addr() 返回的地址已经是按照网络字节顺序的,没有必要再去调用 htonl()
上面的代码可不是很健壮 (robust),因为没有错误检查。 inet_addr() 在发生错误 的时候返回 -1 。记得二进制数,在IP 地址为 255.255.255.255 的时候返回的是 (unsigned)-1。 这是个广播地址。记住正确的使用错误检查。
数据结构 struct in_addr 如何按照 numbers-and-dots 格式打印。在这个时候,要用函数 inet_ntoa() ("ntoa" 意思是 "network to ascii"):
    printf("%s",inet_ntoa(ina.sin_addr));
它将打印 IP 地址。注意的是:函数 inet_ntoa() 的参数是 struct in_addr ,而不是 long 。同时要注意的是它返回的是一个指向字符的指针。 在 inet_ntoa 内部的指针静态地储存字符数组,因此每次调用 inet_ntoa() 的时候它将覆盖以前的内容。例如:
    char *a1, *a2;
    ……
    a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */
    a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */
    printf("address 1: %s/n",a1);
    printf("address 2: %s/n",a2);
运行结果是:
    address 1: 132.241.5.10
    address 2: 132.241.5.10
 
DNS
DNS代表"域名服务 (Domain Name Service)"。主要的功能是:我们给它一个容易记忆的某站点的地址,它给我们IP 地址(然后就可以使用 bind() , connect() , sendto() 或者其他函数。)当一个人输入:
    telnet whitehouse.gov
telnet 能知道它将连接 ( connect() ) 到 "198.137.240.100"。
但是这是如何工作的呢?可以调用函数 gethostbyname()
    #include <netdb.h>
   
    struct hostent *gethostbyname(const char *name);
它返回一个指向 struct hostent 的指针。这个数据结构是这样的:
    struct hostent {
        char    *h_name;
        char    **h_aliases;
        int     h_addrtype;
        int     h_length;
        char    **h_addr_list;
    };
    #define h_addr h_addr_list[0]
这里是这个数据结构的详细资料: struct hostent :
h_name - Official name of the host.
h_aliases - A NULL-terminated array of alternate names for the host.
h_addrtype - The type of address being returned; usually AF_INET .
h_length - The length of the address in bytes.
h_addr_list - A zero-terminated array of network addresses for the host. Host addresses are in Network Byte Order.
h_addr - The first address in h_addr_list .
gethostbyname() 成功时返回一个指向 struct hostent 的 指针,或者是个空 (NULL) 指针。(但是和以前不同, errno 不设置, h_errno 设置错误信息。请看下面的 herror() 。)
但是如何使用呢? 这里是个例子:
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <netinet/in.h>
 
    int main(int argc, char *argv[])
    {
        struct hostent *h;
 
        if (argc != 2)
{ /* error check the command line */
            fprintf(stderr,"usage: getip address/n");
            exit(1);
        }
 
        if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
            herror("gethostbyname");
            exit(1);
        }
 
        printf("Host name : %s/n", h->h_name);
        printf("IP Address : %s/n",inet_ntoa(*((struct in_addr *)h->h_addr)));
 
        return 0;
    }
在使用 gethostbyname() 的时候,不能用 perror() 打印错误信息(因 为 errno 没有使用),应该调用 herror()
相当简单,只是传递一个保存机器名的字符串(例如 "whitehouse.gov") 给 gethostbyname() ,然后从返回的数据结构 struct hostent 中 收集信息。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值