skynet源码——main函数

skynet版本ad06d232a6e54179d1615241b273bc18ab08805c
为例,对源码进行阅读。

在skynet-src/skynet_main.c中有

int main(int argc, char *argv[]) {
	const char * config_file = NULL ;
	if (argc > 1) {
		config_file = argv[1];
	} else {
		fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
			"usage: skynet configfilename\n");
		return 1;
	}

	skynet_globalinit();
	skynet_env_init();
	
	sigign();

	struct skynet_config config;

#ifdef LUA_CACHELIB
	// init the lock of code cache
	luaL_initcodecache();
#endif

	struct lua_State *L = luaL_newstate();
	luaL_openlibs(L);	// link lua lib

	int err =  luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");
	assert(err == LUA_OK);
	lua_pushstring(L, config_file);

	err = lua_pcall(L, 1, 1, 0);
	if (err) {
		fprintf(stderr,"%s\n",lua_tostring(L,-1));
		lua_close(L);
		return 1;
	}
	_init_env(L);

	config.thread =  optint("thread",8);
	config.module_path = optstring("cpath","./cservice/?.so");
	config.harbor = optint("harbor", 1);
	config.bootstrap = optstring("bootstrap","snlua bootstrap");
	config.daemon = optstring("daemon", NULL);
	config.logger = optstring("logger", NULL);
	config.logservice = optstring("logservice", "logger");
	config.profile = optboolean("profile", 1);

	lua_close(L);

	skynet_start(&config);
	skynet_globalexit();

	return 0;
}

从skynet的main函数开始阅读源码。

首先是

	const char * config_file = NULL ;
	if (argc > 1) {
		config_file = argv[1];

从命令行的argv[1]中获取配置文件

./skynet examples/config

平时就像这样运行skynet(第二个参数是配置文件相对或者绝对路径)

void skynet_globalinit(void) {
	G_NODE.total = 0;
	G_NODE.monitor_exit = 0;
	G_NODE.init = 1;
	if (pthread_key_create(&G_NODE.handle_key, NULL)) {
		fprintf(stderr, "pthread_key_create failed");
		exit(1);
	}
	// set mainthread's key
	skynet_initthread(THREAD_MAIN);
}

在文件skynet_server.c中

struct skynet_node {
	int total;
	int init;
	uint32_t monitor_exit;
	pthread_key_t handle_key;
	bool profile;	// default is off
};

static struct skynet_node G_NODE;
void skynet_initthread(int m) {
	uintptr_t v = (uint32_t)(-m);
	pthread_setspecific(G_NODE.handle_key, (void *)v);
}

这就是skynet_globalinit做的事情。

解读:
通过搜索,G_NODE只在skynet/skynet-src/skynet_server.c中使用。

在main函数中调用skynet_globalinit

这里涉及到线程中特有的线程存储,用到以下三个函数:

int pthread_setspecific(pthread_key_t key, const void *value);

void *pthread_getspecific(pthread_key_t key);

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

在多线程程序中,所有线程共享程序中的变量。现在有一全局变量,所有线程都可以使用它,改变它的值。

而如果每个线程希望能单独拥有它,那么就需要使用线程存储了。

表面上看起来这是一个全局变量,所有线程都可以使用它,而它的值在每一个线程中又是单独存储的。这就是线程存储的意义。

下面说一下线程存储的具体用法。

  1. 创建一个类型为 pthread_key_t 类型的变量。

  2. 调用 pthread_key_create() 来创建该变量。
    该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。

  3. 当线程中需要存储特殊值的时候,可以调用 pthread_setspcific() 。该函数有两个参数,第一个为前面声明的 pthread_key_t 变量,第二个为 void* 变量,这样你可以存储任何类型的值。

  4. 如果需要取出所存储的值,调用 pthread_getspecific() 。该函数的参数为前面提到的 pthread_key_t 变量,该函数返回 void * 类型的值。

现在我们再看看:

struct skynet_node {
	int total;
	int init;
	uint32_t monitor_exit;
	pthread_key_t handle_key;
	bool profile;	// default is off
};

static struct skynet_node G_NODE;

...

uint32_t 
skynet_current_handle(void) {
	if (G_NODE.init) {
		void * handle = pthread_getspecific(G_NODE.handle_key);
		return (uint32_t)(uintptr_t)handle;
	} else {
		uint32_t v = (uint32_t)(-THREAD_MAIN);
		return v;
	}
}

...

void skynet_globalinit(void) {
	G_NODE.total = 0;
	G_NODE.monitor_exit = 0;
	G_NODE.init = 1;
	if (pthread_key_create(&G_NODE.handle_key, NULL)) {
		fprintf(stderr, "pthread_key_create failed");
		exit(1);
	}
	// set mainthread's key
	skynet_initthread(THREAD_MAIN);
}

...

void skynet_initthread(int m) {
	uintptr_t v = (uint32_t)(-m);
	pthread_setspecific(G_NODE.handle_key, (void *)v);
}

这三个函数pthread_key_create、pthread_setspecific和pthread_getspecific都出现了,就是给每个线程带上了线程的私有变量。

重点看这个函数:

uint32_t 
skynet_current_handle(void) {
	if (G_NODE.init) {
		void * handle = pthread_getspecific(G_NODE.handle_key);
		return (uint32_t)(uintptr_t)handle;
	} else {
		uint32_t v = (uint32_t)(-THREAD_MAIN);
		return v;
	}
}

在skynet/skynet-src/skynet_imp.h文件中

#define THREAD_MAIN 1

(uint32_t)(-1)表示的是1,111…111(共32个1),表示unsigned 32位整数的最大值。

实际上这里就是将skynet/skynet-src/skynet_imp.h文件中的

#define THREAD_WORKER 0
#define THREAD_MAIN 1
#define THREAD_SOCKET 2
#define THREAD_TIMER 3
#define THREAD_MONITOR 4

通过全工程搜索的可以看到skynet_initthread调用的地方:
在这里插入图片描述

这五个宏定义,0到4,分别与5个线程(其中一个是主线程)绑定起来,然后再利用uint32_t skynet_current_handle(void)获取线程的指针。

参考:
pthread_key_t和pthread_key_create()详解
为什么unsigned (-1)表示无符号整数的最大值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Skynet是一个开源的分布式游戏服务器引擎,它由前后端源码构成。前端源码主要负责处理客户端与服务器之间的交互逻辑,而后端源码则负责处理服务器端的逻辑处理和数据存储。 前端源码中,主要包含了游戏客户端的逻辑处理和界面展示。它通常使用一种编程语言(如C++、Lua等)来编写,并提供了一些接口用于与后端服务器进行通信。在游戏中,前端源码会根据玩家的操作进行界面的展示,同时将用户的输入发送给后端服务器,以便后端进行相应的逻辑处理。 后端源码Skynet的核心,它负责处理游戏服务器的逻辑处理和数据存储。后端源码通常使用一种高性能的编程语言(如C++、Java等)来编写,并提供了一系列的模块用于处理不同的逻辑需求,如游戏逻辑模块、网络模块、数据库模块等。其主要功能包括接收前端发送的请求、进行逻辑处理、更新数据状态等。同时,后端源码还可以支持多个服务器之间的互联,以实现分布式的游戏服务器集群。 Skynet的前后端源码的设计目标是提供一个高性能、易扩展的游戏服务器引擎,以满足不同规模游戏项目的需求。通过前后端源码的配合,Skynet可以快速构建起一个稳定可靠的游戏服务器系统,并支持大规模的并发用户请求处理。同时,通过源码的开源,让开发者可以根据自身需求进行二次开发和定制,以满足不同游戏项目的特定需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值