网络编程

来源:互联网 发布:php正则获取a标签href 编辑:程序博客网 时间:2024/06/10 04:39

    

Network programming

        网络无处不在,我们每天,都在和网络打交道,比如你打开浏览器去看一个网页,给别人发一个电子邮件, 这个时候,你就是在使用一个网络应用程序, 有趣的是,所有网络应用程序都是基于同样的网络模型,有着同样的逻辑结构,还有,依赖于同样的编程接口(programming interface).

      网络应用程序要用用到很多其他的概念,进程, 信号, 对齐方式(byte ordering), 内存映射, 动态内存的分配在网络应用程序中都扮演了重要角色,当然,我们还会引入一些新的概念,我们需要去了解C(client) /S (server) 模型,还有 怎么去利用Internet 提供的服务去编写C/S 模型的程序。 最后,我们利用所有讲到概念去实现一个小型,但有点实际功能的web 服务器,这个服务器可以给真实的浏览器提供动态以及静态的文本、图形的支持。

      Client-Server 编程模型

      每个网络应用程序都是基于client-server 模型,这个模型的框架是指,一个网络应用程序包括一个server 进程 和一个(或者多个)client 进程, server 进程管理着一定的资源,通过对这些资源的管理 去给client 提供一些service.  举个例子,一个web 服务器管理着磁盘上的很多文件,它的职现就是接收client 的请求,还有帮client 执行一些计算。
一个FTP 服务器也是管理着磁盘上的文件,它的职现就是接收和存储来自client的请求。同样的道理,一个email 服务器负责为client端提供内容更新和阅读的功能。
      client-server模型最基础的操作就是交互(transaction), 一个 client - server 交互包括四个步骤:
  1. 当client 需要服务的时候,它就向server 发送一个请求,这个请求就当是transaction的初始化,举个例子,当浏览器需要某个文件的时候,它就向Web 服务器发送一个请求。
  2. 当服务器接到这个请求,通过解析这个请求,然后去操作它所管理的一些资源,举个例子,当Web 服务器接到来自浏览器的请求后,它将会去读磁盘。
  3. server 发送一个相应给client. 然后等待下一次的请求。比如, Web server 将 client 需要的文件发送给client.
  4. client 接到了来自server的相应,然后去做一些动作。 比如, 当浏览器接到server 发来的文件后,它就把它在屏幕上显示出来。
        



    这里需要注意的是,本文中说的client 和 server 指的是进程,而不是某台机器,也不是指host, 虽然在文章中会大量提到它,一个host 上面可以同时运行很多个不同的client 和server . client 和 server 的交互可以是同一台机器上的,也可以是不同机器上的。无论client和server 的映射关系是怎样的,client - server 模型是一致的。

         Network

          Client 和 server 通常运行在不同的 host 上面,这时候它就得用到computer network 提供的各种软,硬件资源。 Network 是一个非常复杂的系统, 在这里,我们仅仅把它比较表面的东西拿出来讨论,目标是从程序员的视角,列出一些概念。
          对于一个host 来说,network 仅仅只是一些I/O 设备作为数据的源头或者存贮池(sink), 一块插在扩展槽上的网卡为网络提供了物理层的接口,从network上接收的数据,会经过网卡,I/O 还有内存总线最后传到内存里面去。比较典型的是通过 DMA 传输, 同理, 数据也可以通过网卡,从内存传到network上。

       物理上说,一个network 是一个通过地理位置的远近关系来分层的结构,在最下面一层是 LAN (Local Area Network), 一般跨度是一个栋楼或者一个学校。目前为止,最流行的 LAN 技术是 Ethernet( 以太网), 这个是 上世纪70 年代中期开发出来的, Ethernet 已经被证明是非常具有弹性(resilient)的, 它的速度范围可以是 3Mb/b 到 10 Gb/s.

       一个Ethernet segment 由一些网线加hub 组成。

    The Global IP Internet

IP 地址

一个IP 地址是一个无符号的32位整数,网络应用程序是放在这样一个结构体里面。
/* Internet address structure */struct in_addr {    unsigned int s_addr;   /* Network byte order (big-endian) */};


由于不同的主机有着不同的对齐方式,TCP/IP 定义了一个统一的network byte order(大端对齐),放在IP 地址这个结构体里面的数据始终以big endian 方式对齐,unix 提供了下面这些函数用于network 与 host 之间的转换。

