Lesson9.网络基础

1. 网络协议初识

  • 所谓的协议就是人们为了通信的一种约定
  •  操作系统要进行协议管理,必然会先描述,再组织
  • 协议本质就是软件,软件是可以"分层"
  • 协议在设计的时候,就是被层状的划分的, 

 为什么要划分成为层状结构

  • 因为有的场景复杂,需要把功能解耦(便于人们进行各种维护)

 常见OSI的5层模型

  •  a.应用层,b. 传输层,c.网络层,d.数据链路层,e.物理层

 2. OSI七层模型

2.1 局域网中两台主机是可以直接通信的 

  • 每层都有自己的协议定制方案,
  • 每层协议都要有自己的协议报头
  • 从上到下交付数据的时候,要添加报头
  • 从下到上递交数据的时候,要去掉报头

2.2 理解报头

  • 报头就像快递的快递单号一样,虽然我们不看,但是必须有
  • 封装的本质: 添加报头
  • 解包: 去掉报头&&展开分析

  •  在使用TCP/IP协议的网络中,IP及其向上的协议,看到的报文都是一样的

  • 报文是要被封装的,如何解包?
  • 决定我们的有效载荷交付给上层的哪一个协议的问题? 
  • 每一个协议都要考虑,都要有一定的方式解决这两个公共问题

2.3 理解源IP地址和目的IP地址 

  •  把数据哦送到对方的机器不是目的,真正的网络通信: 其实是进程间通信
  •  IP地址(公网IP),标定了主机的唯一性
  • 源IP是固定不变的,目的IP是可以变的

2.4 理解端口号

  •  端口号是标识特定主机上的网络进程的唯一性
  •  端口号是一个2字节16位的整数
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

3. socket编程接口

 常见的套接字:

  • 域间socket
  • 原始socket
  • 网络socket

理论上,是三种应用场景,对应的应该是三套接口,不过将所有的接口进行统一


3.1 使用网络在两台主机之间进行通信(之间互发信息)

 

 

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

// #define LOGFILE "./threadpool.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    printf("%s%s\n", stdBuffer, logBuffer);
}

Makefile

.PHONY:all
all:udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11 
udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udp_client udp_server

udp_client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
uint16_t serverport = 0;
std::string serverip;

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    serverport = atoi(argv[2]);
    serverip = argv[1];
    
    // client一般不需要显示的bind指定port,而是让OS自动随机选择(什么时候做的呢?)
    std::string message;
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));// 清0
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);

    char buffer[1024];
    while(true)
    {
        //多线程
        std::cout << "请输入你的信息# ";
        std::getline(std::cin, message);
        if(message == "quit") break;
        // 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof server);

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    return 0;
}

udp_server.cc

#include "udp_server.hpp"
#include <memory>
#include <cstdlib>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

// ./udp_server ip port //云服务器的问题 bug??
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    // std::string ip = argv[1];
    uint16_t port = atoi(argv[1]);// 字符串->整数
    // unique_ptr智能指针
    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->initServer();
    svr->Start();
    return 0;
}

udp_server.hpp

#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP

#include "log.hpp"
#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <queue>

// 网络的常用的四个接口
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = "") : _port(port), _ip(ip), _sock(-1)
    {
    }
    // 初始化
    bool initServer()
    {
        //  1. 创建套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        // 2. bind: 将用户设置的ip和port在内核中和我们当前的进程强关联
        struct sockaddr_in local;// sockaddr_in域间套接字
        bzero(&local, sizeof(local));// 清0
        local.sin_family = AF_INET;

        // 服务器的IP和端口未来也是要发送给对方主机的 -> 先要将数据发送到网络!
        // 主机序列->网络序列
        local.sin_port = htons(_port);
        //INADDR_ANY表示让服务器在工作过程中,可以从任意IP中获取数据
        //inet_add表示将点分十进制字符串(192.168.110.132)风格的IP地址->4字节主机序列->网络序列
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
        // done
        return true;
    }

    void Start()
    {
        // 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!
        // echo server: client给我们发送消息,我们原封不动返回
        char buffer[SIZE];
        for (;;)
        {
            // 注意:peer是纯输出型参数
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            // 输入: peer缓冲区大小
            // 输出: 实际读到的peer
            socklen_t len = sizeof(peer);
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                buffer[s] = 0; // 我们目前数据当做字符串
                uint16_t cli_port = ntohs(peer.sin_port);      // 从网络中来的!
                std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示
                printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
            }
            // 分析和处理数据,TODO
            // end. 写回数据
            sendto(_sock, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
        }
    }
    ~UdpServer()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    // 一个服务器,一般必须需要ip地址和port(16位的整数)
    uint16_t _port;// 端口号
    std::string _ip;// ip地址
    int _sock;// 套接字
};
#endif

 


