网络编程之路---10

开始之前先狠狠的鄙视自己一下,居然这么久都木有继续

select模型:防止在阻塞模式的套接字被锁死,避免在非阻塞套接字里重复检查WSAWOULDBLOCK错误.

流程:1:用FD_ZERO宏来初始化我们感兴趣的fd_set,也就是select函数的第二三四个参数

2:用FD_SET宏来将套接字句柄分配给相应的fd_set。3:调用select函数。

4:用FD_ISSET对套接字句柄进行检查,如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了,马上可以读取成功而不会被阻塞。


下面是select模型服务端代码:

#include <WinSock2.h>
#include <iostream>

#pragma comment(lib, "WS2_32.lib")

#define BUF_SIZE 64
#define DATA_BUFSIZE 1024
#define PORT 9990

typedef struct _SOCKET_INFORMATION
{
	CHAR Buffer[DATA_BUFSIZE]; //收发数据的缓冲区
	WSABUF DataBuf;           //定义发送和接收数据的结构体
	SOCKET socket;            //与客户进行通信的socket
	DWORD ByteSEND;           //socket发送的字节数
	DWORD ByteRECV;          //socket接收的字节数
}SOCKET_INFORMATION , *LPSOCKET_INFORMATION;

DWORD TotalSockets; //正在使用的socket总数
LPSOCKET_INFORMATION SocketArray[FD_SETSIZE];

