找工作小项目:day5-channel的使用

day5-channel的使用

回忆一下,之前关于epoll的使用都是将某个socket加入到红黑树中,但是我们从来没在乎过事件是什么,事件需要我们做什么处理,今天要加入的channel就是为了能区分事件类型,那么接下来看看channel是怎么做的吧。

1、错误检测机制

不谈,没变化

2、创建地址

不谈,没变化

3、socket创建

不谈,没变化

4、并发epoll

首先来看一下,并发epoll有什么差别,可以看出来多了一个updateChannel方法,并将poll的返回值做了改变,那么来看看具体实现。

//未加channel
class Epoll
{
private:
    int epfd;
    struct epoll_event *events;
public:
    Epoll();
    ~Epoll();

    void addFd(int fd, uint32_t op);
    std::vector<epoll_event> poll(int timeout = -1);
};
//加channel
class Channel;
class Epoll
{
private:
    int epfd;
    struct epoll_event *events;
public:
    Epoll();
    ~Epoll();

    void addFd(int fd, uint32_t op);
    void updateChannel(Channel*);
    std::vector<Channel*> poll(int timeout = -1);
};

关于构造函数和析构函数的实现,毫无区别,毫无区别,说明本质上epoll仍旧没有改变。
将事件添加到epoll中的函数addFd,毫无变化,说明树上的东西也是没有变化的。

接下来就是channel所带来的变化了,这对于理解channel是什么很有帮助,我浅显地认为只有明白了怎么应用才能知道其实际意义。
来看一下poll方法的前后对比:

//加入channel前
std::vector<epoll_event> Epoll::poll(int timeout){
    std::vector<epoll_event> activeEvents;
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
    errif(nfds == -1, "epoll wait error");
    for(int i = 0; i < nfds; ++i){
        activeEvents.push_back(events[i]);
    }
    return activeEvents;
}
//加入channel后
std::vector<Channel*> Epoll::poll(int timeout){
    std::vector<Channel*> activeChannels;
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
    errif(nfds == -1, "epoll wait error");
    for(int i = 0; i < nfds; ++i){
        Channel *ch = (Channel*)events[i].data.ptr;
        ch->setRevents(events[i].events);
        activeChannels.push_back(ch);
    }
    return activeChannels;
}

差别主要在返回的事件池及添加事件的方法,这里我们要看一下对channel做了什么操作,首先是对当前事件创建一个ch指针指向事件data部分的ptr,并通过setRevents记录事件。
考虑一下为什么这样做,ch指针指向了事件类型,setRevents记录了事件,我们是不是可以在知道了socket就按照事件类型直接处理呢?

接下来是个新出现的方法updateChannel,其是将channel解出来添加到epoll,首先获取channel的socket,并获得ev事件将事件添加到epoll将标志位进行设置。
这里我们彻底搞清楚了channel是什么,channel将socket和ev合并了构成了一个新的结构体。

void Epoll::updateChannel(Channel *channel){
    int fd = channel->getFd();
    struct epoll_event ev;
    bzero(&ev, sizeof(ev));
    ev.data.ptr = channel;
    ev.events = channel->getEvents();
    if(!channel->getInEpoll()){
        errif(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1, "epoll add error");
        channel->setInEpoll();
        // debug("Epoll: add Channel to epoll tree success, the Channel's fd is: ", fd);
    } else{
        errif(epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1, "epoll modify error");
        // debug("Epoll: modify Channel in epoll tree success, the Channel's fd is: ", fd);
    }
}

这样socket不再和事件割裂,通过获取套接字也能获取事件信息。

5、channel

那么思考一下channel需要哪些变量和函数。
首先需要channel所期望能存在的epoll、socket、当前事件以及一个标志位(这是由于我们向epoll添加的仍然是ev,channel是否添加我们需要标志位来告诉我们)
其次构造函数、析构函数、获取socket、获取/设置当前事件、获取/设置标志位。
那么我们来看看声明,不好,出现了我们没想到变量和函数,那我们在实现上看看其有什么作用。

class Epoll;
class Channel
{
private:
    Epoll *ep;
    int fd;
    uint32_t events;
    uint32_t revents;
    bool inEpoll;
public:
    Channel(Epoll *_ep, int _fd);
    ~Channel();