3.2 使用网络在两台主机之间进行通信(交互式的命令)

void Start()
    {
        // 服务器启动-> 进程 -> 常驻进程 -> 永远在内存中存在,除非挂了!
        // echo server: client给我们发送消息,我们原封不动返回
        char buffer[SIZE];
        for (;;)
        {
            // 注意:peer是纯输出型参数
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            // 输入: peer缓冲区大小
            // 输出: 实际读到的peer
            socklen_t len = sizeof(peer);
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            char result[256];
            char key[64];
            std::string cmd_echo;
            if (s > 0)
            {
                buffer[s] = 0; // 我们目前数据当做字符串
                uint16_t cli_port = ntohs(peer.sin_port);      // 从网络中来的!
                std::string cli_ip = inet_ntoa(peer.sin_addr); // 4字节的网络序列的IP->本主机的字符串风格的IP,方便显示
                // 你发过来的字符串是指令 ls -a -l, rm -rm ~
                if(strcasestr(buffer, "rm") != nullptr || strcasestr(buffer, "rmdir") != nullptr)
                {
                    std::string err_message = "坏人.... ";
                    std::cout << err_message << buffer << std::endl;
                    sendto(_sock, err_message.c_str(), err_message.size(), 0, (struct sockaddr *)&peer, len);
                    continue;
                }
                FILE *fp = popen(buffer, "r");
                if (nullptr == fp)
                {
                    logMessage(ERROR, "popen: %d:%s", errno, strerror(errno));
                    continue;
                }
                while (fgets(result, sizeof(result), fp) != nullptr)
                {
                    cmd_echo += result;
                }
                fclose(fp);
                // printf("[%s:%d]# %s\n", cli_ip.c_str(), cli_port, buffer);
            }
            // 分析和处理数据,TODO
            // end. 写回数据
            sendto(_sock, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr *)&peer, len);
        }

3.3 使用网络在两台主机之间进行通信(多进程)

makeflie

.PHONY:all
all:udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11 -lpthread 
udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udp_client udp_server

 log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

// #define LOGFILE "./threadpool.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    printf("%s%s\n", stdBuffer, logBuffer);
}

thread.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstdio>

// typedef std::function<void* (void*)> fun_t;
typedef void *(*fun_t)(void *);

class ThreadData
{
public:
    void *args_;
    std::string name_;
};

class Thread
{
public:
    Thread(int num, fun_t callback, void *args) : func_(callback)
    {
        char nameBuffer[64];
        snprintf(nameBuffer, sizeof nameBuffer, "Thread-%d", num);
        name_ = nameBuffer;

        tdata_.args_ = args;
        tdata_.name_ = name_;
    }
    void start()
    {
        pthread_create(&tid_, nullptr, func_, (void*)&tdata_);
    }
    void join()
    {
        pthread_join(tid_, nullptr);
    }
    std::string name()
    {
        return name_;
    }
    ~Thread()
    {
    }

private:
    std::string name_;
    fun_t func_;
    ThreadData tdata_;
    pthread_t tid_;
};

udp_client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include "thread.hpp"

uint16_t serverport = 0;
std::string serverip;

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

