🚙

💨 💨 💨

×

  • Categories

  • Archives

  • Tags

  • About

也想做一个这样的博客吗?

Posted on 05-22-2016 | In Misc

这是我的博客源码 ,
我修改了很多NexT的代码来对原版 NexT 做了优化, 如下 :

  • 改了NexT的很多地方来优化移动端的表现,
    • header的布局
    • 移动端和PC端的侧边栏更加统一
    • 移动端的文章目录列表现在可以滑动了
  • 重做了本地搜索引擎
    • 现在移动端不会经常无故弹不出键盘了
    • 也不会列出加密文章的内容了
    • 更优雅的过渡动画
  • 添加了headroom支持, 现在有一个可以会自动隐藏的header了, 往下滚一下鼠标则隐藏, 往上则出现
  • 升级到了fancybox3并完成适配, 3更流畅且拥有更多效果
  • 添加了文章加密的支持

CSDN sucks

  • 实在是被CSDN的广告恶心到了
  • 最近CSDN的Markdown的无序列表每一列前面的小黑点都没有了
  • 手机网页版本的CSDN排版全无

所有博客瞬间排版全部变成一坨, 实在是不能忍.

所以才做了这个私人博客.

vector和string的内存分配与使用注意点

Posted on 05-17-2016 | In Misc

增长方式

为了支持快速随机访问 , vector 将元素连续存储一一每个元素紧挨着前一个元素存
储 。

问题

假定容器中元素是连续存储 的, 且容器的大小是可变的 , 考虑 向 vector 或 string中添加元素会发生什么 :
如果没有空间容纳新元素,容器不可能简单地将它添加到内存中其他位置一一因为元素必须连续存储。

容器必须分配新的内存空间来保存己有元素和新元素 , 将已有元素从 旧位置移动到新空 间中, 然后添加新元素,释放旧存储空间 。

如果我们每添加一个新元素, vector 就执行一次这样的内存分配和释放操作 ,性能会慢到不可接受 。

. . .

stl关联容器的特性

Posted on 04-26-2016 | In Misc

概绍

关联容器和顺序容器有着根本的不同 : 关联容器中的元素是按关键字来保存和访问的 。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的 。

关联容器支持高效的关键字查找和访问 。
两个主要的关联容器类型是 :

  • map
  • set

map概绍

map 中 的元素是一些关键字一值 ( key-value )对 : 关键字起到索 引 的作用,值则表示与索引相关联的数据 。
字典则是一个很好的使用 map 的例子, 可以将单词作为关键字,将单词释义作为值 。

set概绍

set 中每个元素只包含一个关键字 : set 支持高效的关键字检查一个给定关键字是否在 set 中 。
例如,在某些文本处理过程中,可以用一个 set 来保存想要忽略的单词。

. . .

线程局部存储

Posted on 04-22-2016 | In Linux

使用全局变量或者静态变量是导致多线程编程中非线程安全的常见原因。在多线程程序中,保障非线程安全的常用手段之一是使用互斥锁来做保护,这种方法带来了并发性能下降,同时也只能有一个线程对数据进行读写。

如果程序中能避免使用全局变量或静态变量,那么这些程序就是线程安全的,性能也可以得到很大的提升。

如果有些数据只能有一个线程可以访问,那么这一类数据就可以使用线程局部存储机制来处理,虽然使用这种机制会给程序执行效率上带来一定的影响,但对于使用锁机制来说,这些性能影响将可以忽略。

还有一种大致相当的编程技术就是使用 线程特有数据(没 线程局部存储 易用, 也没 线程局部存储 高效) ,这将在 线程特有数据 中讨论。

. . .

线程特有数据

Posted on 04-17-2016 | In Linux

在 Linux 系统中使用 C/C++ 进行多线程编程时,我们遇到最多的就是对同一变量的多线程读写问题,大多情况下遇到这类问题都是通过锁机制来处理,但这对程序的性能带来了很大的影响,

当然对于那些系统原生支持原子操作的数据类型来说,我们可以使用原子操作来处理,这能对程序的性能会得到一定的提高。那么对于那些系统不支持原子操作的自定义数据类型,

