zoukankan      html  css  js  c++  java
  • 《UNIX网络编程 卷1:套接字联网API》读书笔记(一):网络编程简介

    概述

      要编写通过计算机网络通信的程序,首先要确定这些程序相互通信所用的协议。大多数网络是按照划分成客户和服务器来组织的。本章及后续章节的焦点是TCP/IP协议族,也可称为网际协议族。下图为客户与服务器使用TCP在同一个以太网中通信:

    图1.1 客户与服务器使用TCP在同一个以太网进行通信

      同一网络中的客户机与服务器无需出于同局域网,上图1.1所示的是同一个局域网。下图1.2所示的是处于不同局域网的客户机与服务器,这两个局域网通过使用路由器连接到广域网。

     

    图1.2 出于不同局域网的客户主机与服务器主机通过广域网进行连接

      如今讨论Unix是经常使用POSIC一词,它是一种被多数厂商采纳的标准。

    一个简单的时间获取客户程序

    //    该头文件包含了大部分网络程序都需要的许多系统头文件
    #include    "unp.h"
    
    //    main函数定义,其形式参数就是命令行参数
    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>");
    
    //    socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字,该函数返回一个小整数描述符。如果socket函数调用失败,调用err_sys函数放弃程序运行
        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            err_sys("socket error");
    
    //    把IP地址和端口号填入一个网际套接字地址结构(一个名为servadrr的sockdrr_in结构变量),使用bzero把整个结构清零
        bzero(&servaddr, sizeof(servaddr));
    //    置地址族为AF_INET,端口号为13,IP地址为第一个命令行参数的值(argv[1])
    //    网际套接字结构中IP地址和端口号必须使用特定格式,为此调用库函数htons去转换二进制端口号,又调用inet_pton去把ASCII命令行参数转换为合适的格式
        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]);
    
    //    connect函数应用于TCP套接字时,将由它的第二个参数指向套接字地址结构指定的服务器建立一个TCP连接
    //    套接字地址结构的长度必须作为该函数的第三个参数指定
        if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
            err_sys("connect error");
    
    //    使用read函数读取服务器的应答,并用标准I/O函数fputs输出结果
    //    把read放在循环以便读取完数据,当read返回0或者负数时终止循环
        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);
    }

      

      我们从官网www.unpcook.com下载源代码unpv13e.tar.gz。解压后进入文件夹。我的是Ubuntu系统。根据文件夹中Readme的提示输入相应的命令。

    redhat@redhat-virtual-machine:~/桌面/unpv13e$ ./configure    
    
    redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./lib
    redhat@redhat-virtual-machine:~/桌面/unpv13e/lib$ make
    
    
    redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./libfree
    redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ make
    //    如果报错如下,则需要在当前目录下打开inet_ntop.c文件
    //    将第60行的size_t size修改为socklen_t size 然后保存
    //    重新输入make后不报错即可
    
    gcc -I../lib -g -O2 -D_REENTRANT -Wall   -c -o inet_ntop.o inet_ntop.c  
    inet_ntop.c: In function ‘inet_ntop’:  
    inet_ntop.c:60:9: error: argument ‘size’ doesn’t match prototype  
      size_t size;  
             ^  
    In file included from inet_ntop.c:27:0:  
    /usr/include/arpa/inet.h:64:20: error: prototype declaration  
     extern const char *inet_ntop (int __af, const void *__restrict __cp,  
                        ^  
    make: *** [inet_ntop.o] Error 1  
    
    
    redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ cd ../libgai
    redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ make
    //    以下的warning不用理会
    /usr/include/arpa/inet.h: In function ‘inet_ntop’:  
    inet_ntop.c:152:23: warning: ‘best.len’ may be used uninitialized in this function [-Wmaybe-uninitialized]  
       if (best.base == -1 || cur.len > best.len)  
                           ^  
    inet_ntop.c:123:28: note: ‘best.len’ was declared here  
      struct { int base, len; } best, cur;  
                                ^  
    gcc -I../lib -g -O2 -D_REENTRANT -Wall   -c -o inet_pton.o inet_pton.c  
    ar rv ../libunp.a in_cksum.o inet_ntop.o inet_pton.o  
    a - in_cksum.o  
    a - inet_ntop.o  
    a - inet_pton.o  
    ranlib ../libunp.a  
    
    //    用root权限将以上编译生成的libunp.a 文件复制到/usr/lib目录中
    
    redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ cd ..
    redhat@redhat-virtual-machine:~/桌面/unpv13e$ sudo cp libunp.a /usr/lib
    [sudo] redhat 的密码: 
    
    //    打开unp.h文件将其中的#include "../config.h" 改成 #include "config.h"
    redhat@redhat-virtual-machine:~/桌面/unpv13e$ vim lib/unp.h 
    
    //    进入intro目录编译客户端文件并用root权限运行
    redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd intro/
    redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$  make daytimetcpcli
    redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$  sudo ./daytimetcpcli 127.0.0.1
    //    错误提示无法连接
    connect error: Connection refused
    
    //    我们先打开服务器
    redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpsrv
    redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpsrv
    
    //    然后再打开另一个终端,在那里再运行客户端即可
    redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1
    [sudo] redhat 的密码: 
    Mon Dec 25 21:00:36 2017

       

      上面提到了客户端获取时间的程序代码,下面为服务器端的程序。

    #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);
    
        //    填写一个网际套接字地址结构并调用bind函数,把服务器的端口捆绑到所创建的套接字中
    
        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函数把该套接字转换成一个监听套接字,使来自客户端的连接可以在该套接字上由内核接受
        //    socket、bind、listen这三个函数调用步骤是任何tcp服务器准备监听描述符的正常步骤
        //    LISTENQ定义在头文件中,它指定系统内核允许在这个监听描述副符上排队的最大客户连接数
    
        Listen(listenfd, LISTENQ);
    
        //    服务器进程在accept调用中被投入睡眠,等待客户的连接
        //    TCP连接的三次握手完毕时accept返回,其返回值是一个被称为已连接描述符的新描述符    
        for ( ; ; ) {
            connfd = Accept(listenfd, (SA *) NULL, NULL);
    
        //    time函数获取当前时间,ctime函数把时间转换成直观可读的时间格式
            ticks = time(NULL);
        
        //    snprintf函数在这个字符串末尾添加一个回车符和一个换行符
        //    write函数把结果字符串写给客户
    
            snprintf(buff, sizeof(buff), "%.24s
    ", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
        //    终止连接
            Close(connfd);
        }
    }

      

  • 相关阅读:
    入门练习
    课堂作业补充
    例题作业(9.1-9.7)
    stream — 创建stream(一)
    Lambda表达式的四大内置核心函数式接口
    lambda表达式基础语法及其使用
    java 按照固定大小分割 list 集合的几种方式
    分布式锁相关解决方案
    zabbix3.0 使用详解
    eclipse Memory Analyzer (mat) 安装及使用
  • 原文地址:https://www.cnblogs.com/maluning/p/8000679.html
Copyright © 2011-2022 走看看