static void *udpSend(void *args)
{
    int sock = *(int *)((ThreadData *)args)->args_;
    std::string name = ((ThreadData *)args)->name_;

    std::string message;
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    while (true)
    {
        std::cerr << "请输入你的信息# "; //标准错误 2打印
        std::getline(std::cin, message);
        if (message == "quit")
            break;
        // 当client首次发送消息给服务器的时候,OS会自动给client bind他的IP和PORT
        // sendto表示发送报文
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);
    }
    return nullptr;
}

static void *udpRecv(void *args)
{
    int sock = *(int *)((ThreadData *)args)->args_;
    std::string name = ((ThreadData *)args)->name_;

    char buffer[1024];
    while (true)
    {
        memset(buffer, 0, sizeof(buffer));
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        // recvfrom接收解决报文
        ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout  << buffer << std::endl;
        }
    }
}

// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    serverport = atoi(argv[2]);
    serverip = argv[1];

    std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void *)&sock));
    std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void *)&sock));
    // sender->name();
    sender->start();
    recver->start();

    sender->join();
    recver->join();

    close(sock);
    return 0;
}

udp_server.cc

#include "udp_server.hpp"
#include <memory>
#include <cstdlib>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->initServer();
    svr->Start();
    return 0;
}

udp_server.hpp

#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP

#include "log.hpp"
#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <queue>

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = "") : _port(port), _ip(ip), _sock(-1)
    {
    }
    bool initServer()
    {
        //  1. 创建套接字
        _sock = socket(AF_INET, SOCK_DGRAM, 0); 
        if (_sock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        // 2. bind: 将用户设置的ip和port在内核中和我们当前的进程强关联
        struct sockaddr_in local;
        bzero(&local, sizeof(local));// 清0
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);// 转换
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());// 转换
        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "init udp server done ... %s", strerror(errno));
        return true;
    }
    void Start()
    {
        char buffer[SIZE];
        for (;;)
        {
            // 注意:
            //  peer,纯输出型参数
            struct sockaddr_in peer;// 输出型参数
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);
            char result[256];
            char key[64];
            std::string cmd_echo;
            // recvfrom读取报文
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                buffer[s] = 0; // 我们目前数据当做字符串
                uint16_t cli_port = ntohs(peer.sin_port);// 转换
                std::string cli_ip = inet_ntoa(peer.sin_addr);// 转换
                snprintf(key, sizeof(key), "%s-%u", cli_ip.c_str(), cli_port); // 127.0.0.1-8080
                logMessage(NORMAL, "key: %s", key);
                auto it = _users.find(key);
                if (it == _users.end())
                {
                    // 不存在插入
                    logMessage(NORMAL, "add new user : %s", key);
                    _users.insert({key, peer});
                }
            }
            for (auto &iter : _users)
            {
                std::string sendMessage = key;
                sendMessage += "# ";
                sendMessage += buffer; // 127.0.0.1-1234# 你好
                logMessage(NORMAL, "push message to %s", iter.first.c_str());
                sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));
            }
        }
    }
    ~UdpServer()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    // 一个服务器,一般必须需要ip地址和port(16位的整数)
    uint16_t _port;
    std::string _ip;
    int _sock;
    std::unordered_map<std::string, struct sockaddr_in> _users;
    std::queue<std::string> messageQueue;
};

#endif

 

 

  • netstat -antp// 查看网络进程服务 
  •  无论是多线程读还是写,用的sock都是一个,
  • sock代表的就是文件,udp是全双工的,可以同时进行收发而不受干扰

 3.4 使用网络在两台主机之间进行通信(多进程-父子进程):tcp实现

tcp_server.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"

static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
    //echo server
    char buffer[1024];
    while(true)
    {
        // read && write 可以直接被使用!
        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0; //将发过来的数据当做字符串
            std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
        }
        else if(s == 0) //对端关闭连接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else{ // 
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }

        write(sock, buffer, strlen(buffer));
    }
}

