socket非阻塞模式服务器设计与实现

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


阻塞和非阻塞的区别就是一个阻塞等待,一个直接返回,并返回一些有意义的错误代码。

跳转到代码

思路如下:
1.初始化套接字

2.创建套接字

3.设置socket为非阻塞模式

int ioctlsocket(
  SOCKET s,    //要设置的套接字
  long cmd,    //FIONBIO
  u_long FAR* argp  //非零值
);

注: 关于第二第三个参数,MSDN是这样解释的:

Use FIONBIO with a nonzero argp parameter to enable the nonblocking mode of socket s. The argp parameter is zero if nonblocking is to be disabled. The argp parameter points to an unsigned long value. When a socket is created, it operates in blocking mode by default (nonblocking mode is disabled).
大概意思就是第二个参数为FIONBIO的时候,第三个参数设置为非0值,则表示此套接字被设置为非阻塞模式,0就表示阻塞模式。当一个套接字创建的时候默认是阻塞的。

4.绑定服务器

5.监听

6.接受客户端请求

这一步和阻塞模式的就开始有区别了(前面的操作都不会返回WSAEWOULDBLOCK错误代码),因为accept函数不再阻塞的等待一个连接到来,而是立即返回一个错误代码。

7.接收/发送数据

这一步也和阻塞模式有区别,它们也是直接返回而不在阻塞等待。所以,我们需要根据返回的错误码来判断。

缺点:需要用一个while循环,不断的判断是否有数据到来,是否可以发送数据,对CPU消耗很大。(可以通过sleep函数进行控制循环速度,这样可以减小CPU消耗)

实例:

server.cpp

#include "server.h"

int main()
{
	SOCKET sServer = INVALID_SOCKET;
	SOCKET sClient = INVALID_SOCKET;
	SOCKADDR addrClient = { 0 };
	DWORD dwError = 0;
	if (OpenTCPServer(&sServer,18000, 1,&dwError))  //打开服务器
	{
		if (AcceptClient(sServer, &sClient, &addrClient, sizeof(addrClient)))//接受请求
		{
			ShowMessage((SOCKADDR_IN*)&addrClient);                   //显示连接信息函数
			_beginthreadex(NULL, 0, ThreadRecv,&sClient, 0, NULL);    //开启接收和发送数据函数
			_beginthreadex(NULL, 0, ThreadSend, &sClient, 0, NULL);
		}
	}
	Sleep(10000000); //主线程睡眠
	return 0;
}

client.cpp

#include "client.h"

int main()
{
	SOCKET sClient = INVALID_SOCKET;
	SOCKADDR_IN addrServer = { 0 };
	DWORD dwError = 0;
	addrServer.sin_family = AF_INET;      //设置server地址
	addrServer.sin_port = 18000;
	addrServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (InitSocket(&sClient,addrServer,&dwError,1))  //初始化套接字
	{ 
		if (ConnectServer(&sClient, addrServer))     //连接服务器
		{
			ShowMessage(((SOCKADDR_IN*)&addrServer));//显示连接信息 
			_beginthreadex(NULL, 0, ThreadRecv, &sClient, 0, NULL);  //开启接收和发送线程
			_beginthreadex(NULL, 0, ThreadSend, &sClient, 0, NULL);
		}	
	}
	Sleep(10000000); //主线程睡眠
	return 0;
}

server.h

#ifndef _SERVER_H_
#define _SERVER_H_

#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <conio.h>
#pragma comment(lib,"ws2_32.lib")
#define MAXBUF 126

/*
@function OpenTCPServer             打开TCP服务器
@param  _Out_ SOCKET* sServer		客户端套接字
@param _In_ unsigned short Port     服务器端口
@param  _In_ unsigned long block         0为阻塞,非0为非阻塞
@param  _Out_ DWORD* dwError               错误代码
@return  成功返回TRUE 失败返回FALSE
*/
BOOL OpenTCPServer(_Out_ SOCKET* sServer, _In_ unsigned short Port, _In_ unsigned long block, _Out_ DWORD* dwError)
{
	BOOL bRet = FALSE;
	WSADATA wsaData = { 0 };
	SOCKADDR_IN ServerAddr = { 0 };
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = Port;
	ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	do
	{
		if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
		{
			if (LOBYTE(wsaData.wVersion) == 2 || HIBYTE(wsaData.wVersion) == 2)
			{
				*sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
				if (ioctlsocket(*sServer, FIONBIO, (unsigned long*)&block) != SOCKET_ERROR)
				{
					if (*sServer != INVALID_SOCKET)
					{
						if (SOCKET_ERROR != bind(*sServer, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)))
						{
							if (SOCKET_ERROR != listen(*sServer, SOMAXCONN))
							{
								bRet = TRUE;
								break;
							}
							*dwError = WSAGetLastError();
							closesocket(*sServer);
						}
						*dwError = WSAGetLastError();
						closesocket(*sServer);
					}
					*dwError = WSAGetLastError();
				}
				*dwError = WSAGetLastError();
			}
		}
		*dwError = WSAGetLastError();
	} while (FALSE);
	return bRet;
}

