Linux网络事件通知机制

1. kernel space –> kernel space

1.1. notifier_block原理介绍

linux内核中各个子系统相互依赖,当其中某个子系统状态发送改变时,就必须使用一定的机制告知使用其服务的其他子系统,以便其他子系统采取相应的措施。为满足这样的需求,内核实现了事件通知链机制。

网络子系统的通知链有三个:

  • netdev_chain,表示网络设备状态变化;
  • inetaddr_chain,表示ipv4地址发生变化;
  • inet6addr_chain,表示ipv6地址发生变化;

网络子系统中是由下面三个函数来注册netdev_chain、inetaddr_chain和inet6addr_chain的事件通知处理函数的。

int register_netdevice_notifier(struct notifer_block *nb);
int register_inetaddr_notifier(struct notifer_block *nb);
int register_inet6addr_notifier(struct notifer_block *nb);

可见上面三个注册函数都有一个关键的结构体,struct notifier_block;

struct notifier_block {
    int (*notifer_call)(struct notifier_block*, unsigned long, void*);
    struct notifier_block *next;
    int priority;
}
notifer_call: 当相对应事件发生时应以调用的函数;
next: 注册会把nb添加到对应的链表去,上面三个注册函数对应的链表分别为,netdev_chain、inetaddr_chain和inet6addr_chain,next用于遍历链表;
priority: 表示优先级,一般默认为0

例如在ip_fib_init函数中,注册了一个fib_netdev_notifier的网络设备状态变化监听器(netdev_chain),register_netdevice_notifier(&fib_netdev_notifier);
其中fib_netdev_notifier为:

static struct notifier_block fib_netdev_notifier = {
    .notifier_call = fib_netdev_event,
};

处理函数是fib_netdev_event,进到fib_netdev_event函数中可以看到对NETDEV_UP、NETDEV_DOWN、NETDEV_CHANGEMTU、NETDEV_CHANGE事件的处理;

注册好事件监听以及处理函数后,是如何触发事件通知的呢?

int call_netdevice_notifier(unsigned long val, struct net_device *dev)
{
ASSENT_RTNL();
return raw_notifier_call_chain(&netdev_chain, val, dev); /* val表示通知的事件值,例如NETDEV_UP */
}

raw_notifier_call_chain –> __raw_notifier_call_chain –> notifier_call_chain –> nb->notifier_call;
可见是通过call_device_notifier来遍历netdev_chain的监听器(struct notifier_block *nb),并依此调用其notifier_call,对应到上面那个例子就是fib_netdev_event;

同样,对于inetaddr_chain,通知函数是blocking_notifier_call_chain(&inetaddr_chain, val, v);对于inet6addr_chain,通知函数是inet6addr_notifier_call_chain –> atomic_notifier_call_chain(&inet6addr_chain, val, v);

1.2 常见的网络子系统的事件和通知

netdev_chain通知链,包括的事件有:

  • NETDEV_CHANGENAME,设置网口名称,dev_change_name;
  • NETDEV_FEAT_CHANGE,硬件feature改变,例如聚合,netdev_features_change;
  • NETDEV_CHANGE,载波状态发生改变,linkwatch_do_dev;
  • NETDEV_NOTIFY_PEERS,
  • NETDEV_PRE_UP,
  • NETDEV_UP,打开网口,dev_open;
  • NETDEV_GOING_DOWN,即将关闭网口,__dev_close_many;
  • NETDEV_DOWN,关闭网口,dev_close_many;
  • NETDEV_CHANGEMTU,设置mtu,dev_set_mtu;
  • NETDEV_CHANGEADDR,设置mac地址,dev_set_mac_address;
  • NETDEV_UNREGISTER,
  • NETDEV_POST_INIT,
  • NETDEV_REGISTER,网络设备注册完成,register_netdevice;
  • NETDEV_UNREGISTER_FINAL,

netdev_chain通知链的事件全部在net/core/dev.c中通知;

inetaddr_chain通知链,包括的事件有:

  • NETDEV_DOWN,网口上的ipaddr被删除,inet_del_ifa;
  • NETDEV_UP,网口上增加ipaddr,inet_insert_ifa;

2. kernel space -> user space

2.1. netlink原理介绍