class TcpServer
{
private:
    const static int gbacklog = 20;
public:
    TcpServer(uint16_t port, std::string ip=""):listensock(-1), _port(port), _ip(ip)
    {}
    void initServer()
    {
        // 1. 创建socket -- 进程和文件
        listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", listensock); // 3

        // 2. bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if(bind(listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. 因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
        if(listen(listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");
    }
    void start()
    {
        // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
        signal(SIGCHLD, SIG_IGN); 
        while(true)
        {
            // sleep(1);
            // 4. 获取连接
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            int servicesock = accept(listensock, (struct sockaddr*)&src, &len);
            if(servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue;
            }
            // 获取连接成功了
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",\
                servicesock, client_ip.c_str(), client_port);
            
            // 开始进行通信服务啦
            // 多进程版 --- 创建子进程
            pid_t id = fork();
            assert(id != -1);
            if(id == 0)
            {
                // 子进程是来进行提供服务的,需不需要知道监听socket呢?
                close(listensock);
                service(servicesock, client_ip, client_port);
                exit(0); // 僵尸状态
            }
            close(servicesock); // 如果父进程关闭servicesock,会不会影响子进程??
        }
    }
    ~TcpServer(){}
private:
    uint16_t _port;
    std::string _ip;
    int listensock;// 监听套接字
};

 tcp_server.cc

#include "tcp_server.hpp"
#include <memory>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    svr->initServer();
    svr->start();
    return 0;
}

 

 

  •  telnet ip 端口号// 创建临时客户端
  • while :; do ps ajx | grep tcp-server;echo "####";sleep 1;done

tcp_client.cc

 

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

void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

// ./tcp_client targetIp targetPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    bool alive = false;
    int sock = 0;
    std::string line;
    while (true) // TODO
    {
        if (!alive)
        {
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock < 0)
            {
                std::cerr << "socket error" << std::endl;
                exit(2);
            }
            // client 要不要bind呢?不需要显示的bind,但是一定是需要port
            // 需要让os自动进行port选择
            // 连接别人的能力!
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());

            if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
            {
                std::cerr << "connect error" << std::endl;
                exit(3); // TODO
            }
            std::cout << "connect success" << std::endl;
            alive = true;
        }
        std::cout << "请输入# ";
        std::getline(std::cin, line);
        if (line == "quit")
            break;

        ssize_t s = send(sock, line.c_str(), line.size(), 0);
        if (s > 0)
        {
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server 回显# " << buffer << std::endl;
            }
            else if (s == 0)
            {
                alive = false;
                close(sock);
            }
        }
        else
        {
            alive = false;
            close(sock);
        }
    }

    return 0;
}
  • 客户端需要具有连接能力,是通过connect实现的

 4. 模拟实现网络版计算器

  1. 序列化反序列化
  2. 定制自己的协议
  3. 将我们的服务守护进程化,让他变成一个网络服务

CalClient.cc

#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"

using namespace ns_protocol;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " serverIp serverPort\n"
              << std::endl;
}

// ./client server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();
    if (!sock.Connect(sockfd, server_ip, server_port))
    {
        std::cerr << "Connect error" << std::endl;
        exit(2);
    }
    bool quit = false;
    std::string buffer;
    while (!quit)
    {
        // 1. 获取需求
        Request req;
        std::cout << "Please Enter # ";
        std::cin >> req.x_ >> req.op_ >> req.y_;

        // 2. 序列化
        std::string s = req.Serialize();
        // std::string temp = s;
        // 3. 添加长度报头
        s = Encode(s);
        // 4. 发送给服务端
        Send(sockfd, s);

        // 5. 正常读取
        while (true)
        {
            bool res = Recv(sockfd, &buffer);
            if (!res)
            {
                quit = true;
                break;
            }
            std::string package = Decode(buffer);
            if (package.empty())
                continue;
            Response resp;
            resp.Deserialized(package);
            std::string err;
            switch (resp.code_)
            {
            case 1:
                err = "除0错误";
                break;
            case 2:
                err = "模0错误";
                break;
            case 3:
                err = "非法操作";
                break;
            default:
                std::cout << resp.x_ << resp.op_ << resp.y_ << " = " << resp.result_ << " [success]" << std::endl;
                break;
            }
            if(!err.empty()) std::cerr << err << std::endl;
            // sleep(1);
            break;
        }
    }
    close(sockfd);
    return 0;
}

