当前位置 博文首页 > liuguangshibei的博客:纯C手写套接字select服务器端(带注释)

    liuguangshibei的博客:纯C手写套接字select服务器端(带注释)

    作者:[db:作者] 时间:2021-08-13 12:51

    纯C手写套接字select模型服务器端

    #include<WinSock2.h>
    #include<stdio.h>
    #include<iostream>
    #include<string>
    #include<stdlib.h>
    #pragma comment(lib,"ws2_32.lib")
    using namespace std;
    
    fd_set everyclient;
    
    BOOL WINAPI fun(DWORD dwCtrlType)
    {
    	switch (dwCtrlType)
    	{
    	case CTRL_CLOSE_EVENT:
    		for (u_int i = 0; i < everyclient.fd_count; i++)//依次关闭所有的套接字
    		{
    			closesocket(everyclient.fd_array[i]);
    		}
    		WSACleanup();//关闭网络库
    	}
    	return TRUE;
    }
    
    int main(void)
    {
        //用以下回调函数是为了应对直接关闭控制台程序导致的异常退出问题。
    	SetConsoleCtrlHandler(fun,TRUE);//回调函数,参数:(自己定义的函数但需按标准定义,bool值,填TRUE意为着执行自定义函数,反则否)
    
    	WORD wdVersion = MAKEWORD(2, 2);//定义网路库的版本,当前版本号为2.2版;
    	WSADATA wdScokMsg;//结构体定义网络库的信息;
    	int nRes = WSAStartup(wdVersion, &wdScokMsg);//打开网络库,返回值为int类型
    
    	if (0 != nRes)//返回值为0则代表打开网络库成功,否则则返回相应的错误码;
    	{
    		switch (nRes)//打开失败
    		{
    		case WSASYSNOTREADY:
    			printf("重启计算机!");
    			break;
    		case WSAEINPROGRESS:
    			printf("更新网络库!");
    			break;
    		}
    		return 0;//每次的打开失败都应该设定返回值为0结束程序的运行
    	}
    
    	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//地址的类型,套接字的类型,协议的类型。
    
    	if (INVALID_SOCKET == socketServer)
    	{
    		int a = WSAGetLastError();//检测错误,检测离其最近的函数的错误码
    		//执行成功之后返回的值为0;
    		WSACleanup();
    		return 0;
    	}
    
    	if (2 != HIBYTE(wdScokMsg.wVersion) || LOBYTE(wdScokMsg.wVersion) != 2)
    	{
    		WSACleanup();//关闭网络库
    		return 0;
    	}
    
    	struct  sockaddr_in si;
    	si.sin_family = AF_INET;//定义协议类型
    	si.sin_port = htons(12345);//定义链接的端口号
    	si.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");//定义服务器本身的IP地址,在不考虑安全的情况下可以用全零,在本机测试可以用回环地址。
    
    	int res = bind(socketServer, (const struct sockaddr*) & si, sizeof(si));//bind函数,将服务器套接字与自定义的套接字信息进行绑定。
    
    	if (SOCKET_ERROR == res)
    	{
    		int a = WSAGetLastError();//有错误
    		closesocket(socketServer);//关闭当前的套接字。要在关闭网络库之前。
    		WSACleanup();//关闭网络库
    		return 0;
    	}
    
    	if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))//监听事件函数,第一个参数套接字对象,第二个是积压的缓存队列,如上定义后则是让系统自己取寻找合适的值。
    	{
    		int a = WSAGetLastError();//有错误
    		closesocket(socketServer);//关闭当前的套接字。要在关闭网络库之前。
    		WSACleanup();//关闭网络库
    		return 0;
    	}
    
    	struct sockaddr_in clientMsg;//创建一个客户端的套接字地址信息结构体
    	int len = sizeof(clientMsg);//结构体的大小
    
    	printf("服务器已运行!");
    
    	
    	//将select中的fd_array清零;
    	FD_ZERO(&everyclient);
    	//将服务器的套接字装进去
    	FD_SET(socketServer,&everyclient);
    
    	while (1)
    	{
    		fd_set tempsocket = everyclient;//创建一个用于执行中的fd_set结构体实例
    		fd_set writesocket = everyclient;//创建可写的结构体
    		FD_CLR(socketServer,&writesocket);//将可写结构体中的服务器套接字除外
    		fd_set readsocket = everyclient;//创建可读的结构体
    		fd_set errorsocket = everyclient;//创建包含错误信息的结构体
    		struct timeval st;//定义select函数最后一个参数的结构体变量
    		st.tv_sec = 3;//定义半阻塞等待时间为3秒
    		st.tv_usec = 0;//定义为零秒
    		int nRes=select(0,&readsocket,&writesocket,&errorsocket,NULL)//select函数,参数:(int(默认填零),可读套接字结构体,可写套接字结构体,错误信息套接字结构体,等待的时间参数->在该实例中并没有指定等待的时间,填为NULL意为全阻塞,即死等状态);
    		char send_buf[100] = {0};
    
    		if (0==nRes)//没有响应socket
    		{
    			continue;
    		}
    		else if (nRes > 0)
    		{
    		
    			//有响应
    			for (u_int i=0;i< readsocket.fd_count;i++)
    			{
    				//匹配到的是服务器的socket,则代表有客户端想与服务器建立链接
    				if (readsocket.fd_array[i]==socketServer)
    				{
    					SOCKET socketClient = accept(socketServer,NULL,NULL);//创建客户端socket与accept建立链接
    					if (SOCKET_ERROR == socketClient)//建立失败,continue跳过
    					{
    						continue;
    					}
    					FD_SET(socketClient,&everyclient);//将新建立的socket放入集合当中
    					printf("客户端链接成功!\n");
    				}
    				
    				else//链接客户端的socket
    				{
    
    					char strbuf[1500] = { 0 };//创建接收消息的数组
    
    					int nRecv = recv(readsocket.fd_array[i],strbuf,1500,0);
    					{
    						if (0==nRecv)
    						{
    							//客户端下线了
    							//并从集合中将下线的客户端拿掉
    							//一定要先写一个socket记录一下下线的socket,然后拿掉该socket,但关闭的是刚刚用于记录的socket
    							SOCKET socketTemp = readsocket.fd_array[i];
    							FD_CLR(readsocket.fd_array[i],&everyclient);
    							//释放
    							closesocket(socketTemp);
    						}
    						else if (nRecv>0)
    						{
    							//接收到了消息,并打印出来
    							printf("%s\n",strbuf);
    							for (u_int i = 0; i < writesocket.fd_count; i++)//得到可写的套接字,并随时可以发送信息
    							{
    								if (SOCKET_ERROR == send(writesocket.fd_array[i], "ok", sizeof("ok"), 0))//接收到消息,并给客户端返回ok。
    								{
    									int a = WSAGetLastError();
    								}
    
    							}
    						}
    						else
    						{
    							int a = WSAGetLastError();
    							//出错了!!!				
    							switch (a)
    							{
    							case 10054:
    								{
    								printf("客户端已下线!");
    								SOCKET socketTemp = readsocket.fd_array[i];
    								FD_CLR(readsocket.fd_array[i], &everyclient);
    								closesocket(socketTemp);
    								}
    
    							}
    						}
    					}	
    					
    					
    
    				}
    				
    			}
    		
    			
    			for (u_int i = 0;i< errorsocket.fd_count;i++)//得到错误的套接字,并返回错误信息码
    			{
    				char str[100] = { 0 };
    				int len = 99;
    				if (SOCKET_ERROR==getsockopt(errorsocket.fd_array[i]