winsocket 多线程阻塞服务器和客户端

多线程服务器流程
服务端:

#undef UNICODE

#define WIN32_LEAN_AND_MEAN
#define ASSERT assert

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>   
#include <cassert> 
#include <list> 

using namespace std;


static const int c_iPort = 10001;
bool GraceClose(SOCKET *ps);

#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")


int  main(void)//int  __cdecl main(void) 默认的函数调用,不规定参数多少,_stdcall严格控制参数产生
{
	WSADATA wsaData;//1.1创建一个名为wsaData 的WSADATA对象。wsaData(使用版本,最高版本....struct结构体)
	//调用WSAStartup并将其值返回为整数并检查错误
	int iResult=SOCKET_ERROR;//1.1

	SOCKET ListenSocket = INVALID_SOCKET;//2.2监听invalid(病人) 为服务器创建一个名为ListenSocket 的SOCKET对象,以侦听客户端连接
	SOCKET ClientSocket = INVALID_SOCKET;//5.1创建一个名为ClientSocket 的临时SOCKET对象,以接受来自客户端的连接

	struct addrinfo *result = NULL;//2.1


	// 1.2初始化 Winsock
	ZeroMemory(&wsaData, sizeof(WSADATA));
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);//WSAStartup函数启动使用WS2_32.DLL,Winsock的2.2版
	if (iResult != 0) {
		printf("WSAStartup failed with error: %d\n", iResult);
		return 1;
	}
	//建立服务端程序的监听嵌套字
	ListenSocket = socket(AF_INET, SOCK_STREAM, 0);//调用套接字函数,并将其值返回到ListenSocket变量
	if (ListenSocket == INVALID_SOCKET)//2.4检查是否有错误,以确保该套接字是有效的套接字 INVALID_SOCKET为一个嵌套字对象
	{
		printf("socket failed with error: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}

	sockaddr_in adrServ;
	ZeroMemory(&adrServ, sizeof(sockaddr_in));
	adrServ.sin_family = AF_INET;
	adrServ.sin_port = htons(c_iPort);
	adrServ.sin_addr.s_addr = INADDR_ANY;
	

	// 3.1绑定嵌套字
	iResult = bind(ListenSocket, (sockaddr*)&adrServ, sizeof(sockaddr_in));
	if (iResult == SOCKET_ERROR) {
		printf("bind failed with error: %d\n", WSAGetLastError());//如果没有错误发生,则 绑定返回零
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}

	freeaddrinfo(result);//3.2释放该分配的内存
	//4.1监听嵌套字
	iResult = listen(ListenSocket, FD_SETSIZE);//backlog参数设置为SOMAXCONN,允许在队列中允许最大合理数量的挂起连接
	if (iResult == SOCKET_ERROR) {
		printf("listen failed with error: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}

	// 将套接口从阻塞状态设置到非阻塞状态 
	unsigned long ulMode = 1;
	iResult = ioctlsocket(ListenSocket, FIONBIO, &ulMode);
	ASSERT(SOCKET_ERROR != iResult);


	fd_set fsListen;   //fd_set为套字集合
	FD_ZERO(&fsListen); //FD_ZERO(*set)将集合set清空  
	fd_set fsRead;
	FD_ZERO(&fsRead);

	timeval tv;
	tv.tv_sec = 1;//同步 时间计算获得时间延迟 tv_sec 代表多少秒
	tv.tv_usec = 0;//tv_usec 代表多少微秒 1000000 微秒 = 1秒

	list<SOCKET> sl;
	int i = 2;

	for (;;)
	{
		// 接收来自客户端的连接, 并将新建的套接字加入套接字列表中   
		FD_SET(ListenSocket, &fsListen);//FD_SET(s, *set)将套接字s加入到集合set中
		iResult = select(1, &fsListen, NULL, NULL, &tv);//选择监听哪一个I/O
		if (iResult > 0)//端口循环
		{
			sockaddr_in adrClit;
			int iLen = sizeof(sockaddr_in);
			ZeroMemory(&adrClit, iLen);
			SOCKET skAccept = accept(ListenSocket, (sockaddr*)&adrClit, &iLen);
			ASSERT(INVALID_SOCKET != skAccept);//断言,存在程序停止,不存在程序继续进行
			sl.push_back(skAccept);//回调
			cout << "新的连接 " << skAccept << ", c_ip: "
				<< inet_ntoa(adrClit.sin_addr) << ", c_port: " << ntohs(adrClit.sin_port) << endl;
		}
		// 将套接字列表中的套接字加入到可读套接字集合中,以便在可以检测集合中的套接字是否有数据可读   
		FD_ZERO(&fsRead);//清除

		for (list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter)//迭代程序
		{
			FD_SET(*iter, &fsRead);
		}
		// 检测集合中的套接字是否有数据可读   
		iResult = select(sl.size(), &fsRead, NULL, NULL, &tv);
		if (iResult > 0)
		{
			for (list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter)
			{
				// 如果有数据可读, 则遍历套接字列表中的所有套接字,检测出有数据可读的套接字   
				iResult = FD_ISSET(*iter, &fsRead);// FD_ISSET(s, *set)判断套接字s是否在集合中有信号


				if (iResult > 0)
				{
					// 读取套接字上的数据   
					const int c_iBufLen = 512;
					char recvBuf[c_iBufLen + 1] = { '\0' };

					int iRecv = SOCKET_ERROR;

					iRecv = recv(*iter, recvBuf, c_iBufLen, 0);

					if (iRecv <= 0)// 读取出现错误或者对方关闭连接   
					{
						iRecv == 0 ? cout << "Connection shutdown at socket " << *iter << endl ://优雅关闭
							cout << "Connection recv error at socket " << *iter << endl;// 粗暴关闭
						iResult = GraceClose(&(*iter));//优雅关闭
						ASSERT(iResult);
					}
					else
					{
						recvBuf[iRecv] = '\0';
						cout << "Server recved message from socket " << *iter << ": " << recvBuf << endl;

						// 创建可写集合   
						FD_SET fsWrite;
						FD_ZERO(&fsWrite);
						FD_SET(*iter, &fsWrite);
						// 如果可可写套接字, 则向客户端发送数据   
						iResult = select(1, NULL, &fsWrite, NULL, &tv);
						if (iResult <= 0)
						{
							int iWrite = SOCKET_ERROR;
							iWrite = send(*iter, recvBuf, iRecv, 0);
							if (SOCKET_ERROR == iWrite)
							{
								cout << "Send message error at socket " << *iter << endl;
								iResult = GraceClose(&(*iter));
								ASSERT(iResult);
							}
						}
					}
				}
			}  //for 
			sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接字在关闭后被设置为无效  invalid 
		}  //if 
	}  //for (;;) 

	// 将套接字设置回阻塞状态 
	ulMode = 0;
	iResult = ioctlsocket(ListenSocket, FIONBIO, &ulMode);
	ASSERT(SOCKET_ERROR != iResult);

	iResult = GraceClose(&ListenSocket);
	ASSERT(iResult);



	// shutdown the connection since we're done6.断开服务器
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("shutdown failed with error: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}

	// cleanup
	closesocket(ClientSocket);//关闭socket
	WSACleanup();//WSACleanup函数终止使用Winsock 2 DLL(的WS2_32.DLL)

	system("pause");
	return 0;
}

bool GraceClose(SOCKET *ps)
{
	const int c_iBufLen = 512;
	char szBuf[c_iBufLen + 1] = { '\0' };

	// 关闭该套接字的连接   
	int iRet = shutdown(*ps, SD_SEND);
	while (recv(*ps, szBuf, c_iBufLen, 0) > 0);
	if (SOCKET_ERROR == iRet)
	{
		return false;
	}

	// 清理该套接字的资源   
	iRet = closesocket(*ps);
	if (SOCKET_ERROR == iRet)
	{
		return false;
	}

	*ps = INVALID_SOCKET;
	return true;
}

客户端

#define WIN32_LEAN_AND_MEAN
#define ASSERT assert

#include <iostream>   
#include <cassert> 
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

using namespace std;

static const char c_szIP[] = "127.0.0.1";
static const int  c_iPort = 10001;
bool GraceClose(SOCKET *ps); 
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
//#define DEFAULT_BUFLEN 512
//#define DEFAULT_PORT "27015"

int __cdecl main(int argc, char **argv)
{
	WSADATA wsaData;
	int iResult = SOCKET_ERROR;
	ZeroMemory(&wsaData, sizeof(WSADATA));

	// Initialize Winsock初始化winsocket
	SOCKET ConnectSocket = INVALID_SOCKET;//1.1创建嵌套字
	iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);//iresult返回的值是多少0
	if (iResult != 0) {
		printf("WSAStartup failed with error: %d\n", iResult);
		return 1;
	}  

	struct addrinfo *result = NULL,hints;

		// 建立连接套接字
		ConnectSocket = socket(AF_INET, SOCK_STREAM, 0);
		if (ConnectSocket == INVALID_SOCKET) {
			printf("socket failed with error: %ld\n", WSAGetLastError());
			WSACleanup();
			return 1;
		}

		// 初始化连接套接字地址信息
		sockaddr_in adrServ;
		ZeroMemory(&adrServ, sizeof(sockaddr_in));
		adrServ.sin_family = AF_INET;
		adrServ.sin_port = htons(c_iPort);
		adrServ.sin_addr.s_addr = inet_addr(c_szIP);

		// 将套接口从阻塞状态设置到非阻塞状态 
		unsigned long ulEnable = 1;
		iResult = ioctlsocket(ConnectSocket, FIONBIO, &ulEnable);
		ASSERT(SOCKET_ERROR != iResult);

		fd_set fsWrite;
		TIMEVAL tv;

		tv.tv_sec = 1;
		tv.tv_usec = 0;

		cout << "客户端开始连接到服务器..." << endl;

		for (;;)
		{
			// 使用非阻塞方式连接服务器,请注意connect操作的返回值总是为SOCKET_ERROR   
			iResult = connect(ConnectSocket, (sockaddr*)&adrServ, sizeof(sockaddr_in)); //sockaddr_in
			int iErrorNo = SOCKET_ERROR;
			int iLen = sizeof(int);

			// 如果getsockopt返回值不为0,则说明有错误出现
			if (SOCKET_ERROR == iResult && 0 != getsockopt(ConnectSocket, SOL_SOCKET, SO_ERROR, (char*)&iErrorNo, &iLen))
			{
				cout << "连接到服务器时出错;错误号是 " << iErrorNo
					<< ". 程序现在将退出." << endl;
				exit(-1);
			}

			FD_ZERO(&fsWrite);
			FD_SET(ConnectSocket, &fsWrite);
			// 如果集合fsWrite中的套接字有信号,则说明连接成功,此时iRet的返回值大于0   
			iResult = select(1, NULL, &fsWrite, NULL, &tv);
			if (iResult > 0)
			{
				cout << "Successed connect to the server..." << endl;
				break;
			}
			cout << "retrying" << endl;
		}  //for


		for (;;)
		{
			const int c_iBufLen = 512;
			char szBuf[c_iBufLen + 1] = { '\0' };
			cout << "向服务器发送消息:";
			cin >> szBuf;
			if (0 == strcmp("退出", szBuf))
			{
				break;
			}

			FD_ZERO(&fsWrite);
			FD_SET(ConnectSocket, &fsWrite);

			// 如果集合fsWrite中的套接字有信号, 则可以用send操作发数据   
			iResult = select(1, NULL, &fsWrite, NULL, &tv);
			if (0 < iResult)
			{
				iResult = send(ConnectSocket, szBuf, strlen(szBuf), 0);
				if (SOCKET_ERROR == iResult)
				{
					cout << "send error." << endl;
					break;
				}

				fd_set fsRead;
				FD_ZERO(&fsRead);
				FD_SET(ConnectSocket, &fsRead);

				// 如果集合fsRead中的套接字有信号, 则可以用recv操作读数据   
				iResult = select(1, &fsRead, NULL, NULL, &tv);
				if (0 < iResult)
				{
					iResult = recv(ConnectSocket, szBuf, c_iBufLen, 0);
					if (0 == iResult)
					{
						cout << "connection shutdown." << endl;
						break;
					}
					else if (SOCKET_ERROR == iResult)
					{
						cout << "recv error." << endl;
						break;
					}
					szBuf[iResult] = '\0';
					cout << szBuf << endl;
				}
			}  //if 
		}  //for 

		// 将套接字设置回阻塞状态   
		ulEnable = 0;
		iResult = ioctlsocket(ConnectSocket, FIONBIO, &ulEnable);
		ASSERT(SOCKET_ERROR != iResult);

		// 关闭监听套接字   
		iResult = GraceClose(&ConnectSocket);
		ASSERT(iResult);

		// cleanup
		closesocket(ConnectSocket);
		WSACleanup();

		system("pause");

		return 0;

	}


	bool GraceClose(SOCKET *ps)
	{
		const int c_iBufLen = 512;
		char szBuf[c_iBufLen + 1] = { '\0' };

		// 关闭该套接字的连接   
		int iRet = shutdown(*ps, SD_SEND);
		while (recv(*ps, szBuf, c_iBufLen, 0) > 0);
		if (SOCKET_ERROR == iRet)
		{
			return false;
		}

		// 清理该套接字的资源   
		iRet = closesocket(*ps);
		if (SOCKET_ERROR == iRet)
		{
			return false;
		}

		*ps = INVALID_SOCKET;
		return true;
	}
	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值