计算机网络—TCP协议详解:特性、应用(1)

 

                                        🎬慕斯主页修仙—别有洞天

                                       ♈️今日夜电波:マリンブルーの庭園—ずっと真夜中でいいのに。

                                                           0:34━━━━━━️💟──────── 3:34
                                                                🔄   ◀️   ⏸   ▶️    ☰  

                                 💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


 

目录

socket在TCP中主要接口的介绍

listen

accept

connect

setsockopt

telnet工具

Telnet命令行交互

什么是TCP协议?

通过程序来理解TCP

单进程版服务器

客户端

多进程版服务器

多线程版服务器

线程池版服务器

一些杂谈

守护进程化

前置知识

什么是守护进程

setsid

写出一个符合上述服务端的Daemon

系统中提供的Daemon


 

socket在TCP中主要接口的介绍

listen

listen函数是在网络编程中常用的一个函数,特别是在使用套接字(sockets)进行服务器端的编程时。这个函数的主要作用是使套接字进入监听状态,等待客户端的连接请求。

函数原型通常如下(在C语言中):

#include <sys/types.h>  
#include <sys/socket.h>
int listen(int sockfd, int backlog);

参数说明:

  • sockfd:这是你要监听的套接字的文件描述符。这个套接字通常是之前通过socket函数创建的,并且已经绑定到了一个特定的地址和端口上(通过bind函数)。
  • backlog:这是等待连接队列的最大长度。换句话说,这是可以等待处理的未完成连接请求的最大数量。当队列满时,新的连接请求可能会被拒绝。这个值通常设置为5或更大,但具体的值取决于你的应用需求和系统限制。

返回值:

  • 如果函数成功,则返回0。
  • 如果函数失败,则返回-1,并设置全局变量errno以指示错误。

在使用listen函数之前,你通常需要按照以下步骤设置套接字:

  1. 调用socket函数创建一个新的套接字。
  2. 调用bind函数将套接字绑定到一个特定的地址和端口。
  3. 调用listen函数使套接字进入监听状态。

        然后,你可以使用accept函数来接受客户端的连接请求。当accept函数被调用时,它会阻塞(除非设置了非阻塞模式),直到有一个客户端连接请求到达。一旦有请求到达,accept函数就会返回一个新的套接字描述符,这个描述符可以用于与客户端进行通信。

        需要注意的是listen函数只是使套接字进入监听状态,它并不会接受任何连接请求。实际的连接请求是由accept函数处理的。

accept

   accept函数是Linux网络编程中用于服务器端的一个关键函数,它的主要作用是接受客户端的连接请求,并返回一个新的套接字用于和客户端通信。(这个套接字可以理解成什么呢?我们可以理解为文件系统中的fd,也就是文件标识符,我们后续就是根据这个来像文件一样使用write、read进行通信的!)

函数的原型通常如下:

#include <sys/types.h>  
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:

  • sockfd:这是用于监听客户端连接请求的套接字文件描述符。这个套接字应该是之前通过socket函数创建的,并且通过bindlisten函数进行了绑定和监听设置。
  • addr:这是一个指向struct sockaddr的指针,用于保存客户端的套接字地址结构。这个结构包含了客户端的IP地址和端口号等信息。如果调用者对此信息不感兴趣,可以将此参数设为NULL。
  • addrlen:这是一个指向socklen_t变量的指针,用于传入addr结构的大小,并在函数返回时填充实际接收到的地址结构的大小。

返回值:

  • 如果成功,accept函数会返回一个新的套接字文件描述符,这个描述符可以用来与客户端进行通信。
  • 如果失败,则返回-1,并设置全局变量errno以指示错误。

        调用accept函数是一个阻塞的过程。如果没有客户端连接请求到来,accept函数会一直阻塞,直到有客户端发起连接请求并成功完成三次握手。一旦有客户端连接请求成功,accept函数会返回一个新的套接字描述符,这个套接字描述符专用于与刚刚连接的客户端进行通信。同时,客户端的地址信息会被填充到addr所指向的结构中。

        在实际编程中,accept函数通常与bindlisten等函数结合使用,以完成一个完整的服务器端程序。在服务器开始监听客户端连接之前,需要先调用bind函数将套接字绑定到一个特定的地址和端口,然后调用listen函数使套接字进入监听状态。当有客户端连接请求到来时,再调用accept函数接受连接,并返回新的套接字用于通信。

        需要注意的是,如果套接字被设置为非阻塞模式,并且没有未完成的连接请求队列,accept函数会立即返回并设置errnoEAGAIN表示资源暂时不可用。为了处理这种情况,可以使用selectpoll函数来检查套接字上是否有传入的连接请求。

connect

    connect函数是编程中用于建立连接或关联的常用函数之一。在网络编程中,connect函数主要用于客户端与服务器之间的连接建立。它的基本作用是在两个组件(在这种情况下,通常是客户端和服务器)之间建立通信通道,以便在不同组件之间传递数据或实现功能。

函数的原型通常如下:

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd:这是由socket函数创建的套接字文件描述符,代表客户端的套接字。
  • addr:这是一个指向struct sockaddr的指针,包含了要连接的服务器的地址信息,包括IP地址和端口号。
  • addrlen:这是一个整数,表示addr参数指向的地址结构的大小,通常通过sizeof(struct sockaddr_in)或类似表达式获得。

返回值:

  • 如果连接成功,connect函数返回0。
  • 如果连接失败,connect函数返回-1,并设置全局变量errno以指示错误原因。

        在客户端编程中,connect函数通常用于主动发起与服务器的连接请求。一旦连接建立成功,客户端和服务器就可以通过各自的套接字进行数据传输和通信。

        需要注意的是connect函数是阻塞的,意味着它会等待直到连接建立成功或发生错误。如果服务器不可达或拒绝连接,connect函数将返回错误。

        与connect函数相对应的是服务器端的accept函数,它用于接受客户端的连接请求并返回一个新的套接字描述符用于通信。

setsockopt

    setsockopt函数是一个系统调用,主要用于在套接字层面上设置一些参数,以调整套接字的行为。它允许程序员配置套接字的各种属性。

函数的基本原型如下:

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

参数说明:

  • sockfd:标识一个套接字的描述字,即指向一个打开的套接口。
  • level:选项定义的层次,支持以下值:
    • SOL_SOCKET:基本套接口
    • IPPROTO_IP:IPv4套接口
    • IPPROTO_IPV6:IPv6套接口
    • IPPROTO_TCP:TCP套接口
  • optname:需设置的选项名称。
  • optval:指向存放选项待设置的新值的缓冲区。这个参数是一个指向变量的指针,变量的类型可以是整形、套接口结构或其他结构类型,如linger{}timeval{}
  • optlenoptval缓冲区长度,表示optval的大小。

返回值:

  • 如果设置选项成功,setsockopt返回0。
  • 如果设置选项失败,setsockopt返回-1,并且会设置errno来表示具体的错误原因。

        在编程中,使用setsockopt函数可以针对特定的套接字进行各种配置,例如设置套接字的发送和接收缓冲区大小、启用或禁用广播等。通过合理地设置这些选项,可以优化网络性能,提高应用程序的可靠性和效率。

        需要注意的是setsockopt函数的具体用法可能因操作系统和编程环境的不同而有所差异。因此,在使用该函数时,建议查阅相关的文档和资料,以确保正确理解和使用该函数。

telnet工具

        在Linux系统中,telnet是一个用于远程登录到另一台计算机或设备的命令行工具。它使用TCP协议,并允许用户通过命令行界面与远程系统进行交互。以下是关于Linux下telnet使用的详细解释:

安装telnet

        首先,你需要确保你的Linux系统已经安装了telnet客户端。你可以使用包管理器来安装它。例如,在基于Debian的系统(如Ubuntu)上,你可以使用以下命令安装:

sudo apt-get install telnet