在不使用锁的情况下如何做到线程安全呢?本文将从线程特有数据方面,简单讲解处理这一类线程安全问题的方法。

如果有些数据只能有一个线程可以访问,那么这一类数据就可以使用线程特有数据机制来处理,虽然使用这种机制会给程序执行效率上带来一定的影响,但对于使用锁机制来说,这些性能影响将可以忽略。

还有一种大致相当的编程技术就是使用 __thread (比 线程特有数据 易用, 也比 线程特有数据 高效), 它是 GCC 内置的线程局部存储设施 ,这将在 线程局部存储 中讨论。

数据类型

在 C/C++ 程序中常存在全局变量、函数内定义的静态变量以及局部变量,对于局部变量来说,其不存在线程安全问题,因此不在本文讨论的范围之内。

全局变量和函数内定义的静态变量,是同一进程中各个线程都可以访问的共享变量,因此它们存在多线程读写问题。

在一个线程中修改了变量中的内容,其他线程都能感知并且能读取已更改过的内容,这对数据交换来说是非常快捷的,但是由于多线程的存在,对于同一个变量可能存在两个或两个以上的线程同时修改变量所在的内存内容,同时又存在多个线程在变量在修改的时去读取该内存值,如果没有使用相应的同步机制来保护该内存的话,那么所读取到的数据将是不可预知的,甚至可能导致程序崩溃。

如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量,这就需要新的机制来实现,我们称之为 Static memory local to a thread (线程局部静态变量),同时也可称之为线程特有数据(TSD: Thread-Specific Data)

这一类型的数据,在程序中每个线程都会分别维护一份变量的副本 (copy),并且长期存在于该线程中,对此类变量的操作不影响其他线程。
如下图:

一次性初始化

在讲解线程特有数据之前,先让我们来了解一下一次性初始化。

多线程程序有时有这样的需求:不管创建多少个线程,有些数据的初始化只能发生一次。

列如:在 C++ 程序中某个类在整个进程的生命周期内只能存在一个实例对象,在多线程的情况下,为了能让该对象能够安全的初始化,一次性初始化机制就显得尤为重要了。

——在设计模式中这种实现常常被称之为单例模式(Singleton)。
Linux 中提供了如下函数来实现一次性初始化:

#include <pthread.h>

// Returns 0 on success, or a positive error number on error
int pthread_once (pthread_once_t *once_control, void (*init) (void));
// 利用参数once_control的状态,函数pthread_once()可以确保无论有多少个线程调用多少次该函数,
// 也只会执行一次由init所指向的由调用者定义的函数。

// init所指向的函数没有任何参数,形式如下:
void init (void)
{
// some variables initializtion in here
}

另外,参数 once_control 必须是 pthread_once_t 类型变量的指针,指向初始化为 PTHRAD_ONCE_INIT 的静态变量。
在 C++0x 以后提供了类似功能的函数 std::call_once (),用法与该函数类似。
使用实例请参考 :
https://github.com/ApusApp/Swift/blob/master/swift/base/singleton.hpp
实现。

线程特有数据API介绍

在 Linux 中提供了如下函数来对线程特有数据进行操作

#include <pthread.h>

// Returns 0 on success, or a positive error number on error
int pthread_key_create (pthread_key_t *key, void (*destructor)(void *));

// Returns 0 on success, or a positive error number on error
int pthread_key_delete (pthread_key_t key);

// Returns 0 on success, or a positive error number on error
int pthread_setspecific (pthread_key_t key, const void *value);

// Returns pointer, or NULL if no thread-specific data is associated with key
void *pthread_getspecific (pthread_key_t key);

pthread_key_create

// Returns 0 on success, or a positive error number on error
int pthread_key_create (pthread_key_t *key, void (*destructor)(void *));

函数 pthread_key_create() 为线程特有数据创建一个新键,并通过 key 指向新创建的键缓冲区。

因为所有线程都可以使用返回的新键,所以参数 key 可以是一个全局变量(在 C++ 多线程编程中一般不使用全局变量,而是使用单独的类对线程特有数据进行封装,每个变量使用一个独立的 pthread_key_t)。