CalServer.cc

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Daemon.hpp"
#include <memory>
#include <signal.h>

using namespace ns_tcpserver;
using namespace ns_protocol;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " port\n"
              << std::endl;
}

static Response calculatorHelper(const Request &req)
{
    Response resp(0, 0, req.x_, req.y_, req.op_);
    switch (req.op_)
    {
    case '+':
        resp.result_ = req.x_ + req.y_;
        break;
    case '-':
        resp.result_ = req.x_ - req.y_;
        break;
    case '*':
        resp.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (0 == req.y_)
            resp.code_ = 1;
        else
            resp.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (0 == req.y_)
            resp.code_ = 2;
        else
            resp.result_ = req.x_ % req.y_;
        break;
    default:
        resp.code_ = 3;
        break;
    }
    return resp;
}

void calculator(int sock)
{
    std::string inbuffer;
    while (true)
    {
        // 1. 读取成功
        bool res = Recv(sock, &inbuffer); // 在这里我们读到了一个请求?
        if (!res)
            break;
        // std::cout << "begin: inbuffer: " << inbuffer << std::endl;
        
        // 2. 协议解析,保证得到一个完整的报文
        std::string package = Decode(inbuffer);
        if (package.empty())
            continue;
        // std::cout << "end: inbuffer: " << inbuffer << std::endl;
        // std::cout << "packge: " << package << std::endl;
        logMessage(NORMAL, "%s", package.c_str());
        
        // 3. 保证该报文是一个完整的报文
        Request req;
        
        // 4. 反序列化,字节流 -> 结构化
        req.Deserialized(package); // 反序列化
        
        // 5. 业务逻辑
        Response resp = calculatorHelper(req);
        
        // 6. 序列化
        std::string respString = resp.Serialize(); // 对计算结果进行序列化
        
        // 7. 添加长度信息,形成一个完整的报文
        // "length\r\ncode result\r\n"
        // std::cout << "respString: " << respString << std::endl;
        respString = Encode(respString);
        // std::cout << "encode: respString: " << respString << std::endl;
        
        // 8. send这里我们暂时先这样写,多路转接的时候,我们再来谈发送的问题
        Send(sock, respString);
    }
}

// ./CalServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 一般经验:server在编写的时候,要有较为严谨性的判断逻辑
    // 一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法写入的问题!
    // signal(SIGPIPE, SIG_IGN);
    MyDaemon();
    std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();
    // Request req(123, 456, '+');
    // std::string s = req.Serialize();
    // std::cout << s << std::endl;

    // Request temp;
    // temp.Deserialized(s);
    // std::cout << temp.x_ << std::endl;
    // std::cout << temp.op_ << std::endl;
    // std::cout << temp.y_ << std::endl;
    return 0;
}

Daemon.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 设置守护进程
void MyDaemon()
{
    // 1. 忽略信号,SIGPIPE,SIGCHLD
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 2. 不要让自己成为组长
    if (fork() > 0)
        exit(0);
    // 3. 调用setsid
    setsid();
    // 4. 标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息
    int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
    if(devnull > 0)
    {
        dup2(0, devnull);
        dup2(1, devnull);
        dup2(2, devnull);
        close(devnull);
    }
}

Log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./calculator.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    FILE *fp = fopen(LOGFILE, "a");
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

Makefile

.PHONY:all
all:client CalServer

client:CalClient.cc
	g++ -o $@ $^ -std=c++11
CalServer:CalServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f client CalServer

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
// #include <jsoncpp/json/json.h>

namespace ns_protocol
{
#define MYSELF 0

#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof!

