编程语言
首页 > 编程语言> > Winsock 编程详解

Winsock 编程详解

作者:互联网



Winsock 编程



目录

  1. 通用函数讲解
    1. WSAStartup
    2. WSACleanup
    3. socket
    4. closesocket
  2. 面向连接的函数讲解
    1. bind
    2. listen
    3. accept
    4. connect
    5. send
    6. recv
  3. 面向非连接的函数讲解
    1. sendto
    2. recvfrom
  4. 字节顺序
  5. 获取本地 ip 填充 sockaddr_in
    1. gethostname
    2. gethostbyname
    3. 获取本地 ip 填充 sockaddr_in
  6. 入门 TCP/UDP 编程
    1. TCP 基本编程
    2. UDP 基本编程
    3. TCP 封装
    4. UDP 封装
  7. 借助多线程实现 TCP 全双工通信
  8. TCP 多连接多请求设计
  9. Winsock 其他函数
    1. getsockname
    2. setsockopt
    3. getsockopt
    4. shutdown
    5. ioctlsocket
    6. WSAAsyncSelect



一、通用函数讲解

1、WSAStartup

2、WSACleanup

3、socket

4、closesocket



二、面向连接的函数讲解

1、bind

2、listen

3、accept

4、connect

5、send

6、recv



三、面向非连接的函数讲解

1、sendto

2、recvfrom



四、字节顺序



五、获取本地 ip 填充 sockaddr_in

1、gethostname

2、gethostbyname

3、获取本地 ip 填充 sockaddr_in



六、入门 TCP/UDP 编程

1、TCP 基本编程

/*
 * TCP 服务端程序
*/
#include <iostream>
using namespace std;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")

/* WSAStartup()->socket()->bind()->listen()->accept()->send()/recv()->closesocket()->WSACleanup() */

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return WSAGetLastError();

	SOCKET socketServer;
	socketServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketServer == INVALID_SOCKET) return WSAGetLastError();

	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(8082);
	inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

	if (bind(socketServer, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0) return WSAGetLastError();
	if (listen(socketServer, 1) != 0) return WSAGetLastError();

	sockaddr_in clientAddr;
	int nSize = sizeof(clientAddr);
	SOCKET socketClient = accept(socketServer, (SOCKADDR*)&clientAddr, &nSize);
	if (socketClient == INVALID_SOCKET) return WSAGetLastError();

	char ipbuf[20];
	cout << "客户端成功接入,ip - port: " << inet_ntop(AF_INET, &clientAddr.sin_addr, ipbuf, 20) << " - " << ntohs(clientAddr.sin_port) << endl;

	cout << "send: server send" << endl;
	cout << "send ret: " << send(socketClient, "server send", strlen("server send") + 1, 0) << endl;
	char buf[256];
	cout << "recv ret: " << recv(socketClient, buf, 256, 0) << endl;
	cout << "recv: " << buf << endl;

	closesocket(socketClient);
	closesocket(socketServer);
	WSACleanup();
	return 0;
}


/*
 * TCP 客户端程序
*/
#include <iostream>
using namespace std;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")

/* WSAStartup()->socket()->connet()->send()/recv()->closesocket()->WSACleanup() */

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return WSAGetLastError();

	SOCKET socketServer;
	socketServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketServer == INVALID_SOCKET) return WSAGetLastError();

	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(8082);
	inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

	if (connect(socketServer, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0) return WSAGetLastError();

	char buf[256];
	cout << "recv ret: " << recv(socketServer, buf, 256, 0) << endl;
	cout << "recv: " << buf << endl;
	cout << "send: client send" << endl;
	cout << "send ret: " << send(socketServer, "client send", strlen("client send") + 1, 0) << endl;

	closesocket(socketServer);
	WSACleanup();
	return 0;
}
/*
 * TCP 服务端程序输出
*/
客户端成功接入,ip - port: 127.0.0.1 - 49291
send: server send
send ret: 12
recv ret: 12
recv: client send

/*
 * TCP 客户端程序输出
*/
recv ret: 12
recv: server send
send: client send
send ret: 12

2、UDP 基本编程

/*
 * UDP 服务端程序
*/
#include <iostream>
using namespace std;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")