destructor 所指向的是一个自定义的函数,其格式如下:

void Dest (void *value)
{
    // Release storage pointed to by 'value'
}

只要线程终止时与 key 关联的值不为 NULL,则 destructor 所指的函数将会自动被调用。

如果一个线程中有多个线程特有数据变量,那么对各个变量所对应的 destructor 函数的调用顺序是不确定的,因此,每个变量的 destructor 函数的设计应该相互独立。

pthread_key_delete

// Returns 0 on success, or a positive error number on error
int pthread_key_delete (pthread_key_t key);

函数 pthread_key_delete() 并不检查当前是否有线程正在使用该线程特有数据变量,也不会调用清理函数 destructor,而只是将其释放以供下一次调用 pthread_key_create() 使用。

在 Linux 线程中,它还会将与之相关的线程数据项设置为 NULL。

由于系统对每个进程中 pthread_key_t 类型的个数是有限制的,所以进程中并不能创建无限个的 pthread_key_t 变量。Linux 中可以通过 PTHREAD_KEY_MAX(定义于 limits.h 文件中)或者系统调用 sysconf(_SC_THREAD_KEYS_MAX) 来确定当前系统最多支持多少个键。Linux 中默认是 1024 个键,这对于大多数程序来说已经足够了。如果一个线程中有多个线程特有数据变量,通常可以将这些变量封装到一个数据结构中,然后使封装后的数据结构与一个线程局部变量相关联,这样就能减少对键值的使用。

pthread_setspecific

// Returns 0 on success, or a positive error number on error
int pthread_setspecific (pthread_key_t key, const void *value);

函数 pthread_setspecific() 用于将 value 的副本存储于一数据结构中,并将其与调用线程以及 key 相关联。

参数 value 通常指向由调用者分配的一块内存,当线程终止时,会将该指针作为参数传递给与 key 相关联的 destructor 函数。

pthread_getspecific

// Returns pointer, or NULL if no thread-specific data is associated with key
void *pthread_getspecific (pthread_key_t key);

当线程被创建时,会将所有的线程特有数据变量初始化为 NULL,因此第一次使用此类变量前必须先调用 pthread_getspecific() 函数来确认是否已经于对应的 key 相关联,如果没有,那么 pthread_getspecific() 会分配一块内存并通过 pthread_setspecific() 函数保存指向该内存块的指针。

参数 value 的值也可以不是一个指向调用者分配的内存区域,而是任何可以强制转换为 void * 的变量值,在这种情况下,先前的 pthread_keycreate() 函数应将参数 _destructor 设置为 NULL

函数 pthread_getspecific() 正好与 pthread_setspecific() 相反,其是将 pthread_setspecific() 设置的 value 取出。在使用取出的值前最好是将 void * 转换成原始数据类型的指针。

使用线程特有数据API

我们先讨论一下非线程安全的 stderror() 的实现, 接着说明如何使用线程特有数据来实现该函数的线程安全.

非线程安全的stderror()

An implementation of strerror() that is not thread-safe.

/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/

/* Listing 31-1 */

/* strerror.c

An implementation of strerror() that is not thread-safe.
*/
#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist'
declarations from <stdio.h> */
#include <stdio.h>
#include <string.h> /* Get declaration of strerror() */

#define MAX_ERROR_LEN 256 /* Maximum length of string
returned by strerror() */

static char buf[MAX_ERROR_LEN]; /* Statically allocated return buffer */

char *
strerror(int err)
{
if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
}

return buf;
}

线程安全的stderror()

这是使用线程特有数据技术实现的线程安全的stderror().

如果对使用线程局部存储技术实现的线程安全的stderror()感兴趣,
请转 线程局部存储

An implementation of strerror() that is made thread-safe through the use of thread-specific data.

/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
\*************************************************************************/

/* Listing 31-3 */

/* strerror_tsd.c

An implementation of strerror() that is made thread-safe through
the use of thread-specific data.

See also strerror_tls.c.
*/
#define _GNU_SOURCE /* Get '_sys_nerr' and '_sys_errlist'
declarations from <stdio.h> */
#include <stdio.h>
#include <string.h> /* Get declaration of strerror() */
#include <pthread.h>
#include "tlpi_hdr.h"