    class Request
    {
    public:
        // 1. 自主实现 "length\r\nx_ op_ y_\r\n"
        // 2. 使用现成的方案
        // 序列化
        std::string Serialize()
        {
#ifdef MYSELF
            // 使用自己定义的方式
            std::string str;
            str = std::to_string(x_);
            str += SPACE;
            str += op_; // TODO
            str += SPACE;
            str += std::to_string(y_);
            return str;
#else
            Json::Value root;
            root["x"] = x_;
            root["y"] = y_;
            root["op"] = op_;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "x_ op_ y_"
        // "1234 + 5678"
        // 反序列化
        bool Deserialized(const std::string &str)
        {
#ifdef MYSELF
            std::size_t left = str.find(SPACE);
            if (left == std::string::npos)
                return false;
            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
                return false;
            x_ = atoi(str.substr(0, left).c_str());
            y_ = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
                return false;
            else
                op_ = str[left + SPACE_LEN];
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);
            x_ = root["x"].asInt();
            y_ = root["y"].asInt();
            op_ = root["op"].asInt();
            return true;
#endif
        }

    public:
        Request()
        {
        }
        Request(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }
        ~Request() {}

    public:
        int x_;   // 是什么?
        int y_;   // 是什么?
        char op_; // '+' '-' '*' '/' '%'
    };

    class Response
    {
    public:
        // "code_ result_"
        // 结果序列化
        std::string Serialize()
        {
#ifdef MYSELF
            std::string s;
            s = std::to_string(code_);
            s += SPACE;
            s += std::to_string(result_);
            return s;
#else
            Json::Value root;
            root["code"] = code_;
            root["result"] = result_;
            root["xx"] = x_;
            root["yy"] = y_;
            root["zz"] = op_;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "111 100"
        // 结果反序列化
        bool Deserialized(const std::string &s)
        {
#ifdef MYSELF
            std::size_t pos = s.find(SPACE);
            if (pos == std::string::npos)
                return false;
            code_ = atoi(s.substr(0, pos).c_str());
            result_ = atoi(s.substr(pos + SPACE_LEN).c_str());
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(s, root);
            code_ = root["code"].asInt();
            result_ = root["result"].asInt();
            x_ =  root["xx"].asInt();
            y_ =  root["yy"].asInt();
            op_ =  root["zz"].asInt();
            return true;
#endif
        }

    public:
        Response()
        {
        }
        Response(int result, int code, int x, int y, char op) 
        : result_(result), code_(code), x_(x), y_(y), op_(op)
        {
        }
        ~Response() {}

    public:
        int result_; // 计算结果
        int code_;   // 计算结果的状态码

        int x_;
        int y_;
        char op_;
    };

    // 临时方案
    // 调整方案2: 我们期望,你必须给我返回一个完整的报文
    bool Recv(int sock, std::string *out)
    {
        // UDP是面向数据报:
        // TCP 面向字节流的:
        // recv : 你怎么保证,你读到的inbuffer,是一个完整完善的请求呢?不能保证
        // "1234 + 5678" : 1234 +
        // "1234 + 5678" : 1234 + 5678 123+99
        // "1234 "
        // 必须是:"1234 + 5678"
        // 单纯的recv是无法解决这个问题的,需要对协议进一步定制!
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123+789\r\n
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if (s == 0)
        {
            // std::cout << "client quit" << std::endl;
            return false;
        }
        else
        {
            // std::cout << "recv error" << std::endl;
            return false;
        }
        return true;
    }

    void Send(int sock, const std::string str)
    {
        // std::cout << "sent in" << std::endl;
        int n = send(sock, str.c_str(), str.size(), 0);
        if (n < 0)
            std::cout << "send error" << std::endl;
    }
    // "length\r\nx_ op_ y_\r\n..." // 10\r\nabc
    // "x_ op_ y_\r\n length\r\nXXX\r\n"
    // 反序列化(进一步)
    std::string Decode(std::string &buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if(pos == std::string::npos) return "";
        int size = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - 2*SEP_LEN;
        if(surplus >= size)
        {
            //至少具有一个合法完整的报文, 可以动手提取了
            buffer.erase(0, pos+SEP_LEN);
            std::string s = buffer.substr(0, size);
            buffer.erase(0, size + SEP_LEN);
            return s;
        }
        else
        {
            return "";
        }
    }
    // "XXXXXX"
    // "123\r\nXXXXXX\r\n"
    // 序列化(进一步)
    std::string Encode(std::string &s)
    {
        std::string new_package = std::to_string(s.size());
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }

}

Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"

// 封装一下套接字 TCP
class Sock
{
private:
    const static int gbacklog = 20;
public:
    Sock() {}
    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);// 监听套接字 
        if (listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", listensock);
        return listensock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);// 端口转换
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);// 端口转换
        
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }

    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);// 转移
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }

    bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