在基于Red Hat的系统(如CentOS或Fedora)上,你可以使用:

sudo yum install telnet

或者,如果你使用的是dnf

sudo dnf install telnet

使用telnet连接到远程主机:

一旦安装完成,你可以使用以下命令格式连接到远程主机:

telnet 远程主机IP地址 端口号

通常,telnet使用TCP端口23,因此如果你没有指定端口号,它将尝试连接到该端口。例如:

telnet 192.168.1.100

这将尝试通过TCP端口23连接到IP地址为192.168.1.100的远程主机。

        在连接成功后,通常会提示你使用^[来进入,进入后可以向对应的TCP程序发送字节流。想要退出的话可以使用quit或者^]。

Telnet命令行交互

        一旦连接建立成功,你就可以在telnet命令行界面中输入命令并与远程系统进行交互。这通常涉及到登录到远程系统(如果需要的话),然后执行各种命令。通常使用

Telnet选项和命令:

   telnet命令本身也支持一些选项和命令,用于控制telnet会话的行为。你可以通过查看telnet的手册页来获取这些选项的完整列表:

man telnet

一些常用的telnet选项包括:

  • -l--login:自动登录到远程系统。
  • -e--escape:设置转义字符,默认为“^]”。
  • -K--keep-alive:使用TCP keepalive。

注意事项:

  1. 安全性:telnet在传输数据时并不加密,因此它被认为是不安全的,特别是在公共网络上。SSH(安全外壳协议)是一个更安全的替代方案,它提供了加密的数据传输。
  2. 防火墙和网络配置:确保远程主机的防火墙和网络配置允许telnet连接。在某些情况下,你可能需要在远程主机上配置telnet服务以接受连接。
  3. 终端类型:telnet会话可能需要指定终端类型(例如vt100)。这可以通过telnet的-t选项或远程系统的配置来完成。

什么是TCP协议?

        TCP协议,全称传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议。该协议由IETF的RFC 793定义,主要用于在主机间建立一个虚拟连接,以实现高可靠性的数据包交换。

TCP协议的工作原理主要可以分为以下三个步骤:

  1. 建立连接:通信双方首先要建立TCP连接。客户端发送一个连接请求(SYN包)到服务器,并等待服务器的确认(ACK包)。服务器收到客户端的连接请求后,发送确认和自己的连接请求(SYN/ACK包)给客户端。客户端再发送确认(ACK包),至此连接建立完成。
  2. 数据传输:一旦连接建立,通信双方可以开始传输数据。发送方将数据划分成小块(称为报文段),并添加头部和校验等信息,然后通过TCP协议将这些报文段发送给接收方。接收方收到报文段后,校验数据的完整性,并把它们重新组装成完整的数据流。
  3. 可靠传输:TCP通过各种机制来保证数据的可靠传输。它使用序列号对每个报文段进行标记,并确保接收方按照正确的顺序进行数据重组。如果发送方发现某个报文段丢失或未收到确认,它会重新发送该报文段。另外,TCP也采用滑动窗口的机制,允许发送方连续发送多个报文段,而不需要等待确认。

        TCP协议在网络通信中扮演着重要角色,尤其在需要高可靠性的数据传输场景中。然而,也需要注意到TCP协议在某些情况下可能存在性能问题,例如在网络拥塞或高延迟环境中,TCP的流量控制机制可能导致传输效率降低。因此,在选择使用TCP协议时,需要根据具体的应用场景和需求进行权衡。

        重点总结如下:传输层协议、有连接、可靠传、输面向字节流

 

通过程序来理解TCP

单进程版服务器

        这只是一个简单的单进程TCP,按照TCP的规则,首先获取对应IPV4协议和TCP协议的socket,然后创建对应的sockaddr_in用于后续的bind操作,OS在bind了对应的端口和地址后,与UDP不同的是,TCP需要通过listen使套接字进入监听状态,再通过accept来接受连接请求,需要注意accept会返回链接的sockfd,后续我们就可以根据这个sockfd进行相应的write和read的操作了(就像文件一样)。

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
using namespace std;

const int defaultfd = -1;
const int defaultport = 8888;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer
{
public:
    TcpServer( const uint16_t &port = defaultport,const string &ip = defaultip)
        : _listensock(defaultfd), _ip(ip), _port(port)
    {
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg.LogMessage(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg.LogMessage(Info, "create socket success, _listensock: %d \n", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));

        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg.LogMessage(Info, "bind socket success, _listensock: %d \n", _listensock);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if(listen(_listensock,backlog)<0)
        {
            lg.LogMessage(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg.LogMessage(Info, "listen socket success, _listensock: %d \n", _listensock);

    }

    void Start()
    {
        lg.LogMessage(Info, "tcpServer is running.... \n");
        for(;;)
        {
             struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd=accept(_listensock,(struct sockaddr *)&client,&len);
             if (sockfd < 0)
            {
                lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            // version 1 -- 单进程版
            Service(sockfd, clientip, clientport);
            close(sockfd);
        }
    }

    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        // 测试代码
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}


private:
    int _listensock;
    uint16_t _port;
    string _ip;
};

客户端

        客户端同UDP一样不需要我们显示的bind,OS会自动帮我们bind,随机端口。那么是在什么时候进行绑定的呢?在客户端发起connect的时候,进行自动随机bind。客户端中的connect就是为了让服务端的accept响应建立联系的函数。后续我们就可以根据经过socket函数获得的sockfd进行相应的write和read的操作了(就像文件一样,这个socketfd注意是自己(客户端)使用socket函数获得的)。需要注意:这里我们做了重连的处理,也就是do while循环以及其中的内容。

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    while (true)
    {
        int cnt = 5;
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            // tcp客户端要不要bind?1 要不要显示的bind?0 系统进行bind,随机端口
            // 客户端发起connect的时候,进行自动随机bind
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2);
            }
            else
            {
                break;
            }
        } while (cnt && isreconnect);

        if (cnt == 0)
        {
            std::cerr << "user offline..." << std::endl;
            break;
        }

        // while (true)
        // {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

            int n = write(sockfd, message.c_str(), message.size());
            if (n < 0)
            {
                std::cerr << "write error..." << std::endl;
                // break;
            }

            char inbuffer[4096];
            n = read(sockfd, inbuffer, sizeof(inbuffer));
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << inbuffer << std::endl;
            }
            else{
                // break;
            }
        // }
        close(sockfd);
    }

    return 0;
}

多进程版服务器

        只是改动了一些代码,加入了子进程的创建以及进程等待的少量代码。需要特别注意代码中的注释部分,该部分保证了是多进程而不是还为单进程。当然也可以不做上述的步骤,更改SIGCHLD信号:signal(SIGCHLD, SIG_IGN);让父进程不等待即可。

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
using namespace std;

