zoukankan      html  css  js  c++  java
  • 【UNIX网络编程】概述

    一般认为Web服务器程序是一个长时间(后台)运行的程序(即,守护程序,daemon) -> 此类程序会被以进程的形式初始化,守护进程程序的名称通常以字母“d”结尾,如httpd。通常由客户发起请求可以简化协议和程序本身,某些复杂的网络应用需要异步回调(asynchronous callback)通信,由服务器向客户发起请求信息。

    在一个多任务的电脑操作系统中,守护进程英语:daemon,英语发音:/ˈdmən/英语发音:/ˈdmən/)是一种在后台执行的电脑程序。此类程序会被以进程的形式初始化。守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。

    通常,守护进程没有任何存在的父进程(即PPID=1),且在UNIX系统进程层级中直接位于init之下。守护进程程序通常通过如下方法使自己成为守护进程:对一个子进程运行 fork,然后使其父进程立即终止,使得这个子进程能在 init 下运行。这种方法通常被称为“脱壳”。

    系统通常在启动时一同起动守护进程。守护进程为对网络请求,硬件活动等进行响应,或其他通过某些任务对其他应用程序的请求进行回应提供支持。守护进程也能够对硬件进行配置(如在某些Linux系统上的devfsd),运行计划任务(例如cron),以及运行其他任务。

    DOS环境中,此类应用程序被称为驻留程序(TSR)。在Windows系统中,由称为Windows服务的应用程序来履行守护进程的职责。

    在原本的Mac OS系统中,此类应用程序被称为“extensions”。而作为Unix-like的 Mac OS X有守护进程。(在Mac OS X中也有“服务”,但他们与Windows中类似的程序在概念上完全不相同。)

    书上第5页的程序,时间获取客户程序:

    #include    "unp.h"
    
    int
    main(int argc, char **argv)
    {
        int                    sockfd, n;
        char                recvline[MAXLINE + 1];
        struct sockaddr_in    servaddr;
    
        if (argc != 2)
            err_quit("usage: a.out <IPaddress>");
    
        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            err_sys("socket error");
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port   = htons(13);    /* daytime server */
        if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
            err_quit("inet_pton error for %s", argv[1]);
    
        if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
            err_sys("connect error");
    
        while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
            recvline[n] = 0;    /* null terminate */
            if (fputs(recvline, stdout) == EOF)
                err_sys("fputs error");
        }
        if (n < 0)
            err_sys("read error");
    
        exit(0);
    }

    使用的前提是需要有unp.h,配置方法摸我

    主要步骤:

    1. 创建TCP套接字  sockfd = socket(AF_INET,SOCK_STREAM, 0);  
      // socket函数创建套接字,表示网际(AF_INET,IPv4的协议,如果为v6的话则是AF_INET6)字节流(SOCK_STREAM,tcp)。返回值是一个小整数(int)描述符,以后所有的函数调用都用该描述符来标识这个套接字,返回值-1表示错误
    2. 指定服务器的IP地址和端口号
      // 先清空结构体的内容(bzero),htons(int)表示转换端口为short类型,inet_pton是inet_addr的升级版,支持IPv6,用于将点分十进制转换成二进制,如127.0.0.1 -> 0xFF000001
    3. 建立与服务器的连接
      // 传入套接字描述符,服务器地址结构体,服务器地址结构体的长度
    4. 读入并输出服务器的应答
      // 使用read函数来读取服务器的应答,TCP是一个没有记录边界的字节流协议(一次发送的字节数不定)。当数据量很大的时候,不能确保一次read就可以返回服务器的整个应答,因此从TCP套接字读取数据时,要写在while(>0)中,当read返回0(表明对端关闭连接)或负值(错误)的时候终止。可以看出,n表示返回的记录的长度,故recvline[n] = 0;是指添加结束符(TCP本身并不提供记录结束标志),便于输出。
    5. 终止程序
      // exit终止程序运行,Unix在终止进程的同时,关闭该进程所有打开的描述符,TCP套接字就此被关闭

    定义包裹函数(wrapper function) -> 约定首字母大写,有助于代码的简洁,可以实现错误处理。

    以下用包裹函数来写一个时间获取的服务器程序:

    #include    "unp.h"
    #include    <time.h>
    
    int
    main(int argc, char **argv)
    {
        int                    listenfd, connfd;
        struct sockaddr_in    servaddr;
        char                buff[MAXLINE];
        time_t                ticks;
    
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(13);    /* daytime server */
    
        Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
        Listen(listenfd, LISTENQ);
    
        for ( ; ; ) {
            connfd = Accept(listenfd, (SA *) NULL, NULL);
    
            ticks = time(NULL);
            snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
    
            Close(connfd);
        }
    }

    主要步骤:

    1. 创建TCP套接字
    2. 将地址(用于给其他客户端连接的服务器端口)捆绑到套接字中
      // INADDR_ANY表示任意的(但只能到达其中一个网卡)网络接口
      INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
      一般情况下,如果你要建立网络服务器应用程序,则你要通知服务器操作系统:请在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上进行侦听,并且把侦听到的数据包发送给我。这个过程,你是通过bind()系统调用完成的。——也就是说,你的程序要绑定服务器的某地址,或者说:把服务器的某地址上的某端口占为已用。服务器操作系统可以给你这个指定的地址,也可以不给你。
      如果你的服务器有多个网卡(每个网卡上有不同的IP地址),而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:可能是你的服务器操作系统可能随时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 —— 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器程序则在0.0.0.0这个地址上进行侦听。
    3. 把套接字转换成监听套接字(这个套接字是专门用于让外来连接被内核接受)
      //socket、bind和listen这3个调用步骤是任何TCP服务器准备“监听描述符(listening descriptor,本例中为listenfd)"的正常步骤。LISTENQ是在unp.h中定义,它指定系统内核允许在这个监听描述符上排队的最大客户连接数。
    4. 接受客户连接(TCP三次握手,并返回连接服务器的该用户的新描述符),发送应答
      //服务器进程在accept调用中被投入睡眠,等待某个客户连接的到达并被内核接受(这一步是“建立TCP连接”),TCP连接使用所谓的“三次握手”来建立连接,握手完毕时accept返回,返回值称为“已连接描述符(connected descriptor)”的新描述符(connfd),该描述符用于与新近连接的那个客户通信。
    5. 终止连接(TCP四次挥手)
      //close(connfd)引发TCP连接终止序列:每隔方向上发送一个FIN,每隔FIN又由各自的对端确认。

    测试通过的代码(unpv13e中的代码配置方法见之前的文章,部分.h文件直接从lib文件夹中的.c文件改名而来): 码云

  • 相关阅读:
    .net remoting 易则易知,简则易从
    委托和匿名方法学习心得
    (4)插入排序之二 折半插入排序
    (2)排序概述
    (3)插入排序之一 直接插入排序
    (5)插入排序之三 2路插入排序
    (9)交换排序之二 快速排序
    (7)插入排序之五 希尔排序
    (6)插入排序之四 表插入排序
    (8)交换排序之一 起泡排序
  • 原文地址:https://www.cnblogs.com/tyrus/p/unp_ch1.html
Copyright © 2011-2022 走看看