网络编程

原创 2015年07月08日 09:29:37

    

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 order

ungigned 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_t	sa_family;	/* address family, AF_xxx	*/
	char		sa_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_t		sin_family;	/* Address family		*/
  unsigned short int	sin_port;	/* Port number			*/
  struct in_addr	sin_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




版权声明:本文全部是胡说八道,如果你喜欢,可随意转载

相关文章推荐

网络协议(二)Socket编程之TCP

TCP(Transmission Control Protocol):传输控制协议。 1)面向连接的运输层协议,点对点连接; 2)提供可靠交付; 3)提供全双工通信; 4)面向字节流。 TCP连接的建...

Linux下UEFI模拟器编译和启动

操作系统 Linux系统:deepin 2014.3 desktop 需要文件 1.Edk2或者Udk2014 最新版Edk2获取网址:https://www.github.com/tianocore...
  • Kair_Wu
  • Kair_Wu
  • 2015年09月01日 20:41
  • 1102

EFI,UEFI和操作系统

什么是EFI? EFI英文全称为Extensible Firmware Interface,中文译为可扩展固件接口,其主要目的是为了提供一组在 OS 加载之前(启动前)在 所有平台上一致的、正确指定...
  • Kair_Wu
  • Kair_Wu
  • 2015年09月09日 21:47
  • 701

Socket网络编程

  • 2017年11月14日 22:13
  • 322KB
  • 下载

Netty——1、网络编程基本概念

1.Socket Socket又称“套接字”,应用程序通常通过“套接字”向网络发出请求或应答网络请求。 Socket和ServerSocket类库位于java.net包中。ServerSocket用于...

UNIX 网络编程

  • 2017年11月14日 22:57
  • 27.63MB
  • 下载

基于Ice的网络编程测试程序

  • 2017年10月31日 16:12
  • 7.56MB
  • 下载

Perl Socket模块和IO::SOCKET模块的网络编程

Perl的networking 功能非常强大,基本上用c/c++能做的事perl都能做,而且做得更轻松方便,甚 至可以只用10来行代码就完成了c/c++要几十上百甚至几百行才能完成得好的工作。 ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:网络编程
举报原因:
原因补充:

(最多只允许输入30个字)