netlink是一种特殊的socket,它是linux所特有的,类似于BSD中的AF_ROUTE,当又远比它的功能强大,目前使用netlink进行应用与内核通讯的应用很多,包括:NETLINK_ROUTE(路由daemon)、NETLINK_W1(1-wire子系统)、NETLINK_USERSOCK(用户态socket协议)、NETLINK_NFLOG(netfilter日志)、NETLINK_XFRM(ipsce安全策略)、NETLINK_SELINUX(SELinux事件通知)、NETLINK_ISCSI(iSCSI子系统)、NETLINK_AUDIT(进程审计)、NETLINK_FIB_LOOKUP(转发信息表查询)、NETLINK_CONNECTOR(netlink connector)、NETLINK_NETFILTER(netfilter子系统)、NETLINK_IP6_FW(IPv6防火墙)、NETLINK_DNRTMSG(DECnet路由信息)、NETLINK_KOBJECT_UEVENT(内核事件向用户态通知)、NETLINK_GENERIC(通用netlink);

其中网络子系统中最常使用的几个有NETLINK_KOBJECT_UEVENT、NETLINK_ROUTE、NETLINK_NETFILTER;

以dev_open为例,在打开网口后,kernel space会通知user space网口的状态变成IFF_UP|IFF_RUNNING,rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING);

void rtmsg_ifinfo(int type, struct net_device *dev, unsigned int change)
{
        struct net *net = dev_net(dev);
        struct sk_buff *skb;
        int err = -ENOBUFS;
        size_t if_info_size;

        skb = nlmsg_new((if_info_size = if_nlmsg_size(dev, 0)), GFP_KERNEL); /* 新建nlmsg */
        if (skb == NULL)
                goto errout;

        err = rtnl_fill_ifinfo(skb, dev, type, 0, 0, change, 0, 0); /* 填充nlmsg,将type,dev,change信息添加到skb中 */
        if (err < 0) {
                /* -EMSGSIZE implies BUG in if_nlmsg_size() */
                WARN_ON(err == -EMSGSIZE);
                kfree_skb(skb);
                goto errout;
        }
        rtnl_notify(skb, net, 0, RTNLGRP_LINK, NULL, GFP_KERNEL); /* 发送消息 */
        return;
errout:
        if (err < 0)
                rtnl_set_sk_err(net, RTNLGRP_LINK, err);
}

其中nlmsg_new就是申请一定长度的sk_buff的内存,这个长度包括:

return NLMSG_ALIGN(sizeof(struct ifinfomsg))
       + nla_total_size(IFNAMSIZ) /* IFLA_IFNAME */
       + nla_total_size(IFALIASZ) /* IFLA_IFALIAS */
       + nla_total_size(IFNAMSIZ) /* IFLA_QDISC */
       + nla_total_size(sizeof(struct rtnl_link_ifmap))
       + nla_total_size(sizeof(struct rtnl_link_stats))
       + nla_total_size(sizeof(struct rtnl_link_stats64))
       + nla_total_size(MAX_ADDR_LEN) /* IFLA_ADDRESS */
       + nla_total_size(MAX_ADDR_LEN) /* IFLA_BROADCAST */
       + nla_total_size(4) /* IFLA_TXQLEN */
       + nla_total_size(4) /* IFLA_WEIGHT */
       + nla_total_size(4) /* IFLA_MTU */
       + nla_total_size(4) /* IFLA_LINK */
       + nla_total_size(4) /* IFLA_MASTER */
       + nla_total_size(1) /* IFLA_CARRIER */
       + nla_total_size(4) /* IFLA_PROMISCUITY */
       + nla_total_size(4) /* IFLA_NUM_TX_QUEUES */
       + nla_total_size(4) /* IFLA_NUM_RX_QUEUES */
       + nla_total_size(1) /* IFLA_OPERSTATE */
       + nla_total_size(1) /* IFLA_LINKMODE */
       + nla_total_size(ext_filter_mask
                        & RTEXT_FILTER_VF ? 4 : 0) /* IFLA_NUM_VF */
       + rtnl_vfinfo_size(dev, ext_filter_mask) /* IFLA_VFINFO_LIST */
       + rtnl_port_size(dev, ext_filter_mask) /* IFLA_VF_PORTS + IFLA_PORT_SELF */
       + rtnl_link_get_size(dev) /* IFLA_LINKINFO */
       + rtnl_link_get_af_size(dev); /* IFLA_AF_SPEC */