static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t strerrorKey;

#define MAX_ERROR_LEN 256 /* Maximum length of string in per-thread
buffer returned by strerror() */

static void /* Free thread-specific data buffer */
destructor(void *buf)
{
free(buf);
}

static void /* One-time key creation function */
createKey(void)
{
int s;

/* Allocate a unique thread-specific data key and save the address
of the destructor for thread-specific data buffers */

s = pthread_key_create(&strerrorKey, destructor);
if (s != 0)
errExitEN(s, "pthread_key_create");
}

char *
strerror(int err)
{
int s;
char *buf;

/* Make first caller allocate key for thread-specific data */

s = pthread_once(&once, createKey);
if (s != 0)
errExitEN(s, "pthread_once");

buf = pthread_getspecific(strerrorKey);
if (buf == NULL) { /* If first call from this thread, allocate
buffer for thread, and save its location */
buf = malloc(MAX_ERROR_LEN);
if (buf == NULL)
errExit("malloc");

s = pthread_setspecific(strerrorKey, buf);
if (s != 0)
errExitEN(s, "pthread_setspecific");
}

if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
} else {
strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
buf[MAX_ERROR_LEN - 1] = '\0'; /* Ensure null termination */
}

return buf;
}

深入理解线程特有数据机制

深入理解线程特有数据的实现有助于对其 API 的使用。

在典型的实现中包含以下数组:

  • 一个全局(进程级别)的数组,用于存放线程特有数据的键值信息
    pthread_key_create() 返回的 pthread_key_t 类型值只是对全局数组的索引,该全局数组标记为 pthread_keys,其格式大概如下:

    数组的每个元素都是一个包含两个字段的结构,第一个字段标记该数组元素是否在用,第二个字段用于存放针对此键、线程特有数据变的解构函数的一个副本,即 destructor 函数。

  • 每个线程还包含一个数组,存有为每个线程分配的线程特有数据块的指针(通过调用 pthread_setspecific() 函数来存储的指针,即参数中的 value)

在常见的存储 pthread_setspecific()函数参数 value 的实现中,大多数都类似于下图的实现。

图中假设 pthread_keys[1]分配给 func1()函数,pthread API 为每个函数维护指向线程特有数据数据块的一个指针数组,
其中每个数组元素都与图线程特有数据键的实现 (上图) 中的全局 pthread_keys 中元素一一对应。

参考

  • [1] Linux/UNIX 系统编程手册(上)
  • [2] http://www.groad.net/bbs/thread-2182-1-1.html
  • [3] http://baike.baidu.com/view/598128.htm

Git Tutorial

Posted on 04-12-2016 | In Misc

之前有一份私人git笔记老长老长了, 今天得空, 把它浓缩成5分钟版本.
感觉纯基础性的东西整理成博客差也差不多了, 还有很多凌乱的工作笔记慢慢在一点一点整理放上来吧,
估计下面几篇博客就开始游戏服务器的开发心得之类的了.

. . .

模板能放cpp文件里吗之为什么

Posted on 03-21-2016 | In Misc

《C++ 编程思想》第 15 章 (第 300 页) 说明了原因:
模板定义很特殊。由 template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,
有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

上面这段话的意思就是说,只有模板实例化时,编译器才会得知 T 实参是什么。编译器在处理模板实例化时,不仅仅要看到模板的定义式,还需要模版的实现体。

. . .

C/S游戏架构中延迟补偿的设计和优化方法

Posted on 01-06-2016 | In GS

自我总结

注 : V社这篇文章相当有价值, 所以会有尽可能详细的注解以及对原译文各种翻译纰漏的修正.

