对于网络通信中的服务端来说,显然不可能是一对一的,我们所希望的是服务端启用一份则可以选择性的与特定一个客户端通信,而当不需要与客户端通信时,则只需要将该套接字挂到链表中存储并等待后续操作,套接字服务端通过多线程实现存储套接字和选择通信,可以提高服务端的并发性能,使其能够同时处理多个客户端的请求。在实际应用场景中,这种技术被广泛应用于网络编程、互联网应用等领域。
该功能的具体实现思路可以总结为如下流程;
在服务端启动时,创建套接字并进行绑定,然后开启一个线程(称为主线程)用于监听客户端的连接请求。主线程在接收到新的连接请求后,会将对应的套接字加入一个数据结构(例如链表、队列、哈希表等)中进行存储。同时,主线程会将存储套接字的数据结构传递给每个子线程,并开启多个子线程进行服务,每个子线程从存储套接字的数据结构中取出套接字,然后通过套接字与客户端进行通信。
在选择通信方面,用户可以指定要与哪个客户端进行通信。服务端会在存储套接字的数据结构中寻找符合条件的套接字,然后将通信数据发送给对应的客户端。
首先为了能实现套接字的存储功能,此处我们需要定义一个ClientInfo该结构被定义的作用只有一个那就是存储套接字的FD句柄,以及该套接字的IP地址与端口信息,这个结构体应该定义为如下样子;- typedef struct
- {
- SOCKET client;
- sockaddr_in saddr;
- char address[128];
- unsigned short port;
- }ClientInfo;
复制代码 接着我们来看主函数中的实现,首先主函数中listen正常侦听套接字连接情况,当有新的套接字接入后则直接通过CreateThread函数开辟一个子线程,该子线程通过EstablishConnect函数挂在后台,在挂入后台之前通过std::vector info全局变量用来保存套接字。
当读者需要发送数据时,只需要调用SendMessageConnect函数,函数接收一个套接字链表,并接收需要操作的IP地址信息,以及需要发送的数据包,当有了这些信息后,函数内部会首先依次根据IP地址判断是否是我们所需要通信的IP,如果是则从全局链表内取出套接字并发送数据包给特定的客户端。
弹出一个套接字调用PopConnect该函数接收一个全局链表,以及一个字符串IP地址,其内部通过枚举链表的方式寻找IP地址,如果找到了则直接使用ptr.erase(it)方法将找到的套接字弹出链表,并以此实现关闭通信的目的。
输出套接字元素时,通过调用ShowList函数实现,该函数内部首先通过循环枚举所有的套接字并依次Ping测试,如果发现存在掉线的套接字则直接剔除链表,如果没有掉线则客户端会反馈一个pong以表示自己还在,此时即可直接输出该套接字信息。
14.10.1 服务端实现
服务端的实现方式在上述概述中已经简单介绍过了,服务端实现的原理概括起来就是,通过多线程技术等待客户端上线,当有客户端上线后就直接将其加入到全局链表内等待操作,主函数执行死循环,等待用户输入数据,用于选择与某个套接字通信。
[code]#include #include #include #include #include #pragma comment(lib,"ws2_32.lib")using namespace std;typedef struct
{
SOCKET client;
sockaddr_in saddr;
char address[128];
unsigned short port;
}ClientInfo;std::vector info; // 全局主机列表SOCKET server; // 本地套接字sockaddr_in sai_server; // 存放服务器IP、端口// 弹出下线的主机void PopConnect(std::vector &ptr, char *address){ // 循环迭代器,查找需要弹出的元素 for (std::vector::iterator it = ptr.begin(); it != ptr.end(); it++) { ClientInfo *client = *it; // 如果找到了,则将其从链表中移除 if (strcmp(client->address, address) == 0) { ptr.erase(it); // std::cout client, ref_buf, 32, 0); if (strcmp(ref_buf, " ong") != 0) { PopConnect(info, ptr[x]->address); continue; } std::cout port = ntohs(cInfo->saddr.sin_port); info.push_back(cInfo); }}int main(int argc, char* argv[]){ // 初始化 WSA ,激活 socket WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); // 初始化 socket、服务器信息 server = socket(AF_INET, SOCK_STREAM, 0); sai_server.sin_addr.S_un.S_addr = 0; // IP地址 sai_server.sin_family = AF_INET; // IPV4 sai_server.sin_port = htons(8090); // 传输协议端口 // 本地地址关联套接字 if (bind(server, (sockaddr*)&sai_server, sizeof(sai_server))) { WSACleanup(); } // 套接字进入监听状态 listen(server, SOMAXCONN); // 建立子线程实现侦听连接 CreateThread(0, 0, (LPTHREAD_START_ROUTINE)EstablishConnect, 0, 0, 0); while (1) { char command[4096] = { 0 }; input: memset(command, 0, 4096); std::cout |