纯C手写套接字select服务器端(带注释)

本文介绍了使用C语言编写基于select模型的TCP服务器端程序。通过WSAStartup初始化网络库,设置套接字,监听特定端口,并使用select函数进行多路复用,实现客户端连接管理和消息收发。当接收到客户端消息时,服务器会回复'ok',并在客户端断开连接时清理资源。
摘要由CSDN通过智能技术生成

纯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], SOL_SOCKET, SO_ERROR, str, &len))
				{
					printf("无法得到错误信息!!\n");
				}
				printf("%s\n",str);
			}
		}
		else
		{
			int a = WSAGetLastError();
			//出错了!!!
			printf("%d\n", a);
			//发生错误了
		}
	}	
	return 0;
	system("pause");
}
/*
int bind(SOCKET s,const sockaddr *addr,int namelen)//套接字对象,sockaddr(包含IP地址,端口号),sizeof();
*/
/*
select函数总结->解决c/s模型的傻等问题
不等待:  执行阻塞->对应最后一个参数填0;
半等待:  执行阻塞加软阻塞->最后一个参数填任意的时间值
全等待:  执行阻塞加硬阻塞(死等)->最后一个参数填NULL
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值