多核的网络游戏服务器开发 之 网络库(一)

来源:互联网 发布:微信 代理服务器 知乎 编辑:程序博客网 时间:2024/04/28 03:39

处理数千连接的网络服务器,需要一个强大的服务器体系,我们大概可以采用下面几种方式:

  1. 多线程的阻塞方式,每个连接一个线程,阻塞接收和发送数据
  2. 单线程的轮询方式,采用非阻塞的网络接口,轮流查询每个连接是否有数据
  3. 线程池方式,采用某种IO机制,通知特定的连接上是否有数据,或者是否ready,然后由指定的可用线程进行处理

一般而言,采用第三种方式是效率最高的选择,在通知机制上,有跨平台的 select,windows平台下可用的IO完成端口,linux 平台可用的 epoll,freeBSD可用的 kqueue。

无论采用何种通知机制,我们都需要一个缓冲区临时存放发送/接收的数据,因为网络套接字状态,包括是否可用,数据是否发送完成等等,是由网络状况决定的,和应用程序逻辑注定是异步的,也就是说,应用程序逻辑不能等待套接字可用之后再向套接字发送数据,套接字上数据到达之后,也不能立刻打断应用程序的处理。

我们需要一个内部的缓冲区存放发送和接收的数据,应用程序逻辑需要发送的时候,直接调用发送的函数并立刻返回,网络库负责缓存数据并在套接字可用的时候进行发送,应用程序在合适的时候从网络库读取数据并立刻返回。

所以缓冲区就成了我们算法的第一个核心了

对于多个线程可能要同时访问的缓冲区,我们的第一反应是使用互斥量来保护,但是实际上,对于大多数受限制的应用场景来说,锁不是必须的。

首先我们需要一个限制,缓冲区的大小是固定的,对于网络连接来说这个需求是合理的,因为如果缓冲区内部的数据是在不断的写入和取出,如果缓冲区不够,表示太多的数据没有被处理,那么肯定是出现了问题(比如说非法连接的攻击),这种情况下,断开连接往往是一个合理的选择。

其次,写入线程和读取线程是有限的,这个在我们的例子中也能得到满足,因为大多数情况下,一个线程来处理一个网络连接的数据是合理的。

那么,在大小固定,单个写入者和单个读取者的情况下,下面的算法可以不需要锁

struct zbuf {
    int size;                //缓冲区的大小
    int start;                //开始的位置
    int end;                //结束的位置
};

 

使用这个结构描述固定大小的缓冲区,增加数据的时候,仅修改end 变量,取出数据的时候,仅修改start变量,对于单个读和单个写线程的情况,不需要锁。

int zbuf_push(struct zbuf *buf, const char *data, int size) {                            //增加指定大小的缓冲区,适用于单个writer的情况
    int old_end, new_end;
    int length = _zbuf_length(buf);
    if(buf->size - length < size) return -1;
    _zbuf_put(buf, buf->end, (char *)data, size);
    new_end = buf->end + size;
    if(new_end >= buf->size) new_end -= buf->size;
    old_end = buf->end;
    buf->end = new_end;
    return old_end;
}

int zbuf_pop(struct zbuf *buf, char *data, int size) {                                    //弹出指定大小的数据,size为 0 表示弹出全部数据
    int length = _zbuf_length(buf);
    if(length >= size) {
        if(size == 0) size = length;
        _zbuf_get(buf, buf->start, data, size);
        buf->start += size;
        if(buf->start >= buf->size) buf->start -= buf->size;
        return size;
    }
    return 0;
}

其中, _zbuf_length 获取可用数据的长度:

__inline int _zbuf_length(struct zbuf *buf) {
    int length;
    if(buf->end >= buf->start) length = buf->end - buf->start;
    else length = buf->size - buf->end + buf->start;
    return length;
}

_zbuf_put 写入数据,需要处理回绕

void _zbuf_put(struct zbuf *buf, int pos, char *data, int size) {                        //拷贝数据到 zbuf 的 pos 位置处,处理回绕
    int copy_size = buf->size - pos;
    if(copy_size >= size) memcpy(BUF_PTR(buf, pos), data, size);
    else {
        memcpy(BUF_PTR(buf, pos), data, copy_size);
        size -= copy_size;
        memcpy(BUF_PTR(buf, 0), data + copy_size, size);
    }
}

_zbuf_get 拷贝数据(需要处理回绕的情况)

void _zbuf_get(struct zbuf *buf, int pos, char *data, int size) {
    int copy_size = buf->size - pos;
    if(copy_size >= size) memcpy(data, BUF_PTR(buf, pos), size);
    else {
        memcpy(data, BUF_PTR(buf, pos), copy_size);
        size -= copy_size;
        memcpy(data + copy_size, BUF_PTR(buf, 0), size);
    }
}

原创粉丝点击