rtl_notify最终调用nlmsg_notify,nlmsg_unicast,netlink_unicast将nelink事件发到user space;
其中在rtnl_notify中使用到一个sock:struct sock *rtnl = net->rtnl;最终netlink_unicast就是通过rtnl这个socket发送消息的;
其中net->rtnl在rtnetlink_net_init中初始化;

static int __net_init rtnetlink_net_init(struct net *net)
{
        struct sock *sk;
        struct netlink_kernel_cfg cfg = {
                .groups         = RTNLGRP_MAX,
                .input          = rtnetlink_rcv,
                .cb_mutex       = &rtnl_mutex,
                .flags          = NL_CFG_F_NONROOT_RECV,
        };

        sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg); /* 创建netlink socket,类型为NETLINK_ROUTE */
        if (!sk)
                return -ENOMEM;
        net->rtnl = sk;
        return 0;
}

2.2. 常见的网络内核消息通知

linkwatch_do_dev -->
  netdev_state_change
    rtmsg_ifinfo(RTM_NEWLINK, dev, 0)

在rtnl_fill_ifinfo里面,会获取dev的flag,ifm->ifi_flags = dev_get_flags(dev);

unsigned int dev_get_flags(const struct net_device *dev)
{
        unsigned int flags;

        flags = (dev->flags & ~(IFF_PROMISC |
                                IFF_ALLMULTI |
                                IFF_RUNNING |
                                IFF_LOWER_UP |
                                IFF_DORMANT)) |
                (dev->gflags & (IFF_PROMISC |
                                IFF_ALLMULTI));

        if (netif_running(dev)) {
                if (netif_oper_up(dev))
                        flags |= IFF_RUNNING;
                if (netif_carrier_ok(dev))
                        flags |= IFF_LOWER_UP; /* 检查载波状态,如果ok则设置IFF_LOWER_UP,否则不设置;
                if (netif_dormant(dev))
                        flags |= IFF_DORMANT;
        }

        return flags;
}

netlink类型是NETLINK_ROUTE,消息类型是RTM_NEWLINK;

2.2.2. interfaceAdded/interfaceRemoved,网口添加或移除事件

register_netdev -->
  register_netdevice -->
    netdev_register_kobject -->
      device_add -->
        kobject_uevent(&dev-kobj, KOBJ_ADD)

可见网络添加和移除走的是linux通用热插拔事件流程,网口添加时KOBJ_ADD,网络移除是KOBJ_REMOVE,netlink类型是NETLINK_KOBJECT_UEVENT;

2.2.3. addressUpdated/addressRemoved,配置ipaddr,ipaddr删除

__inet_insert_ifa -->
  rtmsg_ifa(RTM_NEWADDR, ifa, nlh, portid) -->
    rtn_notify(skb, net, portid, RTNLGRP_IPV4_IFADDR, nlh, GTP_KERNLE);

配置ipaddr用的netlink类型是NETLINK_ROUTE,消息类型是RTM_NEWADDR;
删除ipaddr用的netlink类型是NETLINK_ROUTE,消息类型是RTM_DELADDR;

2.2.4. routeUpdated/routeRemoved,更新路由,删除路由

fib_table_insert -->
  rtmsg_fib(RTM_NEWROUTE, ...) -->
    rtnl_nofify(skb, info->nl_net, info->portid, RTNLGRP_IPV4_ROUTE, ifo->nlh, GFP_KERNEL)

更新路由用的netlink类型是NETLINK_ROUTE,消息类型是RTM_NEWADDR;
删除路由用的netlink类型是NETLINK_ROUTE,消息类型是RTM_DELADDR;

2.3. 用户态接受消息

android对网络发自kernel的netlink事件的接收是在netd和libsysutils里面实现的,其中netd注册了接收netlink的socket,libsysutils负责接收和事件解析;

2.3.1. 注册socket

if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
         0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
    return -1;
}

if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE,
                                 RTMGRP_LINK |
                                 RTMGRP_IPV4_IFADDR |
                                 RTMGRP_IPV6_IFADDR |
                                 RTMGRP_IPV6_ROUTE |
                                 (1 << (RTNLGRP_ND_USEROPT - 1)),
         NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) {
    return -1;
}
NetlinkHandler *NetlinkManager::setupSocket(int *sock, int netlinkFamily,
    int groups, int format) {

    struct sockaddr_nl nladdr;
    int sz = 64 * 1024;
    int on = 1;

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = groups;

    if ((*sock = socket(PF_NETLINK, SOCK_DGRAM, netlinkFamily)) < 0) {
        ALOGE("Unable to create netlink socket: %s", strerror(errno));
        return NULL;
    }

    if (setsockopt(*sock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
        ALOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));
        close(*sock);
        return NULL;
    }

    if (setsockopt(*sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
        close(*sock);
        return NULL;
    }

    if (bind(*sock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
        ALOGE("Unable to bind netlink socket: %s", strerror(errno));
        close(*sock);
        return NULL;
    }

    NetlinkHandler *handler = new NetlinkHandler(this, *sock, format);
    if (handler->start()) {
        ALOGE("Unable to start NetlinkHandler: %s", strerror(errno));
        close(*sock);
        return NULL;
    }

    return handler;
}

2.3.2. 接收消息

bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
    int socket = cli->getSocket();
    ssize_t count;
    uid_t uid = -1;

    count = TEMP_FAILURE_RETRY(uevent_kernel_multicast_uid_recv(
                                       socket, mBuffer, sizeof(mBuffer), &uid));
    if (count < 0) {
        if (uid > 0)
            LOG_EVENT_INT(65537, uid);
        SLOGE("recvmsg failed (%s)", strerror(errno));
        return false;
    }

    NetlinkEvent *evt = new NetlinkEvent();
    if (evt->decode(mBuffer, count, mFormat)) {
        onEvent(evt);
    } else if (mFormat != NETLINK_FORMAT_BINARY) {
        // Don't complain if parseBinaryNetlinkMessage returns false. That can
        // just mean that the buffer contained no messages we're interested in.
        SLOGE("Error decoding NetlinkEvent");
    }

    delete evt;
    return true;
}

2.3.3. 消息解析

bool NetlinkEvent::decode(char *buffer, int size, int format) {
    if (format == NetlinkListener::NETLINK_FORMAT_BINARY) {
        /**
         * parseIfInfoMessage: NlActionLinkUp, NlActionLinkDown
         * parseIfAddrMessage: NlActionAddressUpdated, NlActionAddressRemoved
         * parseRtMessage: NlActionRouteUpdated, NlActionRouteRemoved
         */
        return parseBinaryNetlinkMessage(buffer, size);
    } else {
        /**
         * NlActionAdd, NlActionRemove
         */
        return parseAsciiNetlinkMessage(buffer, size);
    }
}