/* WSAStartup()->socket()->bind()->sendto()/recvfrom()->closesocket()->WSACleanup() */

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return WSAGetLastError();

	SOCKET mySocket;
	mySocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (mySocket == INVALID_SOCKET) return WSAGetLastError();

	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(8082);
	inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);

	if (bind(mySocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0) return WSAGetLastError();

	sockaddr_in clientAddr;
	int nSize = sizeof(clientAddr);

	char buf[256];
	cout << "recvfrom ret: " << recvfrom(mySocket, buf, 256, 0, (SOCKADDR*)&clientAddr, &nSize) << endl;
	cout << "recvfrom: " << buf << endl;

	char ipbuf[20];
	cout << "客户端成功接入,ip - port: " << inet_ntop(AF_INET, &clientAddr.sin_addr, ipbuf, 20) << " - " << ntohs(clientAddr.sin_port) << endl;

	cout << "sendto: server send" << endl;
	cout << "sendto ret: " << sendto(mySocket, "server send", strlen("server send") + 1, 0, (SOCKADDR*)&clientAddr, sizeof(clientAddr)) << endl;

	closesocket(mySocket);
	WSACleanup();
	return 0;
}


/*
 * UDP 客户端程序
*/
#include <iostream>
using namespace std;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")

/* WSAStartup()->socket()->sendto()/recvfrom()->closesocket()->WSACleanup() */

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return WSAGetLastError();

	SOCKET mySocket;
	mySocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (mySocket == INVALID_SOCKET) return WSAGetLastError();

	sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(8082);
	inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	int nSize = sizeof(serverAddr);

	cout << "sendto: server send" << endl;
	cout << "sendto ret: " << sendto(mySocket, "server send", strlen("server send") + 1, 0, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) << endl;
	char buf[256];
	cout << "recvfrom ret: " << recvfrom(mySocket, buf, 256, 0, (SOCKADDR*)&serverAddr, &nSize) << endl;
	cout << "recvfrom: " << buf << endl;

	closesocket(mySocket);
	WSACleanup();
	return 0;
}
/*
 * UDP 服务端程序输出
*/
recvfrom ret: 12
recvfrom: server send
客户端成功接入,ip - port: 127.0.0.1 - 59939
sendto: server send
sendto ret: 12

/*
 * UDP 客户端程序输出
*/
sendto: server send
sendto ret: 12
recvfrom ret: 12
recvfrom: server send

3、TCP 封装

/*
 * TCP 服务端程序
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>

/* WSAStartup()->socket()->bind()->listen()->accept()->send()/recv()->closesocket()->WSACleanup() */

class TCPServer
{
public:
	TCPServer()
	{
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) exit(EXIT_FAILURE);

		socketServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (socketServer == INVALID_SOCKET)
		{
			auto code = WSAGetLastError();
			WSACleanup();
			exit(code);
		}

		char hostname[MAXBYTE];
		gethostname(hostname, MAXBYTE);
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(8082);
		serverAddr.sin_addr.S_un.S_addr = ((struct in_addr*)gethostbyname(hostname)->h_addr)->s_addr;

		if (bind(socketServer, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0) clear_and_exit();
		if (listen(socketServer, 1) != 0) clear_and_exit();
	}

	~TCPServer()
	{
		closesocket(socketClient);
		closesocket(socketServer);
		WSACleanup();
	}

	void Accept()
	{
		int nSize = sizeof(clientAddr);
		socketClient = accept(socketServer, (SOCKADDR*)&clientAddr, &nSize);
		if (socketClient == INVALID_SOCKET) clear_and_exit();
	}

	auto Send(std::string toSend)
	{
		return send(socketClient, toSend.c_str(), strlen(toSend.c_str()) + 1, 0);
	}

	auto Recv()
	{
		char buf[MAXBYTE];
		recv(socketClient, buf, MAXBYTE, 0);
		return std::string(buf);
	}
	auto Recv(std::string& toRecv, bool append = false)
	{
		char buf[MAXBYTE];
		auto ret = recv(socketClient, buf, MAXBYTE, 0);
		if (!append) toRecv = std::string(buf);
		else toRecv.append(buf);
		return ret;
	}

