接下来,我们来了解节点启动的过程。
1.LoadAddresses全局函数
这个就是加载节点ip地址的全局函数:
cpp
bool LoadAddresses()
{
return CAddrDB("cr+").LoadAddresses();
}
供程序启动时调用,然后会加载地址数据库,其实就是调用对应的数据库类,新建CAddrDB对象,然后调用该类的成员函数LoadAddresses。
cpp
class CAddrDB : public CDB
{
public:
CAddrDB(const char* pszMode="r+", bool fTxn=false) : CDB("addr.dat", pszMode, fTxn) { }
我们可以看到,对应的数据库文件为addr.dat
2.LoadAddresses成员函数
然后我们来看一下成员函数的代码:
cpp
bool CAddrDB::LoadAddresses()
{
CRITICAL_BLOCK(cs_mapAddresses)
{
// Load user provided addresses
CAutoFile filein = fopen("addr.txt", "rt");
if (filein)
{
try
{
char psz[1000];
while (fgets(psz, sizeof(psz), filein))
{
CAddress addr(psz, NODE_NETWORK);
if (addr.ip != 0)
AddAddress(*this, addr);
}
}
catch (...) { }
}
// Get cursor
Dbc* pcursor = GetCursor();
if (!pcursor)
return false;
loop
{
// Read next record
CDataStream ssKey;
CDataStream ssValue;
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
return false;
// Unserialize
string strType;
ssKey >> strType;
if (strType == "addr")
{
CAddress addr;
ssValue >> addr;
mapAddresses.insert(make_pair(addr.GetKey(), addr));
}
}
//// debug print
printf("mapAddresses:\n");
foreach(const PAIRTYPE(vector<unsigned char>, CAddress)& item, mapAddresses)
item.second.print();
printf("-----\n");
}
return true;
}
3.addr.txt
我们来看该函数开头部分代码:
cpp
// Load user provided addresses
CAutoFile filein = fopen("addr.txt", "rt");
if (filein)
{
try
{
char psz[1000];
while (fgets(psz, sizeof(psz), filein))
{
CAddress addr(psz, NODE_NETWORK);
if (addr.ip != 0)
AddAddress(*this, addr);
}
}
catch (...) { }
}
明明是读数据库文件,为什么这里也会去读addr.txt呢?这里是用户手动配置的IP。
也就是说用户如果知道一个节点的IP,可以写在addr.txt,程序启动的时候会加载。
fgets一行一行读取,然后转换成CAddress类型,接着调用AddAddres把这个节点添加到数据库和缓存到mapAddresses中。
4.mapAddresses
在这里我们可以知道,加载的IP都存到了mapAddresses中。
我们来看一下它的定义:
cpp
map<vector<unsigned char>, CAddress> mapAddresses;
后续我们要连接节点就从这里获取。
然后来看一下后面的代码:
cpp
loop
{
// Read next record
CDataStream ssKey;
CDataStream ssValue;
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
return false;
// Unserialize
string strType;
ssKey >> strType;
if (strType == "addr")
{
CAddress addr;
ssValue >> addr;
mapAddresses.insert(make_pair(addr.GetKey(), addr));
}
}
//// debug print
printf("mapAddresses:\n");
foreach(const PAIRTYPE(vector<unsigned char>, CAddress)& item, mapAddresses)
item.second.print();
printf("-----\n");
就是不停的从数据库读取键值数据,然后如果是addr类型,就将它转换添加到mapAddresses里。
在最后还给出打印地址信息的代码,我们测试时可以使用该代码。但现在我们没有节点数据。
等做例子的时候再来使用,现在留个记号。
然后们可以看到:
cpp
if (strType == "addr")
还要判断一下类型,也就是说addr.dat还有其它类型的数据。我们读取的时候要注意了。
5.StartNode
在net.cpp里的StartNode函数负责启动节点,此函数前部分代码负责把自身节点启动,然后就开始做节点相关的工作,连接其它节点等。
我们来看一下这个函数的代码:
cpp
bool StartNode(string& strError)
{
strError = "";
// Sockets startup
WSADATA wsadata;
int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
if (ret != NO_ERROR)
{
strError = strprintf("Error: TCP/IP socket library failed to start (WSAStartup returned error %d)", ret);
printf("%s\n", strError.c_str());
return false;
}
// Get local host ip
char pszHostName[255];
if (gethostname(pszHostName, 255) == SOCKET_ERROR)
{
strError = strprintf("Error: Unable to get IP address of this computer (gethostname returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
struct hostent* pHostEnt = gethostbyname(pszHostName);
if (!pHostEnt)
{
strError = strprintf("Error: Unable to get IP address of this computer (gethostbyname returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
addrLocalHost = CAddress(*(long*)(pHostEnt->h_addr_list[0]),
DEFAULT_PORT,
nLocalServices);
printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str());
// Create socket for listening for incoming connections
SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hListenSocket == INVALID_SOCKET)
{
strError = strprintf("Error: Couldn't open socket for incoming connections (socket returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
// Set to nonblocking, incoming connections will also inherit this
u_long nOne = 1;
if (ioctlsocket(hListenSocket, FIONBIO, &nOne) == SOCKET_ERROR)
{
strError = strprintf("Error: Couldn't set properties on socket for incoming connections (ioctlsocket returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
// The sockaddr_in structure specifies the address family,
// IP address, and port for the socket that is being bound
int nRetryLimit = 15;
struct sockaddr_in sockaddr = addrLocalHost.GetSockAddr();
if (bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
int nErr = WSAGetLastError();
if (nErr == WSAEADDRINUSE)
strError = strprintf("Error: Unable to bind to port %s on this computer. The program is probably already running.", addrLocalHost.ToString().c_str());
else
strError = strprintf("Error: Unable to bind to port %s on this computer (bind returned error %d)", addrLocalHost.ToString().c_str(), nErr);
printf("%s\n", strError.c_str());
return false;
}
printf("bound to addrLocalHost = %s\n\n", addrLocalHost.ToString().c_str());
// Listen for incoming connections
if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR)
{
strError = strprintf("Error: Listening for incoming connections failed (listen returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
// Get our external IP address for incoming connections
if (addrIncoming.ip)
addrLocalHost.ip = addrIncoming.ip;
if (GetMyExternalIP(addrLocalHost.ip))
{
addrIncoming = addrLocalHost;
CWalletDB().WriteSetting("addrIncoming", addrIncoming);
}
// Get addresses from IRC and advertise ours
if (_beginthread(ThreadIRCSeed, 0, NULL) == -1)
printf("Error: _beginthread(ThreadIRCSeed) failed\n");
//
// Start threads
//
if (_beginthread(ThreadSocketHandler, 0, new SOCKET(hListenSocket)) == -1)
{
strError = "Error: _beginthread(ThreadSocketHandler) failed";
printf("%s\n", strError.c_str());
return false;
}
if (_beginthread(ThreadOpenConnections, 0, NULL) == -1)
{
strError = "Error: _beginthread(ThreadOpenConnections) failed";
printf("%s\n", strError.c_str());
return false;
}
if (_beginthread(ThreadMessageHandler, 0, NULL) == -1)
{
strError = "Error: _beginthread(ThreadMessageHandler) failed";
printf("%s\n", strError.c_str());
return false;
}
return true;
}
开始会启动一下套接字,就是在这里启动的,后续不用再启动,这是针对整个进程的:
cpp
// Sockets startup
WSADATA wsadata;
int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
if (ret != NO_ERROR)
{
strError = strprintf("Error: TCP/IP socket library failed to start (WSAStartup returned error %d)", ret);
printf("%s\n", strError.c_str());
return false;
}
然后干什么呢?获取本机地址,然后转换成CAddress类型--addrLocalHost,是为了后续绑定监听使用的:
cpp
struct sockaddr_in sockaddr = addrLocalHost.GetSockAddr();
if (bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR)
然后启动监听:
cpp
if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR)
{
strError = strprintf("Error: Listening for incoming connections failed (listen returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
以上部分,就是节点做为一个服务端创建流程,跟winsock的服务端创建过程其实区别不大。这里应该不难理解。
好,但是没见到accept函数,因为这不是一次连接或发送数据。所以这个函数是在后面相关的线程函数执行任务的。
6.GetMyExternalIP
我们来看StartNode接下来干的事:
cpp
// Get our external IP address for incoming connections
if (addrIncoming.ip)
addrLocalHost.ip = addrIncoming.ip;
if (GetMyExternalIP(addrLocalHost.ip))
{
addrIncoming = addrLocalHost;
CWalletDB().WriteSetting("addrIncoming", addrIncoming);
}
addrIncoming.ip这个是公网IP,从数据库类的读取出来的,如果存在的话,则把它赋给addrLocalHost,就是上次使用的公网IP(就是我们用某些网站可以获取我们的IP地址的那个IP,非局域网IP)。
而GetMyExternalIP就是获取外网IP的函数,如果成功获取则返回ture,并重新设置addrLocalHost.ip。
然后把它写进数据库里。
好,上面就是外网相关部分。
7.IRC
接下是这个代码:
cpp
// Get addresses from IRC and advertise ours
if (_beginthread(ThreadIRCSeed, 0, NULL) == -1)
printf("Error: _beginthread(ThreadIRCSeed) failed\n");
启动了线程去干一件事,执行ThreadIRCSeed函数。
这个是干什么呢?就是如果当一个全新节点启动,之前如果不知道任何节点信息。你怎么获得节点IP呢?虽然节点之间可以互相分享其它节点信息,但第一个节点怎么发现呢?
就是通过这个IRC的机制,IRC是一种聊天协议,有着固定的服务器,这样我们就知道它的IP地址。然后连接上,加入特定的频道,比如bitcoin频道。
这个节点加入后,频道是可以取名字的,就把自己的名字改为ip加端口。
这样就构成了一个分享节点的平台,你可以从这里获取 ,上面的函数就是做这个事。
具体代码就不看了,因为现在已经不采用了。不是很安全,现在已经改成别的方法了。
8.线程函数
StartNode函数的最后,启动了三个线程函数,如下:
cpp
//
// Start threads
//
if (_beginthread(ThreadSocketHandler, 0, new SOCKET(hListenSocket)) == -1)
{
strError = "Error: _beginthread(ThreadSocketHandler) failed";
printf("%s\n", strError.c_str());
return false;
}
if (_beginthread(ThreadOpenConnections, 0, NULL) == -1)
{
strError = "Error: _beginthread(ThreadOpenConnections) failed";
printf("%s\n", strError.c_str());
return false;
}
if (_beginthread(ThreadMessageHandler, 0, NULL) == -1)
{
strError = "Error: _beginthread(ThreadMessageHandler) failed";
printf("%s\n", strError.c_str());
return false;
}
这里是比较关键的地方,理解清楚了, 我们就会对节点执行流程有个清晰的掌握。
由于篇幅过长,我们将在后面章节介绍。