广义的延迟补偿主要包括两个方面 :

  • A 如何显示目标
    • a. 对于本玩家自己
      • 客户端预表现(本文翻译为”客户端预测”) : 比如对于玩家移动的预测, 可以把服务器确认过的movement信息作为开始, 然后使用自己本地的movement input来进行预表现, 服务器跟客户端共享同一套move代码, 当服务器的确认信息过来之后就直接使用服务器发过来的from state进行修正并以from state为基础执行之后的预测.
    • b. 对于其他玩家
      • i. 外推法 : 即航位推测法, 外推法把其它玩家/物体看作一个点,这个点开始的位置、方向、速度已知,沿着自己的弹道向前移动。因此,假设延时是100ms,最新的协议通知客户端这个玩家奔跑速度是500单位每秒,方向垂直于玩家视线,客户端就可以假设事实上这个玩家当前实际的位置已经向前移动了50个单位。客户端可以在这个外推的位置渲染这个玩家. 这个方法不适用于FPS游戏, 因为大部分FPS游戏采用非现实的玩家系统,玩家可以随时转弯,可以在任意角度作用不现实的加速度,因此外推法得到的结果经常是错误地
      • ii. 内推法 : 即影子跟随法, 这种方法是用延时来换取平滑, 客户端物体实际移动位置总是滞后一段时间。举个例子,如果服务器每秒同步10次世界信息,客户端渲染的时候会有100ms滞后。这样,每一帧渲染的时候,我们通过最新收到的位置信息和前100ms的位置信息(或者上一帧渲染位置)进行差值得到结果.
        • 如果一个更新包没有收到,有2种处理方法 :
          • 用上面介绍的外推法(有可能产生较大误差);
          • 保持玩家位于当前位置直到收到下一个更新包(会导致玩家移动顿挫)
  • B. 延迟补偿, 步骤如下 :

    1. 为玩家计算一个相当精确的延迟时间
    2. 对每个玩家,从服务器历史信息中找一个已发送给这个玩家并且这个玩家已收到的的world update, 这个world update是在这个玩家将要执行这个movement command之前的world update
    3. 对于每一个玩家,将其从上述的world update处拉回到这个玩家生成此user command的更新时间中执行用户命令。这个回退时间需要考虑到命令执行的时候的网络延时和插值量
    4. 执行玩家命令(包括武器开火等。)
    5. 将所有移动的、错位的玩家移动到他们当前正确位置

原文

原文出处

原文标题 : Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization




Contents




  • 1 Overview

  • 2 Basic Architecture of a Client / Server Game

  • 3 Contents of the User Input messages

  • 4 Client Side Prediction

  • 5 Client-Side Prediction of Weapon Firing

  • 6 Umm, This is a Lot of Work

  • 7 Display of Targets

  • 8 Lag Compensation

  • 9 Game Design Implications of Lag Compensation

  • 10 Conclusion

  • 11 Footnotes


Overview

Dsigning first-person action games for Internet play is a challenging process. Having robust on-line gameplay in your action title, however, is becoming essential to the success and longevity of the title. In addition, the PC space is well known for requiring developers to support a wide variety of customer setups. Often, customers are running on less than state-of-the-art hardware. The same holds true for their network connections.

While broadband has been held out as a panacea for all of the current woes of on-line gaming, broadband is not a simple solution allowing developers to ignore the implications of latency and other network factors in game designs. It will be some time before broadband truly becomes adopted in the United States, and much longer before it can be assumed to exist for your clients in the rest of the world. In addition, there are a lot of poor broadband solutions, where users may occasionally have high bandwidth, but more often than not also have significant latency and packet loss in their connections.

Your game must behave well in this world. This discussion will give you a sense of some of the tradeoffs required to deliver a cutting-edge action experience on the Internet. The discussion will provide some background on how client / server architectures work in many on-line action games. In addition, the discussion will show how predictive modeling can be used to mask the effects of latency. Finally, the discussion will describe a specific mechanism, lag compensation, for allowing the game to compensate for connection quality.

. . .

1…171819202122232425262728293031323334353637
Mike

Mike

🚙 🚗 💨 💨 If you want to create a blog like this, just follow my open-source project, "hexo-theme-neo", click the GitHub button below and check it out ^_^ . It is recommended to use Chrome, Safari, or Edge to read this blog since this blog was developed on Edge (Chromium kernel version) and tested on Safari.

11 categories
291 posts
111 tags
about
GitHub Spotify
© 2013 - 2025 Mike