	auto getIp(bool client = true)
	{
		char ipBuf[20];
		return std::string(inet_ntop(AF_INET, &(client ? clientAddr : serverAddr).sin_addr, ipBuf, 20));
	}
	auto getPort(bool client = true)
	{
		return ntohs((client ? clientAddr : serverAddr).sin_port);
	}
	auto getIpPort(bool client = true)
	{
		return getIp(client) + " - " + std::to_string(getPort(client));
	}

private:
	void clear_and_exit()
	{
		auto code = WSAGetLastError();
		closesocket(socketServer);
		WSACleanup();
		exit(auto code = WSAGetLastError(););
	}

private:
	WSADATA wsaData;
	SOCKET socketServer;
	sockaddr_in serverAddr;
	SOCKET socketClient;
	sockaddr_in clientAddr;
};

int main()
{
	TCPServer server;
	cout << "local ip - port: " << server.getIpPort(false) << endl;

	server.Accept();
	cout << "客户端成功接入 ip - port: " << server.getIpPort() << endl;

	cout << "Send: " << "Hello Client" << endl;
	server.Send("Hello Client");
	cout << "Recv: " << server.Recv() << endl;

	return 0;
}


/*
 * TCP 客户端程序
*/
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>

/* WSAStartup()->socket()->connet()->send()/recv()->closesocket()->WSACleanup() */

class TCPClient
{
public:
	TCPClient()
	{
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) exit(EXIT_FAILURE);

		socketServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (socketServer == INVALID_SOCKET)
		{
			auto code = WSAGetLastError();
			WSACleanup();
			exit(code);
		}

		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(8082);
		inet_pton(AF_INET, "192.168.43.5", &serverAddr.sin_addr);
	}

	~TCPClient()
	{
		closesocket(socketServer);
		WSACleanup();
	}

	void Connect()
	{
		if (connect(socketServer, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0)
		{
			auto code = WSAGetLastError();
			closesocket(socketServer);
			WSACleanup();
			exit(code);
		}
	}

	auto Send(std::string toSend)
	{
		return send(socketServer, toSend.c_str(), strlen(toSend.c_str()) + 1, 0);
	}

	auto Recv()
	{
		char buf[MAXBYTE];
		recv(socketServer, buf, MAXBYTE, 0);
		return std::string(buf);
	}
	auto Recv(std::string& toRecv, bool append = false)
	{
		char buf[MAXBYTE];
		auto ret = recv(socketServer, buf, MAXBYTE, 0);
		if (!append) toRecv = std::string(buf);
		else toRecv.append(buf);
		return ret;
	}

	auto getIp()
	{
		char ipBuf[20];
		return std::string(inet_ntop(AF_INET, &serverAddr.sin_addr, ipBuf, 20));
	}
	auto getPort()
	{
		return ntohs(serverAddr.sin_port);
	}
	auto getIpPort()
	{
		return getIp() + " - " + std::to_string(getPort());
	}

private:
	WSADATA wsaData;
	SOCKET socketServer;
	sockaddr_in serverAddr;
};

int main()
{
	TCPClient client;

	client.Connect();
    
	cout << "Recv: " << client.Recv() << endl;
	cout << "Send: " << "Hello Server" << endl;
	client.Send("Hello Server");

	return 0;
}
/*
 * TCP 服务端程序输出
*/
local ip - port: 192.168.43.5 - 8082
客户端成功接入 ip - port: 192.168.43.5 - 51249
Send: Hello Client
Recv: Hello Server

/*
 * TCP 客户端程序输出
*/
Recv: Hello Client
Send: Hello Server

4、UDP 封装

/*
 * UDP 服务端程序
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>

/* WSAStartup()->socket()->bind()->sendto()/recvfrom()->closesocket()->WSACleanup() */

class UDPServer
{
public:
	UDPServer()
	{
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) exit(EXIT_FAILURE);

		mySocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if (mySocket == INVALID_SOCKET)
		{
			auto code = WSAGetLastError();
			WSACleanup();
			exit(code);
		}

		char hostname[MAXBYTE];
		gethostname(hostname, MAXBYTE);
		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(8082);
		serverAddr.sin_addr.S_un.S_addr = ((struct in_addr*)gethostbyname(hostname)->h_addr)->s_addr;

		if (bind(mySocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0)
		{
			auto code = WSAGetLastError();
			closesocket(mySocket);
			WSACleanup();
			exit(code);
		}
	}

	~UDPServer()
	{
		closesocket(mySocket);
		WSACleanup();
	}

	auto Sendto(std::string toSend)
	{
		return sendto(mySocket, toSend.c_str(), strlen(toSend.c_str()) + 1, 0, (SOCKADDR*)&clientAddr, sizeof(clientAddr));
	}