3. user space –> kernel space

用户态经常需要配置一下参数到内核态,常见的机制有netlink和ioctl;
android netd中的RouteControl.cpp中就有很多路由的设置,其使用的就是netlink socket;

WARN_UNUSED_RESULT int sendNetlinkRequest(uint16_t action, uint16_t flags, iovec* iov, int iovlen) {
    nlmsghdr nlmsg = {
        .nlmsg_type = action,
        .nlmsg_flags = flags,
    };
    iov[0].iov_base = &nlmsg;
    iov[0].iov_len = sizeof(nlmsg);
    for (int i = 0; i < iovlen; ++i) {
        nlmsg.nlmsg_len += iov[i].iov_len;
    }

    int ret;
    struct {
        nlmsghdr msg;
        nlmsgerr err;
    } response;

    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); // 创建netlink socket
    if (sock != -1 &&
            connect(sock, reinterpret_cast<const sockaddr*>(&NETLINK_ADDRESS),
                    sizeof(NETLINK_ADDRESS)) != -1 &&
            writev(sock, iov, iovlen) != -1 &&
            (ret = recv(sock, &response, sizeof(response), 0)) != -1) { // 发送消息并接收回复
        if (ret == sizeof(response)) {
            ret = response.err.error;  // Netlink errors are negative errno.
            if (ret) {
                ALOGE("netlink response contains error (%s)", strerror(-ret));
            }
        } else {
            ALOGE("bad netlink response message size (%d != %zu)", ret, sizeof(response));
            ret = -EBADMSG;
        }
    } else {
        ALOGE("netlink socket/connect/writev/recv failed (%s)", strerror(errno));
        ret = -errno;
    }

    if (sock != -1) {
        close(sock);
    }

    return ret;
}