const int defaultfd = -1;
const int defaultport = 8888;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer
{
public:
    TcpServer( const uint16_t &port = defaultport,const string &ip = defaultip)
        : _listensock(defaultfd), _ip(ip), _port(port)
    {
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg.LogMessage(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg.LogMessage(Info, "create socket success, _listensock: %d \n", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));

        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg.LogMessage(Info, "bind socket success, _listensock: %d \n", _listensock);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if(listen(_listensock,backlog)<0)
        {
            lg.LogMessage(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg.LogMessage(Info, "listen socket success, _listensock: %d \n", _listensock);

    }

    void Start()
    {
        lg.LogMessage(Info, "tcpServer is running.... \n");
        for(;;)
        {
             struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd=accept(_listensock,(struct sockaddr *)&client,&len);
             if (sockfd < 0)
            {
                lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            // version 2 -- 多进程版
            pid_t id=fork();
            if(id==0)
            {
                //child
                close(_listensock);
                if(fork() > 0) exit(0); //这一步做出后,子进程会马上退出被父进程wait到,父进程可以继续进行循环
                Service(sockfd, clientip, clientport); //孙子进程, system 领养
                close(sockfd);
                exit(0);
            }
            close(sockfd);
            // father
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;

        }
    }

    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        // 测试代码
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}


private:
    int _listensock;
    uint16_t _port;
    string _ip;
};

多线程版服务器

        要实现多线程版,我们要特别用到pthread_detach这个函数。他会将指定的线程标记为可分离的。当一个线程被标记为可分离时,该线程的资源(如堆栈和线程描述符)会在该线程退出时自动被系统回收,而不需要等待其他线程使用pthread_join函数来释放它们。接着对于线程传入的函数Routine我们要特别注意将this指针也传入,为什么呢?因为线程需要用static函数共享资源的缘故。因此需要额外构造一个ThreadData类来储存isockfd、clientip、clientport、this指针。

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
using namespace std;

const int defaultfd = -1;
const int defaultport = 8888;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer( const uint16_t &port = defaultport,const string &ip = defaultip)
        : _listensock(defaultfd), _ip(ip), _port(port)
    {
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg.LogMessage(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg.LogMessage(Info, "create socket success, _listensock: %d \n", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));

        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg.LogMessage(Info, "bind socket success, _listensock: %d \n", _listensock);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if(listen(_listensock,backlog)<0)
        {
            lg.LogMessage(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg.LogMessage(Info, "listen socket success, _listensock: %d \n", _listensock);

    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);//???
        delete td;
        return nullptr;
    }

    void Start()
    {
        lg.LogMessage(Info, "tcpServer is running.... \n");
        for(;;)
        {
             struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd=accept(_listensock,(struct sockaddr *)&client,&len);
             if (sockfd < 0)
            {
                lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            // version 3 -- 多线程版本
            ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);

        }
    }

    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        // 测试代码
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}


private:
    int _listensock;
    uint16_t _port;
    string _ip;
};

线程池版服务器

        需要额外传入Task的任务,这个自己决定,对于线程池则是之前实现的单例模式的线程池,该线程池默认创建10个线程,通过Push这个接口传入任务,后续线程池会自动处理任务。

ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

TcpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
using namespace std;

const int defaultfd = -1;
const int defaultport = 8888;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer( const uint16_t &port = defaultport,const string &ip = defaultip)
        : _listensock(defaultfd), _ip(ip), _port(port)
    {
    }

    void InitServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg.LogMessage(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg.LogMessage(Info, "create socket success, _listensock: %d \n", _listensock);

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));

        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg.LogMessage(Info, "bind socket success, _listensock: %d \n", _listensock);

        // Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直在等待连接到来的状态
        if(listen(_listensock,backlog)<0)
        {
            lg.LogMessage(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg.LogMessage(Info, "listen socket success, _listensock: %d \n", _listensock);

    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);//???
        delete td;
        return nullptr;
    }

    void Start()
    {
        lg.LogMessage(Info, "tcpServer is running.... \n");
        ThreadPool<Task>::GetInstance()->Start();
        for(;;)
        {
             struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd=accept(_listensock,(struct sockaddr *)&client,&len);
             if (sockfd < 0)
            {
                lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            // version 4 --- 线程池版本
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);

        }
    }

    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        // 测试代码
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer() {}


private:
    int _listensock;
    uint16_t _port;
    string _ip;
};

一些杂谈

127.0.0.1本地回环

TCP协议是面相字节流的:可以用read、write。读写网络就像读、写文件一般。

为什么TCP、UDP中对于端口以及IP等等需要设置为网络字节序,而写入读出的信息却不用?端口号和IP地址需要使用网络字节序是为了确保在不同计算机系统之间能够被正确识别,而实际传输的数据则依赖于应用层的处理,不强制要求转换为网络字节序。这是由网络协议的设计和网络通信的需求所决定的。

signal(SIGPIPE, SIG_IGN);常常用于socket在信息被write或者read是socketfd被提前关闭(这个就同进程中的管道一样,可能会导致进程被杀死)。SIG_IGN就是为了防止进程被直接杀死。

守护进程化

前置知识

我们通常会通过ps -axj命令查看所有的进程,如下先认识一下几个概念:

session的概念:session(会话)是用户与操作系统之间建立的一个交互环境。当用户通过命令行工具或图形界面登录到Linux系统后,系统会为该用户创建一个独立的会话。这个会话包含了用户当前的工作环境和正在运行的程序,为用户提供了一个独立且隔离的工作空间。可以理解为我们通过xshell打开的一个新会话,我们每打开一个新的会话就会多一个型的session,那么session id则表示在同一个会话里面的进程!

PGID的概念:终端进程组ID通常指的是与某个特定终端(或称为控制终端、tty)相关联的前台进程组的ID。在终端中,前台进程组是当前用户正在与之交互的进程组,而后台进程组则是在后台运行的进程组。当用户在一个终端中启动多个进程时,这些进程可能属于不同的进程组,但只有一个进程组会成为前台进程组。

TTY的概念:TTY是Teletype的缩写,它指的是由虚拟控制台、串口以及伪终端设备组成的终端设备。这些设备提供了用户与计算机进行交互的接口。通过TTY设备,用户可以输入命令并查看计算机的输出。

守护进程实际的特点:

  • 守护进程基本上都是以超级用户启动( UID 为 0 )
  • 没有控制终端( TTY 为 ?)
  • 终端进程组 ID 为 -1 ( TPGID 表示终端进程组 ID)

组长的概念:进程组组长的进程ID(PID)与进程组ID(PGID)是相同的。这个特性使得系统可以方便地识别和管理进程组组长。进程组组长负责管理组内的其他进程,包括创建新的进程组、将其他进程加入到自己的进程组中,以及对组内的进程进行协调和控制。而进程组内其他成员的PGID则为组长的PID。作为组长如果退出了,那么他的组员是会出错误的!

守护进程实际上就是脱离了session,变成自成进程组、自成会话的进程!他的本质实际上就是孤儿进程的原理,利用fork()创建一个子进程(为什么呢?如果要调用setsid则不能作为组长!),父进程退出,子进程调用setsid()创建一个新的会话并成为会话领导,子进程则会被OS所领养。

介绍一个文件夹:/dev/null

/dev/null 是 Unix 和类 Unix 系统(如 Linux)中的一个特殊设备文件,它被称为“空设备”或“空文件”。当你向 /dev/null 写入数据时,数据会被丢弃,就像被送入了一个黑洞。而从 /dev/null 读取数据则什么也得不到(立即返回 EOF,即文件结束标记)。

/dev/null 常被用于以下几个目的:

  1. 丢弃输出:当你运行一个命令或程序,并且不想看到其输出时,你可以将其重定向到 /dev/null。例如,command > /dev/null 会将标准输出重定向到 /dev/null,从而丢弃所有输出。如果你还想丢弃错误输出,可以使用 command > /dev/null 2>&1
  2. 提供一个空的文件输入:当某个命令或程序需要一个文件输入,但你希望提供一个空的内容时,可以使用 /dev/null。例如,command < /dev/null 会将标准输入设置为来自 /dev/null,这样命令就不会从任何地方读取到数据。

/dev/null 在 shell 脚本编程和系统管理中非常有用,因为它提供了一个简单的方法来丢弃不需要的输出或提供一个空的输入源。

简单来说,/dev/null 是一个数据“黑洞”,你可以向它写入任何你想要丢弃的数据,或者从它读取(但什么也得不到)。

什么是守护进程

        守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lpd等(这里的结尾字母d就是Daemon的意思)。

        在创建守护进程时,一个典型的步骤是父进程先创建子进程,然后父进程退出,子进程继续执行。这样子进程就成为了一个守护进程。守护进程会在后台运行,不受前台用户的影响,即使终端被关闭,守护进程也会继续运行。

        守护进程的特点主要包括:

  1. 在后台运行。
  2. 独立于控制终端(tty)。也就是说,在终端控制的用户无法直接控制它。
  3. 周期性地执行任务或者等待处理某些事件。
  4. 一般不会随着用户的退出而结束,而是会一直运行,直到系统关闭或者接收到特定的终止信号。

