(1)循环接收连接
在“1.4 监听套接字”中提到,服务端调用listen()函数等待客户端连接。当有客户端连接服务端时,就需要调用accept()函数接收客户端的连接。为了使服务端能够接受多个客户端的连接,需要在while()循环中调用accept()函数。
为了能够在主程序中控制线程,将CTCPSocket_Server类的一个bool类型的成员变量m_acceptthread_alive作为while()的条件。当主程序可以通过m_acceptthread_alive来结束该线程。
while (pServer->m_acceptthread_alive)
{
socket_accept = accept(pServer->m_socket_listen, (sockaddr*)&clientaddr_connected, &clientaddr_size);
}
其中,accept()
函数的作用是接收来自客户端的连接。该函数的格式为
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
参数s
表示服务器监听的套接字;
addr
保存了连接的客户端套接字的信息;
addrlen
是
addr
大小的指针。如果成功接受客户端的连接,
accept()
函数的返回值是一个新的套接字,服务端使用该新创建的套接字与客户端进行数据的数据的收发,否则返回值是
INVALID_SOCKET
。
(2)确定新创建套接字在“池”中的索引
服务端要接收多个客户端的连接,所以服务端包含多个套接字与客户端进行数据通信。在CTCPSocket_Server类中,使用两个数组来表示服务端的“套接字池”,在“池”中包含了与客户端通信的套接字以及这些套接字是否可用。
表示服务端的“套接字池”的两个数组分别是SOCKET类型的数组m_clientsocket_array和bool类型的数组m_clientconnectflag_array。其中数组m_clientsocket_array中的元素表示与客户端进行数据通信的套接字;数组m_clientconnectflag_array中对应的元素表示m_clientsocket_array中的套接字是否可用。
SOCKET m_clientsocket_array[MAX_CONNECTION];
bool m_clientconnectflag_array[MAX_CONNECTION];
其中,MAX_CONNECTION
是自定义的常量,表示服务端能够接受客户端最大连接数。
#define MAX_CONNECTION 100
在CTCPSocket_Server
类的构造函数中将m_clientconnectflag_array
中的所有元素初始化为
false
。
for (int i = 0; i < MAX_CONNECTION; ++i)
{
m_clientconnectflag_array[i] = false;
}
要确定新创建套接字在“池”中的索引,只需找到数组
m_clientconnectflag_array
中值为
false
的第一个元素即可。通过CTCPSocket_Server
类类型为int
的成员变量
m_client_currentindex
表示新创建套接字在“池”中的索引。
for (pServer->m_client_currentindex = 0; pServer->m_client_currentindex < MAX_CONNECTION; ++pServer->m_client_currentindex)
{
if (pServer->m_clientconnectflag_array[pServer->m_client_currentindex])
{
break;
}
}
在“(
2
)获取类对象指针”中提到,线程函数中必须使用类的静态成员或者通过类对象的指针使用类的普通成员。
pServer
即为类对象的指针,通过该指针可以使用类的普通成员。
以上代码的作用是遍历数组m_clientconnectflag_array中的所有元素,找到第一个为false元素的索引。
(3)将更新“套接字池”中的元素
在“(4)确定新创建套接字在“池”中的索引”中提到,数组m_clientsocket_array中的元素表示与客户端进行数据通信的套接字。在获取到了新创建套接字在“池”中的索引
后,更新“套接字池”中的元素,即对数组m_clientsocket_array中相应的元素赋值,并且将数组m_clientconnectflag_array中相应的元素值设置为true。
pServer->m_clientsocket_array[pServer->m_client_currentindex] = socket_accept;
pServer->m_clientconnectflag_array[pServer->m_client_currentindex] = true;
(4)处理连接的客户端信息
在“(3)循环接收连接”中提到,连入服务端的客户端信息保存在accept()函数的第二个参数clientaddr_connected中。如果主程序需要处理这些客户端的信息,例如在主窗口中显示连入服务端的客户端IP地址等信息,则需要通过“2.2 定义函数指针类型的成员变量”中定义的回调函数m_acceptclient_proc实现。
由于回调函数m_acceptclient_proc的表示客户端地址信息的参数类型是字符指针char*,所以在调用该回调函数之前,需要将clientaddr_connected中客户端的IP地址转换为char*格式。
为CTCPSocket_Server类定义一个二维数组m_clientip_array,用来保存连入服务器的客户端IP地址。
char m_clientip_array[MAX_CONNECTION][16];
其中,MAX_CONNECTION
表示连入服务器的客户端的最大数量,
16
是表示
IP
地址的字符串的最大长度。在
ThreadFunc_StartServer()
函数中,有如下代码
sprintf_s(pServer->m_clientip_array[pServer->m_client_currentindex]
, "%d.%d.%d.%d"
, clientaddr_connected.sin_addr.S_un.S_un_b.s_b1
, clientaddr_connected.sin_addr.S_un.S_un_b.s_b2
, clientaddr_connected.sin_addr.S_un.S_un_b.s_b3
, clientaddr_connected.sin_addr.S_un.S_un_b.s_b4
);
其中,sprintf_s()
函数的作用是格式化字符串,
clientaddr_connected
保存了客户端
IP
地址的信息,其类型为
sockaddr_in
,在“
1.3.1 sockaddr_in
与
sockaddr”中介绍了
sockaddr_in
的格式,
s_b1
、
s_b2
、
s_b3
和
s_b4
分别表示
IP
地址的四个组成部分。
在将客户端的IP地址转换为字符指针之后,接下来通过回调函数m_acceptclient_proc将客户端的IP地址传递给主程序,由主程序进行处理。
if (pServer->m_acceptclient_proc != NULL)
{
pServer->m_acceptclient_proc(
pServer->m_clientip_array[pServer->m_client_currentindex]
, sizeof(pServer->m_clientip_array[pServer->m_client_currentindex])
, pServer->m_client_currentindex
);
}
其中,pServer->m_client_currentindex
是套接字在“套接字池”中的索引,在主程序中需要用到该索引。
(5)后续处理
服务端接受了客户端的连接之后,之后的动作就是准备接收来自客户端的数据了。通过CTCPSocket_Server类的成员函数NewConnect()实现。该函数的格式为
bool NewConnect(int index);
其中,参数index
表示
accept()
函数创建的套接字在“套接字池”中的索引。
在ThreadFunc_StartServer()函数中,使用类对象的指针调用该函数。
pServer->NewConnect(pServer->m_client_currentindex);
该函数将在后续内容中详细讲解。