/*
@function AcceptClient              接受客户端连接请求
@param  _In_ SOCKET sServer		    server套接字
@param  _Out_ SOCKET* sClient       client套接字
@param  _Out_ SOCKADDR* ClientAddr  client地址
@param  _In_ int iClientAddrSize    client地址大小
@return  成功返回TRUE 失败返回FALSE
*/
BOOL AcceptClient(_In_ SOCKET sServer, _Out_ SOCKET* sClient, _Out_ SOCKADDR* ClientAddr, _In_ int iClientAddrSize)
{
	DWORD dwError = 0;
	do
	{
		*sClient = accept(sServer, (SOCKADDR*)ClientAddr, &iClientAddrSize);
		if (*sClient == INVALID_SOCKET)
		{
			dwError = WSAGetLastError();
			if (dwError == WSAEWOULDBLOCK)  //还没收到连接请求
				Sleep(100);
		}
	} while (*sClient == INVALID_SOCKET);
	return TRUE;
}

/*
@function RecvData                  接收数据函数
@param  _In_ SOCKET s				套接字
@param  _Out_ char* buf				接收数据缓冲区
@param  _In_ int iSize				缓冲区大小
@return  返回接收到的数据大小
*/
int RecvData(_In_ SOCKET s, _Out_ char* buf, _In_ int iSize)
{
	int iRemain = iSize;  //剩余字节
	int iCur = 0;         //当前读取到的字节
	int iLen = 0;
	while (iRemain)
	{
		iLen = recv(s, &buf[iCur], iSize, 0);
		if (iLen == SOCKET_ERROR)            //这里不用判断WSAEWOULDBLOCK错误代码
			break;
		iCur += iLen;
		iRemain = iSize - iLen;  //求出还剩余多少字节没有读完
	}
	return iCur;
}

/*
@function RecvData                  发送数据函数
@param  _In_ SOCKET s				套接字
@param  _In_ char* buf				发送的数据
@param  _In_ int iSize				数据大小
@return  返回接收到的数据大小
*/
int SendData(_In_ SOCKET s, _In_ char* buf, _In_ int iSize)
{
	int iRemain = iSize;  //剩余字节
	int iCur = 0;         //当前发送的字节
	int iLen = 0;
	while (iRemain)
	{
		iLen = send(s, &buf[iCur], iSize, 0);
		if (iLen == SOCKET_ERROR)
		{
			DWORD dwError = GetLastError();
			if (dwError == WSAEWOULDBLOCK)
				continue;
			else                  //其它错误
				return -1;
		}
		iCur += iLen;
		iRemain = iSize - iLen;  //求出还剩余多少字节没有发送
	}
	return iCur;
}

//接收数据线程
unsigned int __stdcall ThreadRecv(void* lparam)
{
	char buf[MAXBUF] = { 0 };
	int iRet = 0;
	while (1)
	{
		Sleep(100);   //这个一定要放在接收数据函数之前,否则一直循环,会导致CPU狂涨
		iRet = RecvData(*(SOCKET*)lparam, buf, MAXBUF);
		if (iRet == 0)
			continue;
		printf("Recv:%s\n", buf);
		memset(buf, 0, MAXBUF);
	}
	return 0;
}

//发送数据线程
unsigned int __stdcall ThreadSend(void* lparam)
{
	char buf[MAXBUF] = { 0 };
	while (1)
	{
		int c = getch();
		if (c == 72 || c == 0 || c == 68)//为了显示美观,加一个无回显的读取字符函数
			continue;
		memset(buf, 0, MAXBUF);
		printf("Send:");
		gets_s(buf);
		if (strlen(buf) == 0)
			continue;
		SendData(*(SOCKET*)lparam, buf, strlen(buf) + 1);  //发送数据
		Sleep(100);
	}
	return 0;
}

//显示信息函数
void ShowMessage(SOCKADDR_IN* addr)
{
	printf("Success accept a connect from IP: %s,port: %d\n", inet_ntoa(addr->sin_addr), htons(addr->sin_port));
}


#endif //_SERVER_H_

client.h

#ifndef _CLIENT_H_
#define  _CLIENT_H_

#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <conio.h>
#pragma comment(lib,"ws2_32.lib")
#define MAXBUF 126