BOOL CreateSocketInformation(SOCKET s) //创建socket信息
{
	LPSOCKET_INFORMATION SI;

	if ( (SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL )
	{
		printf("GlobalAlloc failed with error %d\n", GetLastError());
		return FALSE;
	}

	SI->socket = s;
	SI->ByteSEND = 0;
	SI->ByteRECV = 0;
	SocketArray[TotalSockets] = SI;
	TotalSockets++;

	return TRUE;
}

void FreeSocketInformation(DWORD index) //释放socket信息
{
	LPSOCKET_INFORMATION SI = SocketArray[index];

	DWORD i;

	closesocket(SI->socket);
	GlobalFree(SI);

	for (i = index; i < TotalSockets; i++)
	{
		SocketArray[i] = SocketArray[i + 1];
	}
	TotalSockets--;
}

int main(int argc, char *argv[])
{
	SOCKET ListenSocket;
	SOCKET AcceptSocket;
	SOCKADDR_IN InternetAddr;
	WSADATA wsaData;
	INT ret; //用来检查各种返回值结果的
	FD_SET WriteSet;
	FD_SET ReadSet;
	DWORD Total = 0;
	DWORD SendBytes = 0;
	DWORD RecvBytes = 0;

	if ((ret = WSAStartup(0X0202, &wsaData)) != 0)
	{
		printf("WSAstarup() failed\n");
		WSACleanup();
		return -1;
	}

	if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
	{
		printf("WSAsocket() failed\n");
		closesocket(ListenSocket);
		WSACleanup();
		return -1;
	}

	InternetAddr.sin_family = AF_INET;
	InternetAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	InternetAddr.sin_port = htons(PORT);

	if (bind(ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
	{
		printf("bind error\n");
		closesocket(ListenSocket);
		WSACleanup();
		return -1;
	}

	if (listen(ListenSocket, 5))
	{
		printf("listen failed\n");
		return -1;
	}

	ULONG noBlock = 1; //设置为非阻塞模式
	if (ioctlsocket(ListenSocket, FIONBIO, &noBlock) == SOCKET_ERROR)
	{
		printf("ioctlsocket failed");
		return -1;
	}
	
	CreateSocketInformation(ListenSocket);

	while(TRUE)
	{
		//初始化我们感兴趣的fe_set
		FD_ZERO(&ReadSet);
		FD_ZERO(&WriteSet);

		//用FD_SET宏来将套接字句柄分配给相应的fd_set
		//如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接字句柄加入可读性检查队列中

		FD_SET(ListenSocket, &ReadSet);

		//循环把所有的套接字都放进对应的集合?
		for (DWORD i = 0; i < TotalSockets; ++i)
		{
			LPSOCKET_INFORMATION socket_info = SocketArray[i];
			FD_SET(socket_info->socket, &ReadSet);
			FD_SET(socket_info->socket, &WriteSet);
		}

		//调用select函数,如果该套接字没有数据要接收,select函数会把该套接字从可读性列表中删除
		if ( (Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)
		{
			printf("select failed\n");
			return -1;
		}

		//用FD_ISSET对套接字句柄进行检查
		for (DWORD i = 0; i < TotalSockets; ++i)
		{
			LPSOCKET_INFORMATION socket_info = SocketArray[i];

			if (FD_ISSET(socket_info->socket, &ReadSet))
			{
				if (socket_info->socket == ListenSocket)
				{
					Total--; //就绪的socket-1

					//参数addr所指向的套接字地址结构中将存放客户机的相关信息,addrLen指针将描述套接字地址结构长度
					//通常情况下服务器对这些信息不是很感兴趣,故可以设置为NULL
					if ( (AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET)
					{
						noBlock = 1;
						if (ioctlsocket(AcceptSocket, FIONBIO, &noBlock) == SOCKET_ERROR)
						{
							printf("ioctlsocket failed\n");
							return -1;
						}
						//把accept放入数组中
						if (CreateSocketInformation(AcceptSocket) == FALSE)
							return -1;
					}
					else
					{
						if (GetLastError() != WSAEWOULDBLOCK)
						{
							printf("accept failed\n");
							return -1;
						}
					}
				}

				else //有可读的数据
				{
					if (FD_ISSET(socket_info->socket, &ReadSet))
					{
						Total--; //就绪状态的socket--

						//初始化缓冲区
						memset(socket_info->Buffer, 0 , DATA_BUFSIZE);
						socket_info->DataBuf.buf = socket_info->Buffer;
						socket_info->DataBuf.len = DATA_BUFSIZE;

						DWORD Flags = 0;

						//重叠模型 WSARecv接收数据
						if (WSARecv(socket_info->socket, &(socket_info->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL))
						{
							if (WSAGetLastError() != WSAEWOULDBLOCK)
							{
								printf("wsarecv failed\n");
								return -1;
							}
							continue;
						}
						else
						{
							socket_info->ByteRECV = RecvBytes; //记录接收数据的字节数
							if (RecvBytes == 0)
							{
								FreeSocketInformation(i);
								continue;
							}
							else
							{
								printf(socket_info->DataBuf.buf);
								printf("\n");
							}
						}

					}
				}
			}
			else
			{
				//表明有数据可以发送
				if (FD_ISSET(socket_info->socket, &WriteSet))
				{
					//初始化缓冲区位置;
					socket_info->DataBuf.buf = socket_info->Buffer + socket_info->ByteSEND;
					//初始化缓冲区长度
					socket_info->DataBuf.len = socket_info->ByteRECV - socket_info->ByteSEND;

					if (socket_info->DataBuf.len > 0)
					{
						if (WSASend(socket_info->socket, &(socket_info->DataBuf), 1, &SendBytes, 0, NULL, NULL))
						{
							if (WSAGetLastError() != WSAEWOULDBLOCK)
							{
								printf("WSASend failed\n");
								FreeSocketInformation(i);
							}
							continue;
						}
						else
						{
							socket_info->ByteSEND += SendBytes;
							if (socket_info->ByteSEND == socket_info->ByteRECV) //如果从客户端收到的数据都已经发送到客户端
							{
								socket_info->ByteRECV = 0;
								socket_info->ByteSEND = 0;
							}
						}
					}
				}
			}
		}
	}
	closesocket(ListenSocket);
	closesocket(AcceptSocket);
	WSACleanup();
	return 0;
}

表示根据前面的几部流程还是蛮轻松的。。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值