	auto Recvfrom()
	{
		char buf[MAXBYTE];
		recvfrom(mySocket, buf, 256, 0, (SOCKADDR*)&clientAddr, &nSize);
		return std::string(buf);
	}
	auto Recv(std::string& toRecv, bool append = false)
	{
		char buf[MAXBYTE];
		auto ret = recvfrom(mySocket, buf, 256, 0, (SOCKADDR*)&clientAddr, &nSize);
		if (!append) toRecv = std::string(buf);
		else toRecv.append(buf);
		return ret;
	}

	auto getIp(bool client = true)
	{
		char ipBuf[20];
		return std::string(inet_ntop(AF_INET, &(client ? clientAddr : serverAddr).sin_addr, ipBuf, 20));
	}
	auto getPort(bool client = true)
	{
		return ntohs((client ? clientAddr : serverAddr).sin_port);
	}
	auto getIpPort(bool client = true)
	{
		return getIp(client) + " - " + std::to_string(getPort(client));
	}

private:
	WSADATA wsaData;
	SOCKET mySocket;
	sockaddr_in serverAddr;
	sockaddr_in clientAddr;
	int nSize = sizeof(clientAddr);
};

int main()
{
	UDPServer server;
	cout << "local ip - port: " << server.getIpPort(false) << endl;

	cout << "Recv: " << server.Recvfrom() << endl;
	cout << "客户端成功接入 ip - port: " << server.getIpPort() << endl;
	cout << "Send: " << "Hello Client" << endl;
	server.Sendto("Hello Client");

	return 0;
}


/*
 * UDP 客户端程序
*/
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>

/* WSAStartup()->socket()->sendto()/recvfrom()->closesocket()->WSACleanup() */

class UDPClient
{
public:
	UDPClient()
	{
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) exit(EXIT_FAILURE);

		mySocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
		if (mySocket == INVALID_SOCKET)
		{
			auto code = WSAGetLastError();
			WSACleanup();
			exit(code);
		}

		serverAddr.sin_family = AF_INET;
		serverAddr.sin_port = htons(8082);
		inet_pton(AF_INET, "192.168.43.5", &serverAddr.sin_addr);
	}

	~UDPClient()
	{
		closesocket(mySocket);
		WSACleanup();
	}

	auto Sendto(std::string toSend)
	{
		return sendto(mySocket, toSend.c_str(), strlen(toSend.c_str()) + 1, 0, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
	}

	auto Recvfrom()
	{
		char buf[MAXBYTE];
		recvfrom(mySocket, buf, MAXBYTE, 0, (SOCKADDR*)&serverAddr, &nSize);
		return std::string(buf);
	}
	auto Recvfrom(std::string& toRecv, bool append = false)
	{
		char buf[MAXBYTE];
		auto ret = recvfrom(mySocket, buf, MAXBYTE, 0, (SOCKADDR*)&serverAddr, &nSize);
		if (!append) toRecv = std::string(buf);
		else toRecv.append(buf);
		return ret;
	}

	auto getIp()
	{
		char ipBuf[20];
		return std::string(inet_ntop(AF_INET, &serverAddr.sin_addr, ipBuf, 20));
	}
	auto getPort()
	{
		return ntohs(serverAddr.sin_port);
	}
	auto getIpPort()
	{
		char ipBuf[20];
		return getIp() + " - " + std::to_string(getPort());
	}

private:
	WSADATA wsaData;
	SOCKET mySocket;
	sockaddr_in serverAddr;
	int nSize = sizeof(serverAddr);
};

int main()
{
	UDPClient client;

	cout << "Send: " << "Hello Server" << endl;
	client.Sendto("Hello Server");
	cout << "Recv: " << client.Recvfrom() << endl;

	return 0;
}
/*
 * UDP 服务端程序输出
*/
local ip - port: 192.168.43.5 - 8082
Recv: Hello Server
客户端成功接入 ip - port: 192.168.43.5 - 54075
Send: Hello Client

/*
 * UDP 客户端程序输出
*/
Send: Hello Server
Recv: Hello Client



七、借助多线程实现 TCP 全双工通信

// 为避免标准输出的竞争,发送消息通过检测按键,将相应值拷贝四次后进行发送
// 如:按下 1,发送为 11111