TcpServer.hpp

#pragma once

#include "Sock.hpp"
#include <vector>
#include <functional>
#include <pthread.h>

namespace ns_tcpserver
{
    using func_t = std::function<void(int)>;// 包装器 - 包装的是一个函数

    class TcpServer;

    // 这个更像是一个结构体
    class ThreadData
    {
    public:
        ThreadData(int sock, TcpServer *server):sock_(sock), server_(server)
        {}
        ~ThreadData() {}
    public:
        int sock_;
        TcpServer *server_;
    };

    class TcpServer
    {
    private:
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData *td = static_cast<ThreadData *>(args);
            td->server_->Excute(td->sock_);
            close(td->sock_);
            return nullptr;
        }

    public:
        TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
        {
            // 初始化
            listensock_ = sock_.Socket();
            sock_.Bind(listensock_, port, ip);
            sock_.Listen(listensock_);
        }

        // 新增
        void BindService(func_t func) 
        { 
            func_.push_back(func);
        }

        // 执行
        void Excute(int sock)
        {
            for(auto &f : func_)
            {
                f(sock);
            }
        }

        void Start()
        {
            for (;;)
            {
                std::string clientip;
                uint16_t clientport;
                int sock = sock_.Accept(listensock_, &clientip, &clientport);
                if (sock == -1)
                    continue;
                logMessage(NORMAL, "create new link success, sock: %d", sock);
                // 创建进程
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, this);
                pthread_create(&tid, nullptr, ThreadRoutine, td);
            }
        }
        
        ~TcpServer()
        {
            if (listensock_ >= 0)
                close(listensock_);// 就像关闭文件描述符一样
        }

    private:
        int listensock_;
        Sock sock_;
        std::vector<func_t> func_;
    };
}

 

 

4.1 关于守护进程 