/*
@function	InitSocket					   初始化套接字
@param		_Out_  SOCKET* ClientSocket	   client套接字
@param		_Out_ SOCKADDR_IN addrServer   server地址
@param	    _Out_ DWORD* dwError           错误代码
@param		_In_ unsigned long block       0为阻塞,非0为非阻塞
@return		BOOL						   成功返回TRUE 失败返回FALSE
*/
BOOL InitSocket(_Out_ SOCKET* ClientSocket, _Out_ SOCKADDR_IN addrServer, _Out_ DWORD* dwError, _In_ unsigned long block)
{
	BOOL bRet = FALSE;
	WSADATA wsaData = { 0 };
	do
	{
		if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
		{
			if (LOBYTE(wsaData.wVersion) == 2 || HIBYTE(wsaData.wVersion) == 2)
			{
				*ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
				if (ioctlsocket(*ClientSocket, FIONBIO, (unsigned long*)&block) != SOCKET_ERROR)
				{
					if (*ClientSocket != INVALID_SOCKET)
					{
						bRet = TRUE;
						break;
					}
					*dwError = WSAGetLastError();
				}
				*dwError = WSAGetLastError();
			}
		}
		*dwError = WSAGetLastError();
	} while (FALSE);
	return bRet;
}

/*
@function	ConnectServer				   连接服务器
@param		_Out_  SOCKET* ClientSocket	   client套接字
@param		_Out_ SOCKADDR_IN addrServer   server地址
@return		BOOL						   成功返回TRUE 失败返回FALSE
*/
BOOL ConnectServer(SOCKET* sClient, SOCKADDR_IN addrServer)
{
	DWORD dwError = 0;
	while (1)
	{
		if (SOCKET_ERROR == connect(*sClient, (SOCKADDR*)&addrServer, sizeof(addrServer)))
		{
			dwError = WSAGetLastError();
			if (dwError == WSAEWOULDBLOCK || dwError == WSAEINVAL)
			{
				Sleep(100);
				continue;
			}
			else if (dwError == WSAEISCONN)
				break;
			else
				return FALSE;
		}
		else
			break;
	}
	return TRUE;
}

/*
@function RecvData                  接收数据函数
@param  _In_ SOCKET s				套接字
@param  _Out_ char* buf				接收数据缓冲区
@param  _In_ int iSize				缓冲区大小
@return  返回接收到的数据大小
*/
int RecvData(SOCKET s, char* buf, int iSize)
{
	int iRemain = iSize;  //剩余字节
	int iCur = 0;         //当前读取到的字节
	int iLen = 0;
	while (iRemain)
	{
		iLen = recv(s, &buf[iCur], iSize, 0);
		if (iLen == SOCKET_ERROR)
			break;
		iCur += iLen;
		iRemain = iSize - iLen;  //求出还剩余多少字节没有读完
	}
	return iCur;
}

/*
@function RecvData                  发送数据函数
@param  _In_ SOCKET s				套接字
@param  _In_ char* buf				发送的数据
@param  _In_ int iSize				数据大小
@return  返回接收到的数据大小
*/
int SendData(SOCKET s, char* buf, int iSize)
{
	int iRemain = iSize;  //剩余字节
	int iCur = 0;         //当前发送的字节
	int iLen = 0;
	while (iRemain)
	{
		iLen = send(s, &buf[iCur], iSize, 0);
		if (iLen == SOCKET_ERROR)
		{
			DWORD dwError = GetLastError();
			if (dwError == WSAEWOULDBLOCK)
				continue;
			else                  //其它错误
				return -1;
		}
		iCur += iLen;
		iRemain = iSize - iLen;  //求出还剩余多少字节没有发送
	}
	return iCur;
}

//接收数据线程
unsigned int __stdcall ThreadRecv(void* lparam)
{
	char buf[MAXBUF] = { 0 };
	int iRet = 0;
	while (1)
	{
		Sleep(100);
		memset(buf, 0, MAXBUF);
		iRet = RecvData(*(SOCKET*)lparam, buf, MAXBUF);
		if (iRet == 0)
			continue;
		printf("Recv:%s\n", buf);
	}
	return 0;
}

//发送数据线程
unsigned int __stdcall ThreadSend(void* lparam)
{
	char buf[MAXBUF] = { 0 };
	while (1)
	{
		int c = getch();
		if (c == 72 || c == 0 || c == 68)//为了显示美观,加一个无回显的读取字符函数
			continue;
		memset(buf, 0, MAXBUF);
		printf("Send:");
		gets_s(buf);
		if (strlen(buf) == 0)
			continue;
		SendData(*(SOCKET*)lparam, buf, strlen(buf) + 1);
		Sleep(100);
	}
	return 0;
}

//显示信息
void ShowMessage(SOCKADDR_IN* addr)
{
	printf("Success connect server IP: %s,port: %d\n", inet_ntoa(addr->sin_addr), htons(addr->sin_port));
}



#endif //_CLIENT_H_

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值