/*
 * TCP 服务端程序
 * TCPServer 类见 [六-3] TCP 封装
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>
#include <thread>
#include <atomic>
#include <conio.h>

/* WSAStartup()->socket()->bind()->listen()->accept()->send()/recv()->closesocket()->WSACleanup() */

// 同 [六-3] TCP 封装
class TCPServer;

int main()
{
	TCPServer server;
	cout << "local ip - port: " << server.getIpPort(false) << endl << endl;
	cout << "等待客户端接入..." << endl;

	std::atomic_bool disconnect = false;
	std::atomic_flag ostream_spin_lock = ATOMIC_FLAG_INIT;

	while (true)
	{
		server.Accept();
		cout << "客户端成功接入, ip - port: " << server.getIpPort() << endl;
		disconnect = false;

		std::thread thread_recv(
			[&] {
				while (true)
				{
					std::string recvMsg;
					auto recvRet = server.Recv(recvMsg);
					if (recvRet == SOCKET_ERROR)
					{
						disconnect.store(true);
						return;
					}
					while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
					cout << "Recv:\n\t" << recvMsg << endl;
					ostream_spin_lock.clear();
				}
			}
		);
		thread_recv.detach();

		while (!disconnect.load() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
		{
			if (_kbhit())
			{
				std::string sendMsg(5, static_cast<char>(_getch()));
				server.Send(sendMsg);
				while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
				cout << "Send:\n\t" << sendMsg << endl;
				ostream_spin_lock.clear();
			}
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
		}

		cout << "客户端断开连接,等待重新接入..." << endl << endl;
	}

	return 0;
}


/*
 * TCP 客户端程序
 * TCPClient 类见 [六-3] TCP 封装
*/
#include <iostream>

using std::cout;
using std::endl;

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32")
#include <string>
#include <thread>
#include <atomic>
#include <conio.h>

/* WSAStartup()->socket()->connet()->send()/recv()->closesocket()->WSACleanup() */

// 同 [六-3] TCP 封装
class TCPClient;

int main()
{
	TCPClient client;

	std::atomic_bool disconnect = false;
	std::atomic_flag ostream_spin_lock = ATOMIC_FLAG_INIT;

	client.Connect();
	cout << "成功接入服务端,ip - port: " << client.getIpPort() << endl;
	disconnect = false;

	std::thread thread_recv(
		[&] {
			while (true)
			{
				std::string recvMsg;
				auto recvRet = client.Recv(recvMsg);
				if (recvRet == SOCKET_ERROR)
				{
					disconnect.store(true);
					return;
				}
				while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
				cout << "Recv:\n\t" << recvMsg << endl;
				ostream_spin_lock.clear();
			}
		}
	);
	thread_recv.detach();

	while (!disconnect.load() && !(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
	{
		if (_kbhit())
		{
			std::string sendMsg(5, static_cast<char>(_getch()));
			client.Send(sendMsg);
			while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
			cout << "Send:\n\t" << sendMsg << endl;
			ostream_spin_lock.clear();
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
	}

	cout << "服务端断开连接..." << endl;

	return 0;
}
// 客户端发送 1,服务端发送 2,客户端发送 3
// 此后客户端断开重连后重复上面操作

/*
 * TCP 服务端程序输出
*/
local ip - port: 192.168.43.5 - 8082

等待客户端接入...
客户端成功接入, ip - port: 192.168.43.5 - 53231
Recv:
	11111
Send:
	22222
Recv:
	33333
客户端断开连接,等待重新接入...

客户端成功接入, ip - port: 192.168.43.5 - 53233
Recv:
	11111
Send:
	22222
Recv:
	33333

/*
 * TCP 客户端程序输出
*/
成功接入服务端,ip - port: 192.168.43.5 - 8082
Send:
	11111
Recv:
	22222
Send:
	33333
// 断开重连
成功接入服务端,ip - port: 192.168.43.5 - 8082
Send:
	11111
Recv:
	22222
Send:
	33333



八、TCP 多连接多请求设计

  1. 试想,有许多客户端向服务端发起请求,服务端需要自动返回相关内容怎么办?

  2. 思考几个问题:

    • 如何处理众多客户端的连接请求,也就是如何使用阻塞模式的 accept
    • 如何接收众多客户端的消息,并自动返回返回内容,注意 recv 是阻塞的
    • 用什么数据结构存储众多客户端的 SOCKET 以及 sockaddr_in
    • 某个客户端断开连接后通过什么途径释放资源,也就是如何使用 closesocket
  3. 先假设一个通信场景:

    • 为避免竞争 IO 流,直接处理客户端按键消息,如:按下 1 则发送 11111
    • 服务器将信息接收消息追加返回,如收到 "11111",返回 "1111111111"
  4. 一个解决方案:

    • 用一个线程 thread1 循环执行 accept
    • thread1 线程执行 accept 成功后打印新连接信息,并生成一个线程 threadx 循环执行 recv,threadx 根据 recv 内容进行返回内容,同时 thread1 将线程 threadx 的 id 与得到的 SOCKET 和 sockaddr_in 进行绑定放入哈希表,key 为 thread_id,value 为 pair 对象,存储 SOCKET 和 sockaddr_in
    • 线程 threadx 执行 recv 及 send 时的参数可通过自身 thread_id 检索哈希表得到,当 recv 返回 SOCKET_ERROR 认定对方断开,打印连接结束信息,并调用 closesocket,然后删除哈希表中的记录,最后结束线程自身
  5. 上述方案存在的问题:

    • 输出流竞争:这里采用自旋锁解决

    • 多线程共享哈希表存在竞争,如线信息存入哈希表时,可能存在其他线程从哈希表删除内容,若 thread_id 哈希值相同,则存在同时修改同一个位置的情况,因此需要设计一个安全的哈希表

    • 一个简单的并发哈希表设计:这里为了简单,将插入哈希表操作、从哈希表删除操作和读操作进行串行化,即三个操作共用一把互斥锁(也可用自旋锁,两种锁的区别可自行学习)。带来的问题是对并发的不友好:当 thread_id 的哈希值不同时,多个线程插入、删除和读操作不会发生竞争(除开扩容操作),但任然被串行化

  6. 代码演示:

    /*
     * TCP 服务端程序
    */
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    
    using std::cout;
    using std::endl;
    
    #include <WinSock2.h>
    #include <WS2tcpip.h>
    #pragma comment (lib, "ws2_32")
    #include <string>
    #include <thread>
    #include <mutex>
    #include <atomic>
    #include <conio.h>
    #include <unordered_map>
    
    /* WSAStartup()->socket()->bind()->listen()->accept()->send()/recv()->closesocket()->WSACleanup() */
    
    class TCPServer
    {
    public:
    	TCPServer()
    	{
    		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) exit(EXIT_FAILURE);
    
    		socketServer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    		if (socketServer == INVALID_SOCKET)
    		{
    			auto code = WSAGetLastError();
    			WSACleanup();
    			exit(code);
    		}
    
    		char hostname[MAXBYTE];
    		gethostname(hostname, MAXBYTE);
    		serverAddr.sin_family = AF_INET;
    		serverAddr.sin_port = htons(8082);
    		serverAddr.sin_addr.S_un.S_addr = ((struct in_addr*)gethostbyname(hostname)->h_addr)->s_addr;
    
    		if (bind(socketServer, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) != 0) clear_and_exit();
    		if (listen(socketServer, SOMAXCONN) != 0) clear_and_exit();
    	}
    
    	~TCPServer()
    	{
    		closesocket(socketServer);
    		WSACleanup();
    	}
    
    	void Accept(SOCKET& socketClient, sockaddr_in& clientAddr)
    	{
    		int nSize = sizeof(clientAddr);
    		socketClient = accept(socketServer, (SOCKADDR*)&clientAddr, &nSize);
    		if (socketClient == INVALID_SOCKET) clear_and_exit();
    	}
    
    	auto Send(std::string toSend, SOCKET socketClient)
    	{
    		return send(socketClient, toSend.c_str(), strlen(toSend.c_str()) + 1, 0);
    	}
    
    	auto Recv(SOCKET socketClient)
    	{
    		char buf[MAXBYTE];
    		recv(socketClient, buf, MAXBYTE, 0);
    		return std::string(buf);
    	}
    	auto Recv(std::string& toRecv, SOCKET socketClient, bool append = false)
    	{
    		char buf[MAXBYTE];
    		auto ret = recv(socketClient, buf, MAXBYTE, 0);
    		if (!append) toRecv = std::string(buf);
    		else toRecv.append(buf);
    		return ret;
    	}
    
    	auto getIp(const sockaddr_in& clientAddr)
    	{
    		char ipBuf[20];
    		return std::string(inet_ntop(AF_INET, &clientAddr.sin_addr, ipBuf, 20));
    	}
    	auto getPort(const sockaddr_in& clientAddr)
    	{
    		return ntohs(clientAddr.sin_port);
    	}
    	auto getIpPort(const sockaddr_in& clientAddr)
    	{
    		return getIp(clientAddr) + " - " + std::to_string(getPort(clientAddr));
    	}
    
    	void insertToMap(std::thread::id id, SOCKET s, sockaddr_in addr)
    	{
    		std::lock_guard<std::mutex> auto_lock(mtx);
    		mp.insert({ id, std::make_pair(s,addr) });
    	}
    	void eraseFromMap(std::thread::id id)
    	{
    		std::lock_guard<std::mutex> auto_lock(mtx);
    		mp.erase(id);
    	}
    	auto getFromMap(std::thread::id id)
    	{
    		std::lock_guard<std::mutex> auto_lock(mtx);
    		return mp[id];
    	}
    
    private:
    	void clear_and_exit()
    	{
    		auto code = WSAGetLastError();
    		closesocket(socketServer);
    		WSACleanup();
    		exit(code);
    	}
    
    private:
    	WSADATA wsaData;
    	SOCKET socketServer;
    	sockaddr_in serverAddr;
    	std::unordered_map<std::thread::id, std::pair<SOCKET, sockaddr_in>> mp;
    	std::mutex mtx;
    };
    
    int main()
    {
    	TCPServer server;
    
    	std::atomic_flag ostream_spin_lock = ATOMIC_FLAG_INIT;
    
    	auto recv_send = [&]
    	{
    		while (true)
    		{
    			std::string recvMsg;
    			auto clientMsg = server.getFromMap(std::this_thread::get_id());
    			auto recvRet = server.Recv(recvMsg, clientMsg.first);
    			if (recvRet == SOCKET_ERROR)
    			{
    				while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
    				cout << "连接结束: " << server.getIpPort(clientMsg.second) << endl;
    				ostream_spin_lock.clear();
    				closesocket(clientMsg.first);
    				server.eraseFromMap(std::this_thread::get_id());
    				return;
    			}
    			else server.Send(recvMsg + recvMsg, clientMsg.first);
    		}
    	};
    
    	while (true)
    	{
    		SOCKET socketClient;
    		sockaddr_in clientAddr;
    		server.Accept(socketClient, clientAddr);
    
    		while (!ostream_spin_lock.test_and_set()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
    		cout << "新连接接入: " << server.getIpPort(clientAddr) << endl;
    		ostream_spin_lock.clear();
    
    		std::thread newThread(recv_send);
    		server.insertToMap(newThread.get_id(), socketClient, clientAddr);
    
    		newThread.detach();
    	}
    
    	return 0;
    }
    
    /*
     * TCP 客户端端程序见 [六-3] TCP 封装
    */
    
  7. 输出演示:运行服务端,然后依次开启三个客户端,然后三个客户端依次按下 1,2,3,最后逆序关闭三个客户端

    /*
     * 客户端 1 输出
    */
    成功接入服务端,ip - port: 192.168.43.5 - 8082
    Send:
    	11111
    Recv:
    	1111111111
    /*
     * 客户端 2 输出
    */
    成功接入服务端,ip - port: 192.168.43.5 - 8082
    Send:
    	22222
    Recv:
    	2222222222
    /*
     * 客户端 3 输出
    */
    成功接入服务端,ip - port: 192.168.43.5 - 8082
    Send:
    	33333
    Recv:
    	3333333333
    /*
     * 服务端输出
    */
    新连接接入: 192.168.43.5 - 56168
    新连接接入: 192.168.43.5 - 56170
    新连接接入: 192.168.43.5 - 56172
    连接结束: 192.168.43.5 - 56172
    连接结束: 192.168.43.5 - 56170
    连接结束: 192.168.43.5 - 56168
    



九、Winsock 其他函数

1、getsockname

2、shutdown

3、setsockopt

4、getsockopt

5、ioctlsocket

6、WSAAsyncSelect



标签:std,sockaddr,SOCKET,int,编程,详解,serverAddr,Winsock,addr
来源: https://www.cnblogs.com/teternity/p/WinSock.html