20191324读书笔记十一

第十三章 TCP/IP和网络编程

本章论述了 TCP/IP和网络编程,分为两个部分。第一部分论述了 TCP/IP协议及其应 用,具体包括TCP/IP栈、IP地址、主机名、DNS、IP数据包和路由器;介绍了 TCP/IP网 络中的UDP和TCP协议、端口号和数据流;阐述了服务器-客户机计算模型和套接字编程 接口;通过使用UDP和TCP套接字的示例演示了网络编程。第一个编程项目可实现一对通 过互联网执行文件操作的TCP服务器-客户机,可让用户定义其他通信协议来可靠地传输 文件内容。本章的第二部分介绍了 Web和CGI编程,解释了 HTTP编程模型、Web页面和Web浏 览器;展示了如何配置Linux HTTPD服务器来支持用户Web页面、PHP和CG1编程;阐释了客户机和服务器端动态Web页面;演示了如何使用PHP和CGI创建服务器端动态Web 页面。第二个编程项目可让读者在Linux HTTPD服务器上通过CGI编程实现服务器端动态Web页面。

  • TCP/IP是互联网的基础。TCP代表传输控制协议,IP代表互联网协议。

  • 进程与主机之间的传输层或其上方的数据传输只是逻辑传输。实际数据传输发生在互联网(IP)和链路层,这些层将数据包分成数据帧,以便在物理网络之间传输。下图所示为 TCP/IP 网络中的数据流路径。

  • 主机是支持TCP/IP协议的计算机或设备。每个主机由一个32位的IP地址来标识。为了方便起见,32位的IP地址号通常用点记法表示,其中各个字节用点号分开。

  • 我们可以通过DNS (域名系统)服务器找到另一个,它将IP地址转换为主机名,反之亦然。

  • IP地址分为两部分,即NetworkID字段和HostID字段。根据划分,1P地址分为A~E 类。例如,一个B类JP地址被划分为一个16位NetworklD,其中前2位是10,然后是一 个16位的HostID字段。发往IP地址的数据包首先被发送到具有相同networklD的路由器。 路由器将通过HostID将数据包转发到网络中的特定主机。每个主机都有一个本地主机名 localhost,默认IP地址为127.0.0.1。本地主机的链路层是一个回送虚拟设备,它将每个数 据包路由回同一个localhost0这个特性可以让我们在同一台计算机上运行TCP/IP应用程序, 而不需要实际连接到互联网。

  • ip协议用于在ip主机之间发送/接收数据包。主机只向接收主 机发送数据包,但它不能保证数据包会被发送到它们的目的地,也不能保证按顺序发送。这意味着ip并非可靠的协议。必要时,必须在ip层的上面实现可靠性。

  • ip数据包由ip头、发送方ip地址和接收方ip地址以及数据组成。每个ip数据包的大小最大为64KB。1P头包含有关数据包的更多信息,例如数据包的总长度、数据包使用TCP 还是UDP、生存时间(TTL)计数、错误检测的校验和等。

  • UDP在IP上运行,用于发送/接收数 据报。与IP类似,UDP不能保证可靠性,但是快速高效.它可用于可靠性不重要的情况。

  • ping是一个向目标主机发送带时间戳UDP包的应用程序。接收到一个pinging数据包 后,目标主机将带有时间戳的UDP包回送给发送者,让发送者可以计算和显示往返时间。 如果目标主机不存在或宕机,当TTL减小为0时,路由器将会丢弃pinging UDP数据包。 在这种情况下,用户会发现目标主机没有任何响应。用户可以尝试再次ping,或者断定目标 主机宕机。在这种情况下,最好使用UDP,因为不要求可靠性。

  • TCP(传输控制协议)是一种面向连接的协议,用于发送/接收数据流。TCP也可在IP上运行,但它保证了可靠的数据传输。

  • 在各主机上,多个应用程序(进程)可同时使用TCP/UDP.每个应用程序由三个组成部分唯一标识:应用程序=(主机IP,协议,端口号),其中,协议是TCP或UDP,端口号是分配给应用程序的唯一无符号短整数。要想使用 UDP或TCP,应用程序(进程)必须先选择或获取一个端口号。前1024个端口号已被预留。 其他端口号可供一般使用。应用程序可以选择一个可用端口号,也可以让操作系统内核分配端口号。常用端口号:

HTTP协议代理服务器常用端口号:80/8080/3128/8081/9098
SOCKS代理协议服务器常用端口号:1080
FTP(文件传输)协议代理服务器常用端口号:21
Telnet(远程登录)协议代理服务器常用端口号:23
HTTP服务器,默认端口号为80/tcp(木马Executor开放此端口)
HTTPS(securely transferring web pages)服务器,默认端口号为443/tcp 443/udp
Telnet(不安全的文本传送),默认端口号为23/tcp(木马Tiny Telnet Server所开放的端口)
FTP,默认的端口号为21/tcp(木马Doly Trojan、Fore、Invisible FTP、WebEx、WinCrash和Blade Runner所开放的端口)
TFTP(Trivial File Transfer Protocol),默认端口号为69/udp
SSH(安全登录)、SCP(文件传输)、端口号重定向,默认的端口号为22/tcp
SMTP Simple Mail Transfer Protocol(E-mail),默认端口号为25/tcp(木马Antigen、Email Password Sender、Haebu Coceda、Shtrilitz Stealth、WinPC、WinSpy都开放这个端口)
POP3 Post Office Protocol(E-mail),默认端口号为110/tcp
Webshpere应用程序,默认端口号为9080
webhpere管理工具,默认端口号9090
JBOSS,默认端口号为8080
TOMCAT,默认端口号为8080
WIN2003远程登录,默认端口号为3389
Symantec AV/Filter for MSE,默认端口号为 8081
Oracle 数据库,默认的端口号为1521
ORACLE EMCTL,默认的端口号为1158
Oracle XDB(XML 数据库),默认的端口号为8080
Oracle XDB FTP服务,默认的端口号为2100
MS SQLSERVER数据库server,默认的端口号为1433/tcp 1433/udp
MS SQL
SERVER数据库monitor,默认的端口号为1434/tcp 1434/udp

TCP/IP网络中的数据流

  • 应用程序层的数据被传递到传输层,传输层给数据添加一个TCP或UDP 报头来标识使用的传输协议。合并后的数据被传递到IP网络层,添加一个包含IP地址的IP 报头来标识发送和接收主机。然后,合并后的数据再被传递到网络锥路层,网络链路层将数 据分成多个帧,并添加发送和接收网络的地址,用于在物理网络之间传输:IP地址到网络地址的映射由地址解析协议(ARP)执行(ARP 1982 )。在接收端,数据编码过程是相反的。 每一层通过剥离数据头来解包接收到的数据,重新组装数据并将数据传递到上一层。发送主机上的应用程序原始数据最终会被传递到接收主机上的相应应用程序。