#include <netinet/in.h>unsigned long int htonl(unsigned long int hostlong);unsigned short int htons(unsigned short int hostshort);                                       Returns: value in network byte orderungigned long int ntohl(unsigned long int netlong);unsigned short int ntohs(unsigned short int netshort);                                            Return:value in host byte order




    sockets 编程

     socket 接口(interface)  包括了很多个函数的组合,它配合Unix I/O 就可以去构建network 应用程序,目前市面上,大部分系统都有去实现socket, 包括 所有的类unix, windows, Macintosh  , 下图是 socket 基本架构。
   

   Socket 地址结构

     从Unix kernel 的角度看,一个socket 就是一个连接(communication)的端点(end point). 从Unix 程序的角度看, 一个socket 就是一个已经打开的文件的文件描述符。
     
     internet socket 地址是放在一个16 byte 长的结构体里面,
struct sockaddr {sa_family_tsa_family;/* address family, AF_xxx*/charsa_data[14];/* 14 bytes of protocol address*/};



IP 地址和port number 都是用 big endian 的方式的对齐

/* Structure describing an Internet (IP) socket address. */#define __SOCK_SIZE__16/* sizeof(struct sockaddr)*/struct sockaddr_in {  sa_family_tsin_family;/* Address family*/  unsigned short intsin_port;/* Port number*/  struct in_addrsin_addr;/* Internet address*/  /* Pad to size of `struct sockaddr'. */  unsigned char__pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];};


socket 函数

client 和 server 用socket 来创建文件描述符。
#include <sys/types.h>#include <sys/socket.h>int socket(int domain, int type, int protocol);                            Returns: nonnegative descriptor if OK, -1 on error

在我们的代码里面, socket 始终带如下参数
/ *   AF_INET 表明我们用的是Internet, 
  *   SOCK_STREAM 表明socket 将作为internet 连接的一个端点
  */
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
</pre><pre name="code" class="cpp">socket 返回的文件描述符仅仅只是一个打开的文件,我们还不能去read 和 write. 至于什么时候结束打开一个socket 取决于我们是server 还是 client. 
后续章节将会讲到,如果一个client 如何结束打开一个socket.
</pre><h3>connect 函数</h3><div>client 和server 建立一个连接(connection) 是通过调用 connect 函数</div><div></div><div><pre name="code" class="cpp">#include <sys/socket.h>int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);                                            Returns: 0 if OK, -1 on error

connect 函数尝试和 serv_addr 这个地址建立连接, addrlen 即为 sizeof(sockaddr_in) .
connect 函数直到连接成功建立或者发生error , 才会block.  如connect 返回0, 那么sockfd 描述符就可以被read 和 write. 
最后,一个成功的连接由这样一个二元组表示
(x:y, serv_addr.sin_addr:serv_addr.sin_port)
其中 x 就是client 的ip 地址, y 是其临时(ephemeral)端口号,x 和 y 就唯一标识了client host 上的一个 process.

open_clientfd 函数

我们可以很容易的将socket 和 connect 函数,打包在一起,做成一个 open_clientfd ,用clientfd 去和server 建立一个连接。clientfd 将和这样一个server 建立连接,这个server 名字叫hostname,  还有,正在监听 well-known 的端口。clientfd 最终会返回一个socket 描述符。

int open_clientfd(char *hostname, int port){     int clientfd;    struct hostent *hp;    struct sockaddr_in serveraddr;    if ((clientfd = sock(AF_INET, SOCK_STREAM, 0)) < 0)        return -1;  /* Check errno for cause of error */         /* Fill in the server's IP address and port */    if ((hp = gethostbyname(hostname)) == NULL)        return -2;  /* Check h_errno for cause of error */    bzero((char *) &serveraddr, sizeof(serveraddr));    serveradd.sin_family = AF_INET;    bcopy((char *)hp->h_addr_list[0],           (char *)&serveraddr.sin_addr.s_addr, hp->h_length);    serveraddr.sin_port = htons(port);       /* Establish a connection with the server */    if (connect(clientfd, (SA *)&serveraddr, sizeof(serveraddr)) <0)        return -1;    return clientfd;}



bind 函数

剩下的几个socket 函数 bind, listen, accept 是给server 用的,用来和client 建立连接。
#include <sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, int addrlen);                                            Returns: 0 if OK, -1 on error


bind 函数用来告诉kernel 去将myaddr 和sockfd 关联起来。

listen 函数

client 是发起连接的实体,server 是被动去接受连接,默认情况下,kernel 假设被socket 创建的描述符会一直存在直到连接断开,
by default, the kernel assumes that a descriptor created by the socket function corresponds to an active sock that will live on the client end of a connection.

server 调用listen 函数告诉kernel 这个descriptor 将会被server 用,而不是client.


#include <sys/socket.h>int listen(int sockfd, int backlog);                                                    Returns:0 if OK, -1 on error




1 0