【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文件改名而来): 码云

posted @ 2016-04-17 11:34  dr1  阅读(418)  评论(0编辑  收藏  举报