套接字编程

  • 在网络编程中,TCP/IP的用户界面是通过一系列C语言库函数和系统调用来实现的, 这些函数和系统调用统称为套接字API (( Rago 1993; Stevens等2004 )。为了使用套接宇 API,我们需要套接字地址结构,它用于标识服务器和客户机。netdb.h和sys/socket.h中有 套接字地址结构的定义:
struct sockaddr_in ( sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr;
);
struct in_addr {
uint32_t s_addr;

  • 在套接字地址结构中,TCP/IP 网络的 sin_family 始终设置为 AF_INET,sm_port包含按网络字节顺序排列的端口号,sin_addr是按网络字节顺序排列的主机IP地址。
  • 服务器必须创建一个套接字,并将其与包含服务器IP地址和端口号的套接字地址绑 定。它可以使用一个固定端口号,或者让操作系统内核选择一个端口号(如果sin_port为 0)o为了与服务器通信,客户机必须创建〜个套接字。对于UPD套接字,可以将套接字绑定到服务器地址。如果套接字没有绑定到任何特定的服务器,那么它必须在后续的sendto()/ recvfromO调用中提供一个包含服务器IP和端口号的套接字地址。下面给出了socket。系统调用,它创建一个套接字并返回一个文件描述符。
  • UDP套接字使用scndto()/recvfrom()来发送/接收数据报。
aendto(int aockfdr const void *bufr size.t len, lot flags,
const struct sockaddr •de8t_addrf socklen_t addrlen)|
asize_t recvfrora(int sock£d, void *buf, aiza_t len, int flags, struct sockaddr *Btc_addr, aocklen_t *addrlen};
  • 在创建套接字并将其绑定到服务器地址之后,TCP服务器使用listen()和acccpt()来接 收来自客户机的连接int Iistcn(int sockfd, int backlog);listen()将sockfd引用的套接字标记为将用于接收连入连接的套接字。backlog参数定义了等待连接的最大队列长度。
  • 建立连接后,两个TCP主机都可以使用send()/write()发送数据,并使用recv()/read。接收数据。它们唯一的区别是send和recv()中的nag参数不同,通常情况下可以将其设置为0。
ssize_t send(int Bockfd, const void *bufr size.t len« int flags);
write(sockfd/ void *buf, aize_t, l«n)
S0izo_t recv(int sockfd, void *buf# size_t len, int flags);
ssize_t read(sockfd, void *buf, size_t len);
  • 库函数gethostname(char *name, sizeof(name))在name数组中返回计算机的主机名字符串,但它可能不是用点记法表示的完整正式名称, 也不是其IP地址。库函数struct hostent *gethostbyname(void *addr, socklen_t len, int typo)可以用来获取计算机的全名及其IP地址。它会返回一个指向<netdb.h>中hostent结构体的指针:
struct hostent {
char	*h_name;	
char	**h_aliases;	
int	h_addrtype;	
int	h_length;	
char	**h_addr_list;	
}
#define h_addr h_addr_list[0]
  • 下面的代码段展示了如何使用gethostbyname()和getsockname()来获取服务器1P地址 和端口号(若是动态分配)。服务器必须发布其主机名或IP地址和端口号,以便客户机连接。
char myname[64];
struct sockaddr_in server_addr, sock_addr;
// gethostname(), gethostbyname() gethostname(myname,64);
struct hostent *hp = gethostbyname(myname); if (hp == 0)(
printf("unknown host %s\n", myname); exit(1);
// initialize the server_addr structure server_addr.sin_family = AF.INET;	// for TCP/IP
server_addr.Bin_addr・ s_addr = *(long *)hp->h_addr; server_addr.sin_port = 0; // let kernel assign port number
// create a TCP socket
int mysock = socket(AF_INET, SOCK_STREAM, 0);
bind socket with server_addr bind(mysock,(struct
to show port number assigned by kernel
(struct sockaddr *)&name_addr, &length)/
// show server host name and port number
printf("hostname=%s IP=%s port=%d\n", hp->h_name,
inet_ntoa(*(long *)hp->h_addr), ntohs(name_addr.sin_port));
struct sockaddr_in server_addr, sock_addr;
// 1. get server IP by name
struct hostent *hp = gethostbyname(argv[l]);
SERVER_IP = *(long *)hp->h_addr;
SERVER_PORT = atoi(argv[2]);
// 2. create TCP socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
// 3. fill server_addr with server IP and PORT#
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = SERVER_IP;
server_addr.sin_port = htons(SERVER_PORT);
// 4. connect to server
connect(sock,(struct sockaddr *)&server_addr, sizeof(server_addr));
  • 在命令行中,根据命令的不同,路径名可以是文件或目录。有效命令有:

mkdir:创建一个带路径名的目录。
rmdir:删除名为路径名的冃录。
rm:删除名为路径名的文件。
cd:将当前工作目录(CWD)更改为路径名。
pwd:显示当前工作目录的绝对路径名。
Is:按照与Linux的Is -1相同的格式列出当前工作目录或路径名。
get:从服务器下载路径名文件。
put:将路径名文件上传到服务器。

  • 对于传输文件内容的get/put命令,不能使用特殊ASCII字符作为文件的开始和结束标记。这是因为二进制文件可能包含ASCII代码。
  • 用标准HTML编写的Web页面都是静态的。当从服务器获取并用浏览器显示时,Web 页面的内容不会变化。要显示包含不同内容的Web页面,必须再次从服务器获取不同的 Web页面文件。
  • 动态Web页面有两种,分别称为 客户机端动态Web页面和服务器端动态Web页面。客户机端动态Web页面文件包含JavaScript写的代码,这些代码由JavaScript解释器在客户机上执行。它可以响应用户输入、时间事件等来对Web页面进行本地修改,而不需要与服务器进行任何交互。服务器端动态Web页面是真正的动态页面,因为它们是根据URL请求中的用户输入动态生成的。服务器 端动态Web页面的核心在于服务器在HTML文件中执行PHP代码,或CGI程序通过用户输入生成HTML文件的能力。
  • CGI代表通用网关接口,它是一种协议,允许Web服务器执行程序, 根据用户输入动态生成Web页面。使用CGI, Web服务器不必维护数百万个静态Web页面文件来满足客户机请求。相反,它通过动态生成Web页面来满足客户机请求。

实践

#include <stdio.h>
#include <arpa/inet.h>//inet_addr() sockaddr_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()

#define BUFFER_SIZE 1024

int main() {

    char listen_addr_str[] = "0.0.0.0";
    size_t listen_addr = inet_addr(listen_addr_str);
    int port = 8080;
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_size;
    char buffer[BUFFER_SIZE];//缓冲区大小
    int str_length;
    server_socket = socket(PF_INET, SOCK_STREAM, 0);//创建套接字
    bzero(&server_addr, sizeof(server_addr));//初始化
    server_addr.sin_family = INADDR_ANY;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = listen_addr;
    if (bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\n");

        exit(1);

    }

    if (listen(server_socket, 5) == -1) {

        printf("监听失败\n");

        exit(1);

    }

    printf("创建tcp服务器成功\n");

 

 

    fd_set reads,copy_reads;

    int fd_max,fd_num;

    struct timeval timeout;

 

    FD_ZERO(&reads);//初始化清空socket集合

    FD_SET(server_socket,&reads);

    fd_max=server_socket;

 

 

    while (1) {

        copy_reads = reads;

        timeout.tv_sec = 5;

        timeout.tv_usec = 5000;

 

        //无限循环调用select 监视可读事件

        if((fd_num = select(fd_max+1, &copy_reads, 0, 0, &timeout)) == -1) {

            perror("select error");

            break;

        }

        if (fd_num==0){//没有变动的socket

            continue;

        }

 

        for(int i=0;i<fd_max+1;i++){

            if(FD_ISSET(i,&copy_reads)){

                if (i==server_socket){//server_socket变动,代表有新客户端连接

                    addr_size = sizeof(client_addr);

                    client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &addr_size);

                    printf("%d 连接成功\n", client_socket);

                    char msg[] = "恭喜你连接成功";

                    write(client_socket, msg, sizeof(msg));

                    FD_SET(client_socket,&reads);

                    if(fd_max < client_socket){

                        fd_max=client_socket;

                    }

                }else{

                    memset(buffer, 0, sizeof(buffer));

                    str_length = read(i, buffer, BUFFER_SIZE);

                    if (str_length == 0)    //读取数据完毕关闭套接字

                    {

                        close(i);

                        printf("连接已经关闭: %d \n", i);

                        FD_CLR(i, &reads);//从reads中删除相关信息

                    } else {

                        printf("%d 客户端发送数据:%s \n", i, buffer);

                        write(i, buffer, str_length);//将数据发送回客户端

                    }

                }

            }

        }

 

    }

 

    return 0;

}

原文地址:https://www.cnblogs.com/pxmxxr/p/15593758.html