发送的消息有:

  • RTM_NEWRULE: 添加策略路由表
  • RTM_DELRULE: 删除策略路由表
  • RTM_NEWROUTE: 插入路由表项
  • RTM_DElROUTE: 删除路由表项

kernel中在rtnelink_net_init中就注册了一个接收用户态netlink事件额socket:

static int __net_init rtnetlink_net_init(struct net *net)
{
        struct sock *sk;
        struct netlink_kernel_cfg cfg = {
                .groups         = RTNLGRP_MAX,
                .input          = rtnetlink_rcv, /* 接收函数 */
                .cb_mutex       = &rtnl_mutex,
                .flags          = NL_CFG_F_NONROOT_RECV,
        };

        sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
        if (!sk)
                return -ENOMEM;
        net->rtnl = sk;
        return 0;
}

netlink_rcv –> netlink_rcv_skb –> rtnetlink_rcv_msg –> rtnl_get_doit –> doit;
其中rtnl_get_doit及时从一个数组中去消息处理函数,由family和type来做匹配,而这个消息处理函数是在ip_fib_init中添加的;

rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL);
rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL);

在fib_rules_init中添加RTM_NEWRULE和RTM_DELRULE的处理函数:

rtnl_register(PF_USPEC, RTM_NEWRULE, fib_nl_newrule, NULL, NULL);
rtnl_register(PF_USPEC, RTM_DELRULE, fib_nl_delrule, NULL, NULL);

综上,RTM_NEWRULE、RTM_DELRULE、RTM_NEWROUTE、RTM_DElROUTE对应于kernel的处理函数是:
* RTM_NEWRULE –> fib_nl_newrule
* RTM_DELRULE –> fib_nl_delrule
* RTM_NEWROUTE –> inet_rtm_newroute
* RTM_DElROUTE –> inet_rtm_delroute

此外,还要一些用户态消息与内核态处理函数的映射关系:

