基于select模型的tcp服务器------一个服务器如何与多个客户端进行通信?

972 篇文章 327 订阅
147 篇文章 46 订阅

      很多时候, 服务器都需要同时与多个客户端进行通信, 服务嘛, 就是这样。 下面, 我们用select模型来简要模拟一下这种情形。代码是最好的解释, 所以, 还是上代码吧:

      服务端程序:

 

#include <stdio.h>
#include <winsock2.h>   
#pragma comment(lib, "ws2_32.lib")

int totalSockets = 0; // socket的总数
SOCKET socketArray[100]; // socket组成的数组, 假设最多有100个socket吧

// 日志打印
void log(const char *pStr)
{
	FILE *fp = fopen("log.txt", "a");
	fprintf(fp, "log:%s\n", pStr);
	fclose(fp);
}

// 创建socket
void addToSocketArr(SOCKET s)   
{
    socketArray[totalSockets] = s;       
    totalSockets++; 
}

// 启动服务器
int main()
{
	log("into main");

	// 网络初始化
	WSADATA   wsaData;
    WSAStartup(MAKEWORD(1, 1), &wsaData);

	// 创建socket
    SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    addToSocketArr(listenSocket);    
   
	// 服务地信息
	SOCKADDR_IN   srvAddr;
    srvAddr.sin_family = AF_INET;   
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);   
    srvAddr.sin_port = htons(8888);  
	
	// 绑定
    bind(listenSocket, (SOCKADDR*)&srvAddr, sizeof(srvAddr)); 
  
	// 监听
    listen(listenSocket, 5); 
  
	// 设置socket为非阻塞模式
    unsigned long nonBlock = 1;   
    ioctlsocket(listenSocket, FIONBIO, &nonBlock); 
 
	while(1)   
    {
		// 读集, 要记得清零初始化
		FD_SET   readSet;
        FD_ZERO(&readSet);   

		// 将每个socket都塞入读集, 便于让内核来监测这些socket
		int i = 0;
        for(i = 0; i < totalSockets; i++)   
		{            
			FD_SET(socketArray[i], &readSet);    
		}

		// 应用程序通知内核来监测读集中的socket, 最后的NULL表示超时时间无限长
		int total = select(0, &readSet, NULL, NULL, NULL);  
      
		// 我们不考虑select失败, 那么程序到这里, 说明读集中必有socket处于"就绪状态"
        for(i = 0; i < totalSockets; i++)   
		{
			char szTmp[20] = {0};
			sprintf(szTmp, "%d", i);
			log(szTmp);

			if(socketArray[i] == listenSocket) 	// 对监听的socket进行判断
			{
				log("socketArray[i] == listenSocket");

				if(FD_ISSET(listenSocket, &readSet)) // 如果该socket在可读集中, 则表明有客户端来连接
				{

					log("listenSocket, socketArray[i] == listenSocket");

					// 接收来自于客户端的connect请求
					SOCKADDR_IN addrClient;  
					int len = sizeof(SOCKADDR);
					SOCKET acceptSocket = 0;
					acceptSocket = accept(listenSocket,(SOCKADDR*)&addrClient, &len);
 
					// 设置为非阻塞模式
					nonBlock = 1;   
					ioctlsocket(acceptSocket, FIONBIO, &nonBlock);

					// 添加到socket数组中
					addToSocketArr(acceptSocket);
				}

				continue;
			}


			// 注意:上面的listenSocket是不负责通信的, 下面的一些socket都是负责通信的socket


			// 如果通信socket处于读就绪状态
			if (FD_ISSET(socketArray[i], &readSet))   
            {
				log("to receive");

				char szRecvBuf[1024] = {0};
				recv(socketArray[i], szRecvBuf, sizeof(szRecvBuf) - 1, 0);   
				printf("socketArray[i] is %d, %s\n", socketArray[i], szRecvBuf);
			}
		}   		
    }

	// 省略了关闭socket等后续操作

	return 0;
}

      启动服务端。

 

      

      再看客户端程序:

 

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(1, 1);
	
	WSAStartup( wVersionRequested, &wsaData );

	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(8888);
	
	connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	

	while(1)
	{
		char szSendBuf[100] = {0};
		scanf("%s", szSendBuf);
		send(sockClient, szSendBuf, strlen(szSendBuf) + 1, 0);
	}

	closesocket(sockClient);
	WSACleanup();

	return 0;
}

        在此处, 我们编译并链接, 然后连续运行8次客户端(不要关闭), 也就产生了8个客户端进程, 并在8个客户端进程中依次输入1, 2, 3, 4, 5, 6, 7, 8后分别按Enter发送, 然后在第一个客户端进程中再次输入111, 并按Enter发送。 我们看看服务端的结果:

 

socketArray[i] is 136, 1
socketArray[i] is 152, 2
socketArray[i] is 164, 3
socketArray[i] is 176, 4
socketArray[i] is 188, 5
socketArray[i] is 200, 6
socketArray[i] is 212, 7
socketArray[i] is 224, 8
socketArray[i] is 136, 111

       可以看到, 服务端可以同时与各个客户端进行通信, 而且还不会混淆他们。 另外,从log.txt日中中可以看出, 尽管有8个客户端, 但服务端的监听socket是唯一的。有一个小小的问题值得我们注意: 在服务端, 我们listen函数的第二个参数是5, 很多人会认为是服务端最多允许5个客户端同时连接, 其实, 不是酱紫的。看看, 我们实战中就有8个客户端同时连接上了呢, 我们后续会对listen的第二个参数的具体含义进行更加详细的讨论。

      最后要说明的是, 上述程序还是比较简单的, 没有考虑很多异常的情况, 比如, 某个客户端突然退出对服务器的影响。 如果以后玩程序的时候需要考虑更复杂的情况, 我们再完善。


 


 

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值