setsid

    setsid 是在 Unix 和 Linux 环境中用于处理进程和会话的一个重要工具。它既可以作为系统调用在程序中使用,也可以作为命令行工具来执行。下面是对 setsid 的详细解释:

作为系统调用

        当 setsid 作为系统调用时,它允许一个进程创建一个新的会话,并成为该会话的领导。这在创建守护进程时尤其有用,因为守护进程需要在后台运行,且不受前台用户或终端会话的控制。

  • 调用条件:只有当前进程不是会话领导时,才能成功调用 setsid。如果已经是会话领导,则调用会失败,并返回错误。
  • 效果:成功调用 setsid 后,当前进程会成为新会话的会话领导和进程组领导,且新会话没有控制终端。此外,新会话的 ID 和进程组 ID 将与调用进程的 PID 相同。
  • 用途:常用于创建守护进程。守护进程是在系统启动时开始运行,并在系统关闭时终止的进程。它们通常在后台运行,执行一些系统级任务,如监听网络请求、管理硬件资源等。

作为命令行工具

setsid 也可以作为命令行工具来使用,用于在后台启动一个进程,并将其与当前终端会话分离。

  • 语法setsid [选项] 命令 [参数]
    • 选项:用于控制 setsid 的行为,如 -w 表示等待命令完成后再返回,-c 用于指定要在后台运行的命令等。
    • 命令:要在后台运行的程序。
    • 参数:传递给命令的参数。
  • 用途:常用于在终端关闭或会话结束后,仍需要继续运行的进程。例如,你可能使用 setsid 来启动一个长时间运行的任务,如备份操作或网络服务器,以确保即使你关闭了终端,这些任务也会继续执行。

示例

        在编程中,你可能会看到类似以下的代码片段,用于创建一个守护进程:

pid_t pid = fork(); // 创建一个子进程
if (pid < 0) {
    // 处理 fork 失败的情况
} else if (pid > 0) {
    // 父进程退出,子进程继续执行
    exit(0);
} else {
    // 子进程中调用 setsid,创建新的会话并成为会话领导
    if (setsid() < 0) {
        // 处理 setsid 调用失败的情况
    }
    // 继续执行守护进程的初始化操作和任务...
}

        在命令行中,你可能会使用类似以下的命令来在后台启动一个程序:

setsid myprogram arg1 arg2 &

        这条命令会在后台启动 myprogram,并将其与当前终端会话分离。即使你关闭了终端,myprogram 也会继续运行。

写出一个符合上述服务端的Daemon

#pragma once 
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string nullfile="/dev/null";

void daemon(const std::string &cwd="")
{
    // 1. 忽略其他异常信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 将自己变成独立的会话
    if(fork()>0) exit(0);

    setsid();

    // 3. 更改当前调用进程的工作目录
    if (!cwd.empty())
        chdir(cwd.c_str());

     // 4. 标准输入,标准输出,标准错误重定向至/dev/null
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }

}

系统中提供的Daemon

        在 C 语言中,你可以使用 daemon 函数来将当前进程转换为守护进程。daemon 函数定义在 <unistd.h> 头文件中,其原型如下:

#include <unistd.h>

int daemon(int nochdir, int noclose);

daemon 函数接受两个整数参数:

  • nochdir:如果此参数非零,则 daemon 不会将当前工作目录更改为根目录(/)。
  • noclose:如果此参数非零,则 daemon 不会关闭标准输入、标准输出和标准错误输出。

daemon 函数的返回值是一个整数:

  • 如果成功,返回 0。
  • 如果失败,返回 -1,并设置 errno 以指示错误。

下面是一个简单的示例,展示了如何使用 daemon 函数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

int main() {
    pid_t pid;
    
    // 创建守护进程
    if (daemon(0, 0) == -1) {
        perror("daemon failed");
        exit(EXIT_FAILURE);
    }
    
    // 由于守护进程已经关闭了标准输入、输出和错误输出,
    // 如果需要记录日志,可以重新打开或重定向到某个文件。
    int fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }
    
    // 将标准输出和标准错误输出重定向到日志文件
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    
    // 守护进程的主循环
    while (1) {
        sleep(10); // 休眠10秒
        printf("Daemon is still alive...\n"); // 写入日志
        fflush(stdout); // 刷新输出缓冲区
    }
    
    close(fd); // 关闭文件描述符(实际上这个代码不会被执行到,因为有一个无限循环)
    return 0;
}

        在上面的示例中,我们首先调用 daemon(0, 0) 来创建守护进程。然后,我们打开一个日志文件,并将标准输出和标准错误输出重定向到该文件。最后,守护进程进入一个无限循环,每隔10秒向日志文件写入一条消息。请注意,由于守护进程在后台运行并且已经脱离了控制终端,因此它们不会响应终端信号(如 SIGINT)。如果你需要优雅地关闭守护进程,你应该实现一种机制来接收和处理适当的信号(如 SIGTERM)。

 


                       感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

  • 88
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 93
    评论