/* 邻居子系统相关 *、
rtnl_register(PF_UNSPEC, RTM_NEWNEIGH, neigh_add, NULL, NULL);
rtnl_register(PF_UNSPEC, RTM_DELNEIGH, neigh_delete, NULL, NULL);
rtnl_register(PF_UNSPEC, RTM_GETNEIGH, neigh_delete, NULL, NULL);

/* ipaddr相关 */
rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL, NULL);
rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL, NULL);
rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr, NULL);
  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第 1章 概述 1 1.1 网络编程相关的基本概念 1 1.1.1 网络编程与进程通信 1 1.1.2 Internet中网间进程的标识 3 1.1.3 网络协议的特征 7 1.2 三类网络编程 10 1.2.1 基于TCP/IP协议栈的网络编程 10 1.2.2 基于WWW应用的网络编程 10 1.2.3 基于.NET框架的Web Services网络编程 10 1.3 客户机/服务器交互模式 13 1.3.1 网络应用软件的地位和功能 13 1.3.2 客户机/服务器模式 14 1.3.3 客户机与服务器的特性 15 1.3.4 容易混淆的术语 16 1.3.5 客户机与服务器的通信过程 16 1.3.6 网络协议与C/S模式的关系 17 1.3.7 错综复杂的C/S交互 17 1.3.8 服务器如何同时为多个客户机服务 18 1.3.9 标识一个特定服务 20 1.4 P2P模式 21 1.4.1 P2P技术的兴起 21 1.4.2 P2P的定义和特征 21 1.4.3 P2P的发展 22 1.4.4 P2P的关键技术 22 1.4.5 P2P系统的应用与前景 22 习题 23 第 2章 套接字网络编程基础 24 2.1 套接字网络编程接口的产生与发展 24 2.1.1 问题的提出 24 2.1.2 套接字编程接口起源于UNIX操作系统 25 2.1.3 套接字编程接口在Windows和Linux操作系统中得到继承和发展 25 2.1.4 套接字编程接口的两种实现方式 25 2.1.5 套接字通信与UNIX操作系统的输入/输出的关系 26 2.2 套接字编程的基本概念 27 2.2.1 什么是套接字 27 2.2.2 套接字的特点 28 2.2.3 套接字的应用场合 30 2.2.4 套接字使用的数据类型和相关的问题 30 2.3 面向连接的套接字编程 32 2.3.1 可靠的传输控制协议 32 2.3.2 套接字的工作过程 33 2.3.3 面向连接的套接字编程实例 34 2.3.4 进程的阻塞问题和对策 40 2.4 无连接的套接字编程 43 2.4.1 高效的用户数据报协议 43 2.4.2 无连接的套接字编程的两种模式 43 2.4.3 数据报套接字的对等模式编程实例 45 2.5 原始套接字 47 2.5.1 原始套接字的创建 47 2.5.2 原始套接字的使用 48 2.5.3 原始套接字应用实例 49 习题 51 第3章 WinSock编程 53 3.1 WinSock概述 53 3.2 WinSock库函数 55 3.2.1 WinSock的注册与注销 55 3.2.2 WinSock的错误处理函数 58 3.2.3 主要的WinSock函数 61 3.2.4 WinSock的辅助函数 74 3.2.5 WinSock的信息查询函数 77 3.2.6 WSAAsyncGetXByY类型的扩展函数 79 3.3 网络应用程序的运行环境 82 习题 84 第4章 MFC编程 85 4.1 MFC概述 85 4.1.1 MFC是一个编程框架 85 4.1.2 典型的MDI应用程序的构成 87 4.2 MFC和Win32 89 4.2.1 MFC对象和Windows对象的关系 89 4.2.2 几个主要的类 91 4.3 CObject类 95 4.3.1 CObject类的定义 95 4.3.2 CObject类的特性 96 4.4 消息映射的实现 98 4.5 MFC对象的创建 102 4.5.1 MFC对象的关系 102 4.5.2 MFC提供的接口 104 4.5.3 MFC对象的创建过程 104 4.6 应用程序的退出 107 习题 107 第5章 MFC WinSock类的 编程 109 5.1 CAsyncSocket类 110 5.1.1 使用CAsyncSocket类的一般步骤 110 5.1.2 创建CAsyncSocket类对象 111 5.1.3 关于CAsyncSocket类可以接受并处理的消息事件 112 5.1.4 客户端套接字对象请求连接到服务器端套接字对象 114 5.1.5 服务器接收客户机的连接请求 115 5.1.6 发送与接收流式数据 116 5.1.7 关闭套接字 118 5.1.8 错误处理 118 5.1.9 其他成员函数 119 5.2 CSocket类 120 5.2.1 创建CSocket对象 120 5.2.2 建立连接 120 5.2.3 发送和接收数据 120 5.2.4 CSocket类、CArchive类和CSocketFile类 121 5.2.5 关闭套接字和清除相关的对象 122 5.3 CSocket类的编程模型 122 5.4 用CAsyncSocket类实现聊天室程序 123 5.4.1 实现目标 123 5.4.2 创建客户端应用程序 124 5.4.3 客户端程序的类与消息驱动 134 5.4.4 客户端程序主要功能的代码和分析 135 5.4.5 创建服务器端程序 142 5.4.6 服务器端程序的流程和消息驱动 144 5.4.7 点对点交谈的服务器端程序主要功能的代码和分析 145 5.5 用CSocket类实现聊天室程序 151 5.5.1 聊天室程序的功能 151 5.5.2 创建聊天室的服务器端程序 151 5.5.3 聊天室服务器端程序的主要实现代码和分析 154 5.5.4 创建聊天室的客户端程序 162 5.5.5 聊天室客户端程序的主要实现代码和分析 163 习题 170 实验 170 第6章 WinInet编程 172 6.1 MFC WinInet类 172 6.1.1 概述 172 6.1.2 MFC WinInet所包含的类 173 6.1.3 使用WinInet类编程的一般步骤 174 6.1.4 创建CInternetSession类对象 175 6.1.5 查询或设置Internet请求选项 176 6.1.6 创建连接类对象 177 6.1.7 使用文件检索类 178 6.1.8 重载OnStatusCallback函数 179 6.1.9 创建并使用网络文件类对象 180 6.1.10 CInternteException类 183 6.2 用MFC WinInet类实现FTP客户端 183 6.2.1 程序要实现的功能 183 6.2.2 创建应用程序的过程 184 习题 186 实验 187 第7章 WinSock的多线程 编程 188 7.1 WinSock为什么需要多线程编程 188 7.1.1 WinSock的两种I/O模式 188 7.1.2 两种模式的优缺点及解决方法 189 7.2 Win32操作系统下的多进程多线程机制 189 7.2.1 Win32 OS是单用户多任务的操作系统 189 7.2.2 Win32 OS是支持多线程的操作系统 190 7.2.3 多线程机制网络编程中的应用 191 7.3 VC++对多线程网络编程的支持 192 7.3.1 MFC支持的两种线程 192 7.3.2 创建MFC的工作线程 193 7.3.3 创建并启动用户界面线程 195 7.3.4 终止线程 198 7.4 多线程FTP客户端实例 200 7.4.1 编写线程函数 200 7.4.2 添加事件处理函数 206 习题 208 第8章 WinSock的I/O模型 209 8.1 select模型 210 8.2 WSAAsyncSelect异步I/O模型 212 8.3 WSAEventSelect事件选择模型 216 8.4 重叠I/O模型 221 8.4.1 重叠I/O模型的优点 221 8.4.2 重叠I/O模型的基本原理 221 8.4.3 重叠I/O模型的关键函数和数据结构 222 8.4.4 使用事件通知实现重叠模型的步骤 225 8.4.5 使用完成例程实现重叠模型的步骤 227 8.5 完成端口模型 229 8.5.1 什么是完成端口模型 229 8.5.2 使用完成端口模型的方法 230 习题 238 第9章 HTTP及编程 239 9.1 HTTP 239 9.1.1 HTTP的背景 239 9.1.2 HTTP的内容 240 9.1.3 HTTP消息的一般格式 242 9.1.4 HTTP请求的格式 243 9.1.5 HTTP响应的格式 245 9.1.6 访问认证 248 9.1.7 URL编码 249 9.1.8 HTTP的应用 250 9.2 利用CHtmlView类创建Web浏览器型的应用程序 250 9.2.1 CHtmlView类与WebBrowser控件 250 9.2.2 CHtmlView类的成员函数 251 9.2.3 创建一个Web浏览器型的应用程序的一般步骤 256 9.3 Web浏览器应用程序实例 261 9.3.1 程序实现的目标 261 9.3.2 创建实例程序 262 习题 265 实验 265 第 10章 电子邮件协议与编程 267 10.1 电子邮件系统的工作原理 267 10.1.1 电子邮件的特点 267 10.1.2 电子邮件系统的构成 267 10.1.3 电子邮件系统的实现 268 10.2 简单邮件传送协议 270 10.2.1 概述 270 10.2.2 SMTP客户机与SMTP服务器之间的会话 270 10.2.3 常用的SMTP命令 271 10.2.4 常用的SMTP响应码 273 10.2.5 SMTP的会话过程 274 10.2.6 使用WinSock来实现电子邮件客户机与服务器的会话 274 10.3 电子邮件信件结构详述 275 10.3.1 Internet文本信件的格式标准——RFC 822 275 10.3.2 信件的头部 276 10.3.3 构造和分析符合RFC 822标准的电子信件 281 10.4 MIME编码解码与发送附件 281 10.4.1 MIME概述 281 10.4.2 MIME定义的新的信头字段 282 10.4.3 MIME邮件的内容类型 283 10.4.4 MIME邮件的编码方式 292 10.5 POP3与接收电子邮件 294 10.5.1 POP3 294 10.5.2 POP3的会话过程 294 10.5.3 POP3会话的3个状态 295 10.5.4 POP3标准命令 296 10.5.5 接收电子邮件的一般步骤 298 10.6 接收电子邮件的程序实例 299 10.6.1 实例程序的目的和实现的技术要点 299 10.6.2 创建应用程序的过程 301 10.7 发送电子邮件的程序实例 302 10.7.1 实例程序的目的和实现的技术要点 302 10.7.2 创建应用程序的过程 303 习题 305 参考文献 307

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值