全都是在前台运行的 

  • 前台进程: 和终端关联的进程,前台进程
  • 任何xshell登录,只允许一个前台进程和多个后台进程
  • 进程除了有自己的pid,ppid,还有一个组ID
  • 在命令行中,同时有管道启动多个进程,多个进程是兄弟关系,父进程都是bash->可以用来匿名管道来通信
  • 而被创建的多个进程可一个成为一个进程组的概念,组长一般是第一个进程
  • 任何一次登陆,登陆的用户,需要有多个进程(组),来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组,
    • 我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的
  •  通过setsid()可以将自己变成自成会话
  • 通过fork()可以保证自己不是进程组的组长,
  • 守护进程不能直接向显示器打印消息,一旦打印,就会被暂停,终止

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《计算机应用基础》中的微课教学 本文从网络收集而来,上传到平台为了帮到更多的人,如果您需要使用本文档,请点击 下载按钮下载本文档(有偿下载),另外祝您生活愉快,工作顺利,万事如意!   随着科技的不断发展,我们已经悄然进入"微"时代,在教育领域中,微课的出现也 带领着职业教育进入了一个全新的时代,本文旨在探讨中职《计算机应用基础》课程教学 的现状及微课在该课程教学中的应用。      0 引言      随着科技的进步和教育技术的不断发展,现代教育领域也在快速步入"微"时代,近 些年,微视频、微课堂、慕课等纷纷出现。 短小精悍、资源丰富、传播途径多、速度快、范围广是"微"信息的主要特点。"微"时代 带给了我们一些全新的教育模式和理念:将一节课的精华部分,比如某个教学点(一般是 重点、难点或是疑点) 录制成10分钟以内的简短视频,学生利用课堂或业余时间通过微课平台来点播视频进行 学习,同时学生借助网络传播也可下载、分享和互动。这种微课的模式能在短时间内抓 住学习者的学习兴趣,提高学习者的学习效率。本文将以《计算机应用基础》课程为例, 分析探讨微课在该课程中的应用。      1 中职《计算机应用基础》课程的教学现状      《计算机应用基础》课程是一门中职学生必修的文化基础课,面向的对象是所有中职 一年级的学生。大多数的中职学生都不具备主动学习的习惯,大多缺乏学习兴趣,基础 也薄弱。而《计算机应用基础》则是一门操作性和逻辑性强的课程。目前该课程在中职学 校大多采用大班教学和实训上机相结合的讲授模式,讲授也大部分采用的是任务驱动教 学法或项目教学法等。学生由于基础薄弱,在学习中学生在课堂学习和实训过程中或是 课余自学的过程中如果遇到问题时,由于教师和学生之间还做不到及时地沟通,这些问 题往往得不到及时地解决。      2 什么是微课      2008年秋,美国新墨西哥州圣胡安学院高级教学设计师戴维·彭罗斯(David Penrose)首次提出了"微课(Micro- Lesson)"这一概念。在国内,广东佛山市教育信息中心主任胡铁生首先提出这一概念, 并于2010年在佛山市教育局信息网络中心首创微课学习平台,引起强烈反响。微课就是 根据学习者的学习规律,将课程分解成为一系列具有目标、任务、方法、资源、作业、 互动与反思等在内的微型课程体系。主要方式是制作播放长度只有几分钟的一段"片段化 "教学视频。目前网络技术特别是移动互联网技术已经相当普及,在校中职生可以根据自 己的实际学习情况通过学校局域网络或是移动互联网络进行个性化化学习,移动学习, 有目的地学习,反复学习。微课的最主要特点就是短小精悍、目标明确、资源丰富、易 于交互。      3 微课的应用特点       趣味教学,提高效率      计算机课程往往逻辑性较强,比如一个实例,教师先讲授一遍操作步骤,学生在自 己动手操作的过程中,很容易出现的一种情况就是记住了这一步却忘记了下一步该做什 么。而使用微课教学可以很方便地解决这一问题。一节制作完整而精美,并且充满趣味 性的微课视频能使学生在观看的时候集中注意力,提高兴趣,如有遗忘可以反复来播放 视频加深记忆。微课能突出教学重点,基于一个核心问题来编排,没有烦琐的理论,突 出实际应用,突出操作技能,实效性高,提高了学习效率。       增加互动,促进交流      和传统教学的师生交流欠缺相反,微课的交互性能使学生自主选择学习内容和学习 方式,有的放矢。微课的互动交流区还可使学生和教师之间,学生和学生之间,教师和 教师之间都能建立良好的互动关系,及时做到交流和沟通。制作完整的微课系统不仅具 备丰富的学习资源,还应该有课后反思,课下作业,互动分享等内容,使学生遇到问题 时能得到教师或者其他同学的及时解决。       延伸课堂,随时学习      目前,中职生中电脑终端和移动终端也比较普及,另外,现在有越来越多的学校正 在实现无线网络的全覆盖,这些都为微课的应用提供了良好的保障条件。学生甚至能够 利用课余或者其他的零散时间随时随地进行学习。微课能使我们的传统课堂得到延伸, 也符合了当前国家教育部门提倡的移动学习、自主学习的新型学习理念。      4 制作《计算机应用基础》微课的要点       制作工具的选择      制作微课,特别是录制微课视频时,需要专业设备的同时,还应有专业的技术支持 。这些装备和技术包括高配置的电脑、耳麦或话筒,还需要有专业的视频录像软件,比 如Camtasia Studio和ppt软件等。目前,已有越来越多的中职学校开展了对专职教师的Camtasia Studio软件的使用培训,对于计算机教师来说,则更易于上手。       微课视频的设计      微课视频的设计是微课的核心所在。微课视频一

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值