编辑推荐 “我在全国各地教授Cisc0联网课程.每本新的有名的数据通信的书我都听说了,因为我的学生们拿它们作为课程的参考书。Stevens的《TCP/IP详解卷1 协议》的到来如同一枚炸弹的爆炸……学生们被这本书所倾倒。我刚读完《TCP/IP详解卷2:实现》。如果说卷1像一枚炸弹,则卷2就是一枚重磅炸弹!” “在读完Stevens的《TCP/IP详解卷1协议》后,我认为很难再有另一本如此有用的书了,但卷2就是这样一本书。有些人可能会问,如果你不是一个专职的网络程序员,如何让这本书适合于你。因为在我最初使用UNIX的日子里,回答‘这到底是如何工作的?’的标准答案是‘看源代 码’。用这本书,你不仅能看源代码,还有一个最清晰的解释:它们是如何互相配合起来的。” 《TCP/IP详解》系列覆盖了TCP/IP的许多方面.提供了一个高效直观的方法来学习联网协议族。 《TCP/IP详解卷2:实现》详尽地说明了TCP/IP协议是如何实现的。目前还没有更多的 书——此卷是唯一一本书,本书全面讲解了来源于44BSD—Lite版本的事实上的标准实现.这个 实现是运行在世界上成千上万个系统上的TCP/IP实现的基础。 500个示例总共15000行代码都是真正正在使用的代码,《TCP/IP详解卷2:实现》使用一种举例说明的方法来帮助你精通TCP/IP的实现。你会掌握以下问题:插口API和协议族之间的关系,一个主机实现和一个路由器实现之间的区别等。另外本书覆盖了4.4BSD—Lite版本的最新特性,包括多播,长肥管道支持、窗口缩放、时间戳选项、防止序列号环绕及很多其他主题。 本书覆盖范围广,它基于一个当前正使用的标准,并且有详尽的示例,因此它是任何使用TCP/IP的人员一个不可缺少的资源。 内容简介 本书完整而详细地介绍了TCP/IP协议是如何实现的。书中给出了约500个图例,15000行实际操作的C代码,采用举例教学的方法帮助你掌握TCP/IP实现。本书不仅说明了插口API和协议族的关系以及主机实现与路由器实现的差别。还介绍了4.4BSD-Lite版的新的特点。本书适用于希望理解TCP/IP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 作者简介 Gary R.Wright 研究TCP/IP多年。他是Connix公司的董事长,这个公司的基地在康涅狄格州,它提供Internet接入和咨询服务。 W.Richard Stevens(1951-1999)是一位非常受人尊敬的专家,除了《TCP/IP详解》三卷本外,他还有其他两部最为畅销的作品;《UNIX环境高级编程》和《UNIX网络编程》(两卷本)。 目录 译者序 前言 第1章 概述  1.1 引言 1.2 源代码表示  1.2.1 将拥塞窗口设置为  1.2.2 印刷约定 1.3 历史 1.4 应用编程接口 1.5 程序示例 1.6 系统调用和库函数 1.7 网络实现概述 1.8 描述符 1.9 mbuf与输出处理 1.9.1 包含插口地址结构的mbuf 1.9.2 包含数据的mbuf 1.9.3 添力口IP和UDP首部 1.9.4 IP输出 1.9.5 以太网输出 1.9.6 UDP输出小结 1.10 输入处理 1.10.1 以太网输入 1.10.2 IP输入 1.10.3 UDP输入 1.10.4 进程输入 1.11 网络实现概述(续) 1.12 中断级别与并发 1.13 源代码组织 1.14 测试网络 1.15 小结 第2章 mbuf:存储器缓存 2.1 引言 2.2 代码介绍 2.2.1 全局变量  2.2.2 统计  2.2.3 内核统计  2.3 mbuf的定义  2.4 mbuf结构  2.5 简单的mbuf宏和函数  2.5.1 m_get函数  2.5.2 MGET宏  2.5.3 m_retry函数  2.5.4 mbuf锁  2.6 m_devget和m_pullup函数  2.6.1 m_devget函数  2.6.2 mtod和dtom宏  2.6.3 m_pullup函数和连续的协议首部  2.6.4 m_pullup和IP的分片与重组  2.6.5 TCP重组避免调用m_pul lup  2.6.6 m_pullup使用总结  2.7 mbuf宏和函数的小结  2.8 Net/3联网数据结构小结  2.9 m_copy和簇引用计数  2.10 其他选择  2.11 小结 第3章 接口层 3.1 引言 3.2 代码介绍  3.2.1 全局变量   3.2.2 SNMP变量 3.3 ifnet结构 3.4 ifaddr结构 3.5 sockaddr结构 3.6 ifnet与ifaddr的专用化 3.7 网络初始化概述 3.8 以太网初始化 3.9 SLIP初始化 3.10 环回初始化  …… 第4章 接口:以太网 第5章 接口:SLIP和环回 第6章 IP编址 第7章 域和协议 第8章 IP:网际协议 第9章 IP选项处理 第10章 IP的分片与重装 第11章 ICMP:Internet控制报文协议 第12章 IP多播 第13章 ICMP:Internet组管理协议 第14章 IP多播选路 第15章 插口层 第16章 插口I/O 第17章 插口选项 第18章 Radix树路由表 第19章 选路请求和选路消息 第20章 选路插口 书摘插图 第1章 概述  1.1 引言   本章介绍伯克利(Berkeley)联网程序代码。开始我们先看一段源代码并介绍一些通篇要用的印刷约定。对各种不同代码版本的简单历史回顾让我们可以看到本书中的源代码处于什么位置。接下来介绍了两种主要的编程接口,它们在Unix与非Unix系统中用于编写TCP/IP协议。   然后我们介绍一个简单的用户程序,它发送一个UDP数据报给一个位于另一主机上的日期,时间服务器,服务器返回一个UDP数据报,其中包含服务器上日期和时间的ASCIl码字符串。这个进程发送的数据报经过所有的协议栈到达设备驱动器,来自服务器的应答从下向上经过所有协议栈到达这个进程。通过这个例子的这些细节介绍了很多核心数据结构和概念,这些数据结构和概念在后面的章节中还要详细说明。 本章的最后介绍了在本书中各源代码的组织,并显示了联网代码在整个组织中的位置。  1.2 源代码表示   1.2.1 将拥塞窗口设置为1   这是文件tcp—subr.c中的函数tcp—quench。这些源文件名引用4.4BSD-Lite发布的文件。4.4BSD在1.13节中讨论。每个非空白行都有编号。正文所描述的代码的起始和结束位置的行号记于行开始处,如本段所示。有时在段前有一个简短的描述性题头,对所描述的代码提供一个概述。   这些源代码同4.4BSD—Lite发行版一样,偶尔也包含一些错误,在遇到时我们会提出来并加以讨论,偶尔还包括一些原作者的编者评论。这些代码已通过了GNU缩进程序的运行,使它们从版面上看起来具有一致性。制表符的位置被设置成4个栏的界线使得这些行在一个页面中显示得很合适。在定义常量时,有些#ifdef语句和它们的对应语句#endif被删去(如:GATEWAY和MROUTING,因为我们假设系统被作为一个路由器或多播路由器)。   ……
TCP-IP详解》共3卷,其他卷请到我空间下载,第2卷共分两个part,请下载完两个part后在解压。本书完整而详细地介绍了TCP/IP协议是如何实现的。书中给出了约500个图例,15 000行实际操作的C代码,采用举例教学的方法帮助你掌握TCP/IP实现。本书不仅说明了插口API和协议族的关系以及主机实现与路由器实现的差别。还介绍了4.4BSD-Lite版的新的特点,如多播、长肥管道支持、窗口缩放、时间戳选项以及其他主题等等。读者阅读本书时,应当具备卷1中阐述的关于TCP/IP的基本知识。本书适用于希望理解TCP/TP协议如何实现的人,包括编写网络应用程序的程序员以及利用TCP/IP维护计算机网络的系统管理员。 目 录 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 源代码表示 1 1.2.1 将拥塞窗口设置为1 1 1.2.2 印刷约定 2 1.3 历史 2 1.4 应用编程接口 3 1.5 程序示例 4 1.6 系统调用和库函数 6 1.7 网络实现概述 6 1.8 描述符 7 1.9 mbuf与输出处理 11 1.9.1 包含插口地址结构的mbuf 11 1.9.2 包含数据的mbuf 12 1.9.3 添加IP和UDP首部 13 1.9.4 IP输出 14 1.9.5 以太网输出 14 1.9.6 UDP输出小结 14 1.10 输入处理 15 1.10.1 以太网输入 15 1.10.2 IP输入 15 1.10.3 UDP输入 16 1.10.4 进程输入 17 1.11 网络实现概述(续) 17 1.12 中断级别与并发 18 1.13 源代码组织 20 1.14 测试网络 21 1.15 小结 22 第2章 mbuf:存储器缓存 24 2.1 引言 24 2.2 代码介绍 27 2.2.1 全局变量 27 2.2.2 统计 28 2.2.3 内核统计 28 2.3 mbuf的定义 29 2.4 mbuf结构 29 2.5 简单的mbuf宏和函数 31 2.5.1 m_get函数 32 2.5.2 MGET宏 32 2.5.3 m_retry函数 33 2.5.4 mbuf锁 34 2.6 m_devget和m_pullup函数 34 2.6.1 m_devget函数 34 2.6.2 mtod和dtom宏 36 2.6.3 m_pullup函数和连续的协议首部 36 2.6.4 m_pullup和IP的分片与重组 37 2.6.5 TCP重组避免调用m_pullup 39 2.6.6 m_pullup使用总结 40 2.7 mbuf宏和函数的小结 40 2.8 Net/3联网数据结构小结 42 2.9 m_copy和簇引用计数 43 2.10 其他选择 47 2.11 小结 47 第3章 接口层 49 3.1 引言 49 3.2 代码介绍 49 3.2.1 全局变量 49 3.2.2 SNMP变量 50 3.3 ifnet结构 51 3.4 ifaddr结构 57 3.5 sockaddr结构 58 3.6 ifnet与ifaddr的专用化 59 3.7 网络初始化概述 60 3.8 以太网初始化 61 3.9 SLIP初始化 64 3.10 环回初始化 65 3.11 if_attach函数 66 3.12 ifinit函数 72 3.13 小结 73 第4章 接口:以太网 74 4.1 引言 74 4.2 代码介绍 75 4.2.1 全局变量 75 4.2.2 统计量 75 4.2.3 SNMP变量 76 4.3 以太网接口 77 4.3.1 leintr函数 79 4.3.2 leread函数 79 4.3.3 ether_input函数 81 4.3.4 ether_output函数 84 4.3.5 lestart函数 87 4.4 ioctl系统调用 89 4.4.1 ifioctl函数 90 4.4.2 ifconf函数 91 4.4.3 举例 94 4.4.4 通用接口ioctl命令 95 4.4.5 if_down和if_up函数 96 4.4.6 以太网、SLIP和环回 97 4.5 小结 98 第5章 接口:SLIP和环回 100 5.1 引言 100 5.2 代码介绍 100 5.2.1 全局变量 100 5.2.2 统计量 101 5.3 SLIP接口 101 5.3.1 SLIP线路规程:SLIPDISC 101 5.3.2 SLIP初始化:slopen和slinit 103 5.3.3 SLIP输入处理:slinput 105 5.3.4 SLIP输出处理:sloutput 109 5.3.5 slstart函数 111 5.3.6 SLIP分组丢失 116 5.3.7 SLIP性能考虑 117 5.3.8 slclose函数 117 5.3.9 sltioctl函数 118 5.4 环回接口 119 5.5 小结 121 第6章 IP编址 123 6.1 引言 123 6.1.1 IP地址 123 6.1.2 IP地址的印刷规定 123 6.1.3 主机和路由器 124 6.2 代码介绍 125 6.3 接口和地址小结 125 6.4 sockaddr_in结构 126 6.5 in_ifaddr结构 127 6.6 地址指派 128 6.6.1 ifioctl函数 130 6.6.2 in_control函数 130 6.6.3 前提条件:SIOCSIFADDR、 SIOCSIFNETMASK和 SIOCSIFDSTADDR 132 6.6.4 地址指派:SIOCSIFADDR 133 6.6.5 in_ifinit函数 133 6.6.6 网络掩码指派:SIOCSIFNETMASK 136 6.6.7 目的地址指派:SIOCSIFDSTADDR 137 6.6.8 获取接口信息 137 6.6.9 每个接口多个IP地址 138 6.6.10 附加IP地址:SIOCAIFADDR 139 6.6.11 删除IP地址:SIOCDIFADDR 140 6.7 接口ioctl处理 141 6.7.1 leioctl函数 141 6.7.2 slioctl函数 142 6.7.3 loioctl函数 143 6.8 Internet实用函数 144 6.9 ifnet实用函数 144 6.10 小结 145 第7章 域和协议 146 7.1 引言 146 7.2 代码介绍 146 7.2.1 全局变量 147 7.2.2 统计量 147 7.3 domain结构 147 7.4 protosw结构 148 7.5 IP 的domain和protosw结构 150 7.6 pffindproto和pffindtype函数 155 7.7 pfctlinput函数 157 7.8 IP初始化 157 7.8.1 Internet传输分用 157 7.8.2 ip_init函数 158 7.9 sysctl系统调用 159 7.10 小结 161 第8章 IP:网际协议 162 8.1 引言 162 8.2 代码介绍 163 8.2.1 全局变量 163 8.2.2 统计量 163 8.2.3 SNMP变量 164 8.3 IP分组 165 8.4 输入处理:ipintr函数 167 8.4.1 ipintr概观 167 8.4.2 验证 168 8.4.3 转发或不转发 171 8.4.4 重装和分用 173 8.5 转发:ip_forward函数 174 8.6 输出处理:ip_output函数 180 8.6.1 首部初始化 181 8.6.2 路由选择 182 8.6.3 源地址选择和分片 184 8.7 Internet检验和:in_cksum函数 186 8.8 setsockopt和getsockopt系统调用 190 8.8.1 PRCO_SETOPT的处理 192 8.8.2 PRCO_GETOPT的处理 193 8.9 ip_sysctl函数 193 8.10 小结 194 第9章 IP选项处理 196 9.1 引言 196 9.2 代码介绍 196 9.2.1 全局变量 196 9.2.2 统计量 197 9.3 选项格式 197 9.4 ip_dooptions函数 198 9.5 记录路由选项 200 9.6 源站和记录路由选项 202 9.6.1 save_rte函数 205 9.6.2 ip_srcroute函数 206 9.7 时间戳选项 207 9.8 ip_insertoptions函数 210 9.9 ip_pcbopts函数 214 9.10 一些限制 217 9.11 小结 217 第10章 IP的分片与重装 218 10.1 引言 218 10.2 代码介绍 219 10.2.1 全局变量 220 10.2.2 统计量 220 10.3 分片 220 10.4 ip_optcopy函数 223 10.5 重装 224 10.6 ip_reass函数 227 10.7 ip_slowtimo函数 237 10.8 小结 238 第11章 ICMP:Internet控制报文协议 239 11.1 引言 239 11.2 代码介绍 242 11.2.1 全局变量 242 11.2.2 统计量 242 11.2.3 SNMP变量 243 11.3 icmp结构 244 11.4 ICMP 的protosw结构 245 11.5 输入处理:icmp_input函数 246 11.6 差错处理 249 11.7 请求处理 251 11.7.1 回显询问:ICMP_ECHO和 ICMP_ECHOREPLY 252 11.7.2 时间戳询问:ICMP_TSTAMP和 ICMP_TSTAMPREPLY 253 11.7.3 地址掩码询问:ICMP_MASKREQ和 ICMP_MASKREPLY 253 11.7.4 信息询问:ICMP_IREQ和ICMP_ IREQREPLY 255 11.7.5 路由器发现:ICMP_ROUTERADVERT 和ICMP_ROUTERSOLICIT 255 11.8 重定向处理 255 11.9 回答处理 257 11.10 输出处理 257 11.11 icmp_error函数 258 11.12 icmp_reflect函数 261 11.13 icmp_send函数 265 11.14 icmp_sysctl函数 266 11.15 小结 266 第12章 IP多播 268 12.1 引言 268 12.2 代码介绍 269 12.2.1 全局变量 270 12.2.2 统计量 270 12.3 以太网多播地址 270 12.4 ether_multi结构 271 12.5 以太网多播接收 273 12.6 in_multi结构 273 12.7 ip_moptions结构 275 12.8 多播的插口选项 276 12.9 多播的TTL值 277 12.9.1 MBONE 278 12.9.2 扩展环搜索 278 12.10 ip_setmoptions函数 278 12.10.1 选择一个明确的多播接口:IP_ MULTICAST_IF 280 12.10.2 选择明确的多播TTL: IP_ MULTICAST_TTL 281 12.10.3 选择多播环回:IP_MULTICAST_ LOOP 281 12.11 加入一个IP多播组 282 12.11.1 in_addmulti函数 285 12.11.2 slioctl和loioctl函数:SIOCADDMULTI和SIOCDELMULTI 287 12.11.3 leioctl函数:SIOCADDMULTI和 SIOCDELMULTI 288 12.11.4 ether_addmulti函数 288 12.12 离开一个IP多播组 291 12.12.1 in_delmulti函数 292 12.12.2 ether_delmulti函数 293 12.13 ip_getmoptions函数 295 12.14 多播输入处理:ipintr函数 296 12.15 多播输出处理:ip_output函数 298 12.16 性能的考虑 301 12.17 小结 301 第13章 IGMP:Internet组管理协议 303 13.1 引言 303 13.2 代码介绍 304 13.2.1 全局变量 304 13.2.2 统计量 304 13.2.3 SNMP变量 305 13.3 igmp结构 305 13.4 IGMP的protosw的结构 306 13.5 加入一个组:igmp_joingroup函数 306 13.6 igmp_fasttimo函数 308 13.7 输入处理:igmp_input函数 311 13.7.1 成员关系查询:IGMP_HOST_ MEMBERSHIP_QUERY 312 13.7.2 成员关系报告:IGMP_HOST_ MEMBERSHIP_REPORT 313 13.8 离开一个组:igmp_leavegroup函数 314 13.9 小结 315 第14章 IP多播选路 316 14.1 引言 316 14.2 代码介绍 316 14.2.1 全局变量 316 14.2.2 统计量 317 14.2.3 SNMP变量 317 14.3 多播输出处理(续) 317 14.4 mrouted守护程序 318 14.5 虚拟接口 321 14.5.1 虚拟接口表 322 14.5.2 add_vif函数 324 14.5.3 del_vif函数 326 14.6 IGMP(续) 327 14.6.1 add_lgrp函数 328 14.6.2 del_lgrp函数 329 14.6.3 grplst_member函数 330 14.7 多播选路 331 14.7.1 多播选路表 334 14.7.2 del_mrt函数 335 14.7.3 add_mrt函数 336 14.7.4 mrtfind函数 337 14.8 多播转发:ip_mforward函数 338 14.8.1 phyint_send函数 343 14.8.2 tunnel_send函数 344 14.9 清理:ip_mrouter_done函数 345 14.10 小结 346 第15章 插口层 348 15.1 引言 348 15.2 代码介绍 349 15.3 socket结构 349 15.4 系统调用 354 15.4.1 举例 355 15.4.2 系统调用小结 355 15.5 进程、描述符和插口 357 15.6 socket系统调用 358 15.6.1 socreate函数 359 15.6.2 超级用户特权 361 15.7 getsock和sockargs函数 361 15.8 bind系统调用 363 15.9 listen系统调用 364 15.10 tsleep和wakeup函数 365 15.11 accept系统调用 366 15.12 sonewconn和soisconnected 函数 369 15.13 connect系统调用 372 15.13.1 soconnect函数 374 15.13.2 切断无连接插口和外部地址的 关联 375 15.14 shutdown系统调用 375 15.15 close系统调用 377 15.15.1 soo_close函数 377 15.15.2 soclose函数 378 15.16 小结 380 第16章 插口I/O 381 16.1 引言 381 16.2 代码介绍 381 16.3 插口缓存 381 16.4 write、writev、sendto和sendmsg 系统调用 384 16.5 sendmsg系统调用 387 16.6 sendit函数 388 16.6.1 uiomove函数 389 16.6.2 举例 390 16.6.3 sendit代码 391 16.7 sosend函数 392 16.7.1 可靠的协议缓存 393 16.7.2 不可靠的协议缓存 393 16.7.3 sosend函数小结 401 16.7.4 性能问题 401 16.8 read、readv、recvfrom和recvmsg 系统调用 401 16.9 recvmsg系统调用 402 16.10 recvit函数 403 16.11 soreceive函数 405 16.11.1 带外数据 406 16.11.2 举例 406 16.11.3 其他的接收操作选项 407 16.11.4 接收缓存的组织:报文边界 407 16.11.5 接收缓存的组织:没有报文边界 408 16.11.6 控制信息和带外数据 409 16.12 soreceive代码 410 16.13 select系统调用 421 16.13.1 selscan函数 425 16.13.2 soo_select函数 425 16.13.3 selrecord函数 427 16.13.4 selwakeup函数 428 16.14 小结 429 第17章 插口选项 431 17.1 引言 431 17.2 代码介绍 431 17.3 setsockopt系统调用 432 17.4 getsockopt系统调用 437 17.5 fcntl和ioctl系统调用 440 17.5.1 fcntl代码 441 17.5.2 ioctl代码 443 17.6 getsockname系统调用 444 17.7 getpeername系统调用 445 17.8 小结 447 第18章 Radix树路由表 448 18.1 引言 448 18.2 路由表结构 448 18.3 选路插口 456 18.4 代码介绍 456 18.4.1 全局变量 458 18.4.2 统计量 458 18.4.3 SNMP变量 459 18.5 Radix结点数据结构 460 18.6 选路结构 463 18.7 初始化:route_init和rtable_init 函数 465 18.8 初始化:rn_init和rn_inithead 函数 468 18.9 重复键和掩码列表 471 18.10 rn_match函数 473 18.11 rn_search函数 480 18.12 小结 481 第19章 选路请求和选路消息 482 19.1 引言 482 19.2 rtalloc和rtalloc1函数 482 19.3 宏RTFREE和rtfree函数 484 19.4 rtrequest函数 486 19.5 rt_setgate函数 491 19.6 rtinit函数 493 19.7 rtredirect函数 495 19.8 选路消息的结构 498 19.9 rt_missmsg函数 501 19.10 rt_ifmsg函数 503 19.11 rt_newaddrmsg函数 504 19.12 rt_msg1函数 505 19.13 rt_msg2函数 507 19.14 sysctl_rtable函数 510 19.15 sysctl_dumpentry函数 514 19.16 sysctl_iflist函数 515 19.17 小结 517 第20章 选路插口 518 20.1 引言 518 20.2 routedomain和protosw结构 518 20.3 选路控制块 519 20.4 raw_init函数 520 20.5 route_output函数 520 20.6 rt_xaddrs函数 530 20.7 rt_setmetrics函数 531 20.8 raw_input函数 532 20.9 route_usrreq函数 534 20.10 raw_usrreq函数 535 20.11 raw_attach、raw_detach和raw_disconnect函数 539 20.12 小结 540 第21章 ARP:地址解析协议 542 21.1 介绍 542 21.2 ARP和路由表 542 21.3 代码介绍 544 21.3.1 全局变量 544 21.3.2 统计量 544 21.3.3 SNMP变量 546 21.4 ARP结构 546 21.5 arpwhohas函数 548 21.6 arprequest函数 548 21.7 arpintr函数 551 21.8 in_arpinput函数 552 21.9 ARP定时器函数 557 21.9.1 arptimer函数 557 21.9.2 arptfree函数 557 21.10 arpresolve函数 558 21.11 arplookup函数 562 21.12 代理ARP 563 21.13 arp_rtrequest函数 564 21.14 ARP和多播 569 21.15 小结 570 第22章 协议控制块 572 22.1 引言 572 22.2 代码介绍 573 22.2.1 全局变量 574 22.2.2 统计量 574 22.3 inpcb的结构 574 22.4 in_pcballoc和in_pcbdetach函数 575 22.5 绑定、连接和分用 577 22.6 in_pcblookup函数 581 22.7 in_pcbbind函数 584 22.8 in_pcbconnect函数 589 22.9 in_pcbdisconnect函数 594 22.10 in_setsockaddr和in_setpeeraddr 函数 595 22.11 in_pcbnotify、in_rtchange和in_losing函数 595 22.11.1 in_rtchange函数 598 22.11.2 重定向和原始插口 599 22.11.3 ICMP差错和UDP插口 600 22.11.4 in_losing函数 601 22.12 实现求精 602 22.13 小结 602 第23章 UDP:用户数据报协议 605 23.1 引言 605 23.2 代码介绍 605 23.2.1 全局变量 606 23.2.2 统计量 606 23.2.3 SNMP变量 607 23.3 UDP 的protosw结构 607 23.4 UDP的首部 608 23.5 udp_init函数 609 23.6 udp_output函数 609 23.6.1 在前面加上IP/UDP首部和mbuf簇 612 23.6.2 UDP检验和计算和伪首部 612 23.7 udp_input函数 616 23.7.1 对收到的UDP数据报的一般确认 616 23.7.2 分用单播数据报 619 23.7.3 分用多播和广播数据报 622 23.7.4 连接上的UDP插口和多接口主机 625 23.8 udp_saveopt函数 625 23.9 udp_ctlinput函数 627 23.10 udp_usrreq函数 628 23.11 udp_sysctl函数 633 23.12 实现求精 633 23.12.1 UDP PCB高速缓存 633 23.12.2 UDP检验和 634 23.13 小结 635 第24章 TCP:传输控制协议 636 24.1 引言 636 24.2 代码介绍 636 24.2.1 全局变量 636 24.2.2 统计量 637 24.2.3 SNMP变量 640 24.3 TCP 的protosw结构 641 24.4 TCP的首部 641 24.5 TCP的控制块 643 24.6 TCP的状态变迁图 645 24.7 TCP的序号 646 24.8 tcp_init函数 650 24.9 小结 652 第25章 TCP的定时器 654 25.1 引言 654 25.2 代码介绍 655 25.3 tcp_canceltimers函数 657 25.4 tcp_fasttimo函数 657 25.5 tcp_slowtimo函数 658 25.6 tcp_timers函数 659 25.6.1 FIN_WAIT_2和2MSL定时器 660 25.6.2 持续定时器 662 25.6.3 连接建立定时器和保活定时器 662 25.7 重传定时器的计算 665 25.8 tcp_newtcpcb算法 666 25.9 tcp_setpersist函数 668 25.10 tcp_xmit_timer函数 669 25.11 重传超时:tcp_timers函数 673 25.11.1 慢起动和避免拥塞 675 25.11.2 精确性 677 25.12 一个RTT的例子 677 25.13 小结 679 第26章 TCP输出 680 26.1 引言 680 26.2 tcp_output概述 680 26.3 决定是否应发送一个报文段 682 26.4 TCP选项 691 26.5 窗口大小选项 692 26.6 时间戳选项 692 26.6.1 哪个时间戳需要回显,RFC1323 算法 694 26.6.2 哪个时间戳需要回显,正确的 算法 695 26.6.3 时间戳与延迟ACK 695 26.7 发送一个报文段 696 26.8 tcp_template函数 707 26.9 tcp_respond函数 708 26.10 小结 710 第27章 TCP的函数 712 27.1 引言 712 27.2 tcp_drain函数 712 27.3 tcp_drop函数 712 27.4 tcp_close函数 713 27.4.1 路由特性 713 27.4.2 资源释放 716 27.5 tcp_mss函数 717 27.6 tcp_ctlinput函数 722 27.7 tcp_notify函数 723 27.8 tcp_quench函数 724 27.9 TCP_REASS宏和tcp_reass函数 724 27.9.1 TCP_REASS宏 725 27.9.2 tcp_reass函数 727 27.10 tcp_trace函数 732 27.11 小结 736 第28章 TCP的输入 737 28.1 引言 737 28.2 预处理 739 28.3 tcp_dooptions函数 745 28.4 首部预测 747 28.5 TCP输入:缓慢的执行路径 752 28.6 完成被动打开或主动打开 752 28.6.1 完成被动打开 753 28.6.2 完成主动打开 756 28.7 PAWS:防止序号回绕 760 28.8 裁剪报文段使数据在窗口内 762 28.9 自连接和同时打开 768 28.10 记录时间戳 770 28.11 RST处理 770 28.12 小结 772 第29章 TCP的输入(续) 773 29.1 引言 773 29.2 ACK处理概述 773 29.3 完成被动打开和同时打开 774 29.4 快速重传和快速恢复的算法 775 29.5 ACK处理 778 29.6 更新窗口信息 784 29.7 紧急方式处理 786 29.8 tcp_pulloutofband函数 788 29.9 处理已接收的数据 789 29.10 FIN处理 791 29.11 最后的处理 793 29.12 实现求精 795 29.13 首部压缩 795 29.13.1 引言 796 29.13.2 首部字段的压缩 799 29.13.3 特殊情况 801 29.13.4 实例 802 29.13.5 配置 803 29.14 小结 803 第30章 TCP的用户需求 805 30.1 引言 805 30.2 tcp_usrreq函数 805 30.3 tcp_attach函数 814 30.4 tcp_disconnect函数 815 30.5 tcp_usrclosed函数 816 30.6 tcp_ctloutput函数 817 30.7 小结 820 第31章 BPF:BSD 分组过滤程序 821 31.1 引言 821 31.2 代码介绍 821 31.2.1 全局变量 821 31.2.2 统计量 822 31.3 bpf_if结构 822 31.4 bpf_d结构 825 31.4.1 bpfopen函数 826 31.4.2 bpfioctl函数 827 31.4.3 bpf_setif函数 830 31.4.4 bpf_attachd函数 831 31.5 BPF的输入 832 31.5.1 bpf_tap函数 832 31.5.2 catchpacket函数 833 31.5.3 bpfread函数 835 31.6 BPF的输出 837 31.7 小结 838 第32章 原始IP 839 32.1 引言 839 32.2 代码介绍 839 32.2.1 全局变量 839 32.2.2 统计量 840 32.3 原始 IP的protosw结构 840 32.4 rip_init函数 842 32.5 rip_input函数 842 32.6 rip_output函数 844 32.7 rip_usrreq函数 846 32.8 rip_ctloutput函数 850 32.9 小结 852 结束语 853 附录A 部分习题的解答 854 附录B 源代码的获取 872 附录C RFC 1122 的有关内容 874 参考文献 895
### 回答1: 《TCP/IP详解 卷1:协议》是由Douglas E. Comer撰写的计算机网络领域的经典著作。本书主要介绍了互联网协议套件TCP/IP的基本原理和实现细节。 本书分为18章,从概述、物理层和数据链路层到网络层、传输层和应用层,详细讲解了TCP/IP协议栈中各个层次的协议和功能。每一章都深入浅出地解释了相关概念和原理,并通过丰富的示例和实验帮助读者加深理解。 书中首先介绍了互联网的发展背景和TCP/IP协议的起源,让读者对互联网的整体架构和规模有个初步了解。然后逐层介绍了数据的传输过程,包括二进制数据在网络中的传输、数据链路层的封装和解封装、网络层的IP协议和路由选择、传输层的TCP和UDP协议、以及应用层的各种协议。 此外,本书还介绍了一些基本的网络工具和调试方法,以及网络安全和QoS(Quality of Service)等相关主题。通过对各种案例的剖析和实践,读者能够深入理解协议的运作机制和网络应用的原理。 总之,《TCP/IP详解 卷1:协议》是一本细致入微地讲解TCP/IP协议的经典著作,能够帮助读者全面了解互联网基础协议的原理和实现细节。无论是计算机网络从业者还是对网络技术感兴趣的读者都可以从中受益匪浅。 ### 回答2: 《TCP/IP详解 卷1:协议》是一本权威的网络协议教材,以深入浅出的方式介绍了TCP/IP协议栈的各个层次和相关协议的细节。 该书分为7个章节,分别是前言、概述、IP协议、ARP协议、RARP协议、ICMP协议和IGMP协议。 在前言中,作者简要介绍了TCP/IP协议族的起源和发展,并概述了本书的内容和结构。 在概述部分,作者详细讲解了计算机网络的基本概念、网络协议的基本原理和TCP/IP协议栈的结构。同时,还介绍了TCP/IP协议和OSI参考模型之间的关系,并解释了TCP/IP协议在实际应用中的重要性。 接下来的IP协议章节中,作者详细介绍了IP协议的结构、数据包的格式和路由选择等相关知识。同时,还深入讨论了IP地址的分配、子网划分和无类别域间选路(CIDR)等重要概念。 在ARP协议和RARP协议章节中,作者解释了这两个协议的作用和工作原理。ARP协议用于将IP地址转换为MAC地址,而RARP协议则用于将MAC地址转换为IP地址。 ICMP协议章节中,作者详细介绍了ICMP协议的功能和使用场景。ICMP协议是一种用于网络中传递控制信息的协议,常用于错误报告和网络故障诊断。 最后一个章节是IGMP协议,作者讲解了IGMP协议是如何在多播通信中起到关键作用的。IGMP协议用于管理和控制多播组成员的加入和退出。 总之,《TCP/IP详解 卷1:协议》通过详细的讲解,帮助读者深入理解TCP/IP协议栈的各个层次和相关协议的工作原理,是一本非常宝贵的网络协议教材。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 93
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕斯( ˘▽˘)っ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值