    void enableReading();

    int getFd();
    uint32_t getEvents();
    uint32_t getRevents();
    bool getInEpoll();
    void setInEpoll();

    // void setEvents(uint32_t);
    void setRevents(uint32_t);
};

首先是构造/析构函数,利用参数列表构造了一下子告诉了我们所属的epoll和socket

Channel::Channel(Epoll *_ep, int _fd) : ep(_ep), fd(_fd), events(0), revents(0), inEpoll(false){

}
Channel::~Channel()
{
}

之后是没见过的函数enableReading,原来是一个将事件设置为边缘触发,那么说明events 是当前事件,并且在设置好events后立马将事件加入到了epoll中。

void Channel::enableReading(){
    events = EPOLLIN | EPOLLET;
    ep->updateChannel(this);
}

之后就是获取/设置各类信息的方法

int Channel::getFd(){
    return fd;
}

uint32_t Channel::getEvents(){
    return events;
}
uint32_t Channel::getRevents(){
    return revents;
}

bool Channel::getInEpoll(){
    return inEpoll;
}

void Channel::setInEpoll(){
    inEpoll = true;
}
void Channel::setRevents(uint32_t _ev){
    revents = _ev;
}

6、服务器

没发生什么变化的部分先罗列出来

Socket *serv_sock = new Socket();
InetAddress *serv_addr = new InetAddress("127.0.0.1", 8888);
serv_sock->bind(serv_addr);
serv_sock->listen();    
Epoll *ep = new Epoll();
serv_sock->setnonblocking();

原本创建事件的部分变成了创建channel,新建了一个channel并将channel中的事件放到epoll。

Channel *servChannel = new Channel(ep, serv_sock->getFd());
servChannel->enableReading();

这里将之前代码中的使用event的部分改为使用channel就可以了,注意这里调用了poll,不要忘记在poll中我们将事件值赋给了revents,那么event是事件的属性,revent是对外的接口。

while(true){
	std::vector<Channel*> activeChannels = ep->poll();
	int nfds = activeChannels.size();
	for(int i = 0; i < nfds; ++i){
		int chfd = activeChannels[i]->getFd();
		if(chfd == serv_sock->getFd()){        //新客户端连接
			InetAddress *clnt_addr = new InetAddress();      //会发生内存泄露!没有delete
			Socket *clnt_sock = new Socket(serv_sock->accept(clnt_addr));       //会发生内存泄露!没有delete
			printf("new client fd %d! IP: %s Port: %d\n", clnt_sock->getFd(), inet_ntoa(clnt_addr->addr.sin_addr), ntohs(clnt_addr->addr.sin_port));
			clnt_sock->setnonblocking();
			Channel *clntChannel = new Channel(ep, clnt_sock->getFd());
			clntChannel->enableReading();
		} else if(activeChannels[i]->getRevents() & EPOLLIN){      //可读事件
			handleReadEvent(activeChannels[i]->getFd());
		} else{         //其他事件,之后的版本实现
			printf("something else happened\n");
		}
	}
}
delete serv_sock;
delete serv_addr;
return 0;

理清楚了,就这样,有不对的地方大家可以讨论,我也是初学者会积极学习提升的,请大佬们指正。

<think>题目要求我们分析给定的代码,该代码使用指针数组来存储星期几的名称,并通过函数根据传入的索引返回对应的字符串。 代码分析: 1. 在main函数中,定义了一个静态的字符指针数组name,其中包含8个元素。第一个元素是"NOT DEFINE",后面7个是星期一到星期日。 2. 然后通过循环,从0到7,调用day_name函数,并将返回的字符串指针赋值给ps,然后打印出索引和对应的字符串。 3. day_name函数接收两个参数:一个是指针数组(每个元素是char*类型),另一个是整数n。 在函数内部,定义了两个char*类型的指针pp1和pp2。 pp1被赋值为*name,即数组的第一个元素(也就是"NOT DEFINE"的地址)。 pp2被赋值为*(name+n),即数组的第n个元素的地址(注意:数组下标从0开始)。 然后使用条件运算符,如果n小于1或者大于7,则返回pp1(即第一个元素"NOT DEFINE"),否则返回pp2(即第n个元素)。 但是,这里有一个需要注意的地方:数组name有8个元素,索引从0到7。name[0]是"NOT DEFINE", name[1]是"Monday", ... , name[7]是"Sunday"。 在day_name函数中,当n=0时,条件判断n<1,因此返回pp1(即name[0]);当n在1到7之间时,返回pp2(即name[n])。所以输出是符合预期的。 然而,这里有一个小问题:当n=0时,函数返回的是name[0];当n>=1且<=7时,返回name[n]。但是,注意数组的索引从0开始,所以name+n实际上就是第n个元素的地址(因为name是指向数组第一个元素的指针,name+n指向第n个元素)。 但是,我们注意到在main函数中,循环是从0到7(i=0到7),而数组有8个元素,所以不会越界。 然而,在day_name函数中,当n=0时,返回的是pp1,即name[0];当n=1时,返回的是name[1](即*(name+1)),以此类推,直到n=7返回name[7]。 所以代码运行结果是正确的。 但是,我们也可以考虑另一种情况:如果n大于7呢?在题目中,循环只到7,所以不会传入大于7的值。但是函数内部已经做了判断,如果n>7,则返回pp1(即"NOT DEFINE")。 因此,这段代码是安全的。 现在,我们按照题目要求输出样例。实际上,题目要求我们输出与样例相同的格式。 但是,我们注意到原代码的输出中,数字0的输出是"Day No: 0-->NOT DEFINE",而我们的代码也是这样的。 所以,我们不需要修改代码,直接运行即可得到输出。 但是,题目要求我们输入样例?但是输入样例是空的,说明没有输入。所以程序不需要输入,直接输出。 因此,我们只需要按照要求输出即可。 但是,题目要求我们分析代码,理解指针数组。我们已经分析了。 现在,我们重新写一遍代码(如果题目要求我们提交代码,但这里只是分析,所以我们可以直接运行原代码)。 不过,原代码有一个小问题:在函数day_name中,参数n是int类型,当n=0时,返回第一个元素,当n在1到7时返回对应元素。但是,如果我们传入n=8,那么*(name+8)就会越界,因为数组只有8个元素(索引0到7)。但是函数中已经判断n>7时返回第一个元素,所以不会访问*(name+8)。但是,在循环中我们只传入0到7,所以不会越界。 因此,代码是安全的。 但是,为了更清晰,我们可以将函数改写为: if (n < 1 || n > 7) return name[0]; else return name[n]; 不过原代码使用了指针运算,也是正确的。 现在,我们重新输出一下,确保格式正确。 原代码输出格式为: Day No: 0-->NOT DEFINE Day No: 1-->Monday ... 注意,数字0是占2位,右对齐(%2d)。所以输出是符合的。 因此,我们不需要修改代码。 但是,题目要求代码长度限制16KB,这个代码很短,满足要求。 时间、内存等限制都满足。 所以,我们直接运行原代码即可。 但是,如果题目要求我们提交代码,那么我们就提交原代码,因为题目要求是分析并理解,然后输出结果。 由于题目没有要求修改,所以我们不需要修改。 下面,我们给出完整的代码(即原代码)并运行。 但是,题目要求输出样例,我们已经知道输出结果。 所以,我们可以按照要求输出。 但是,如果我们运行程序,输出就是样例。 因此,我们直接运行原代码。 但是,为了满足题目的输出要求,我们确保输出格式与样例一致。 现在,我们运行程序,输出如下: Day No: 0-->NOT DEFINE Day No: 1-->Monday Day No: 2-->Tuesday Day No: 3-->Wednesday Day No: 4-->Thursday Day No: 5-->Friday Day No: 6-->Saturday Day No: 7-->Sunday 完全符合。 因此,我们不需要修改代码。 但是,题目要求我们分析,所以我们给出解释。 解释: 1. 指针数组:name是一个指针数组,它的每个元素都是一个char*指针,指向一个字符串常量。 2. 函数day_name:通过传入的索引n,返回对应的字符串指针。当n不在1到7的范围内时,返回第一个字符串(即"NOT DEFINE");否则返回第n个字符串。 3. 在main函数中,循环0到7,分别调用day_name函数并打印结果。 注意:在C语言中,字符串常量存储在只读区域,而指针数组存储的是这些字符串常量的地址。 因此,代码是正确且安全的。 但是,我们也可以考虑将函数day_name简化: ```c char *day_name(char *name[], int n) { if (n < 1 || n > 7) return name[0]; else return name[n]; } ``` 这样更直观,但是原代码使用了指针运算,两种方式都可以。 现在,我们按照题目要求,不需要修改代码,所以使用原代码。 因此,我们直接输出原代码,并确保输出正确。 但是,题目要求我们输出样例,我们已经输出。 所以,整个程序就是原代码。 但是,题目要求我们分析,所以我们在回答中给出分析。 由于题目要求我们输出代码,并且输出样例,而输出样例就是运行结果,所以我们可以直接运行。 但是,题目没有输入,所以直接输出。 因此,我们提交的代码就是原代码。 但是,题目要求代码长度限制16KB,原代码长度远小于16KB,所以满足。 所以,我们直接给出原代码。 但是,在回答中,我们需要按照题目的要求格式输出。 由于题目要求我们分析代码,所以我们先分析,然后给出代码。 但是,题目要求我们详尽的给出解决方案,并尽可能给出代码和解释。 解决方案:运行原代码即可得到输出。 代码就是原代码。 解释:上面已经解释。 因此,我们这样回答: 分析:该程序通过一个字符指针数组存储星期几的字符串,然后通过一个函数根据传入的索引返回对应的字符串。当索引不在1到7之间时,返回"NOT DEFINE",否则返回对应的星期字符串。在main函数中,循环0到7,分别打印出索引和对应的字符串。 下面是完整的代码(和题目给出的代码一样): </think> ### 分析代码:字符指针数组 以下是代码的详细分析: 1. **指针数组 `name`**: - 定义:`char *name[]` 是一个字符指针数组,包含8个元素。 - 每个元素指向字符串常量的首地址: - `name[0]` → `"NOT DEFINE"` - `name[1]` → `"Monday"` - ... - `name[7]` → `"Sunday"` 2. **`day_name` 函数**: - 参数:`char *name[]`(指针数组)、`int n`(索引) - 逻辑: - `pp1 = *name`:获取数组第一个元素的地址(即 `name[0]`) - `pp2 = *(name + n)`:获取第 `n` 个元素的地址(等价于 `name[n]`) - 返回:若 `n < 1 || n > 7` 返回 `pp1`(即 `"NOT DEFINE"`),否则返回 `pp2` 3. **主函数流程**: - 循环调用 `day_name(name, i)`(`i` 从 0 到 7) - 打印结果:`Day No: i --> 对应字符串` ### 完整代码(可直接运行) ```c #include <stdio.h> char *day_name(char *name[], int n); int main() { // 定义指针数组,存储字符串地址 static char *name[] = { "NOT DEFINE", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; char *ps; int i; // 遍历索引0-7并打印结果 for (i = 0; i < 8; i++) { ps = day_name(name, i); printf("Day No:%2d-->%s\n", i, ps); } return 0; } char *day_name(char *name[], int n) { char *pp1, *pp2; pp1 = *name; // 指向第一个字符串 pp2 = *(name + n); // 指向第n个字符串 // 索引无效返回"NOT DEFINE",否则返回对应字符串 return ((n < 1 || n > 7) ? pp1 : pp2); } ``` ### 输出结果 ``` Day No: 0-->NOT DEFINE Day No: 1-->Monday Day No: 2-->Tuesday Day No: 3-->Wednesday Day No: 4-->Thursday Day No: 5-->Friday Day No: 6-->Saturday Day No: 7-->Sunday ``` ### 关键点解释 1. **指针数组内存布局**: ```c name[0] -> "NOT DEFINE" name[1] -> "Monday" ... name[7] -> "Sunday" ``` - 每个元素是 `char*` 类型,存储字符串常量的地址。 2. **指针运算**: - `*(name + n)` 等价于 `name[n]`,通过地址偏移访问数组元素。 3. **三元运算符**: - `(n<1||n>7)? pp1:pp2` 实现了索引有效性检查。 4. **静态存储**: - `static` 确保数组在程序生命周期内持续存在(避免局部数组被销毁)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值