服务器开发记录

流程:
// 为服务器创建套接字
// 绑定套接字
// 侦听套接字
// 接受连接
// 在服务器上接收和发送数据
// 断开服务器连接

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

using namespace  std;
#pragma comment(lib, "Ws2_32.lib")

#define DEFAULT_PORT "27015"
#define DEFAULT_BUFLEN 512

int iResult;
// 创建名为 ClientSocket 的临时 SOCKET 对象,用于接受来自客户端的连接。	
SOCKET ClientSocket;

// 创建名为 wsaData 的 WSADATA 对象。
WSADATA wsaData;

// addrinfo 结构由 getaddrinfo 函数使用。
struct addrinfo *result = NULL, *ptr = NULL, hints;
// Initialize Winsock 
bool Init_Socket() {
	// 调用 WSAStartup 函数以启动WS2_32.dll的使用。
	// WSADATA 结构包含有关Windows套接字实现的信息。 
	// WSAStartup 的 MAKEWORD (2,2) 参数对系统上的 Winsock 版本 2.2 发出请求,
	// 并将传递的版本设置为调用方可以使用的Windows套接字支持的最高版本。

	// 调用 WSAStartup 并将其值作为整数返回并检查错误
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0) {
		printf("WSAStartup failed: %d\n", iResult);
		return false;
	}
	printf("WSAStartup success: %d\n", iResult);
	return true;
}
// 创建一个名为 ListenSocket 的 SOCKET 对象,供服务器侦听客户端连接。
int CreatSocket(){
	// AF_INET 用于指定 IPv4 地址系列。
	// SOCK_STREAM 用于指定流套接字。
	// IPPROTO_TCP 用于指定 TCP 协议。
	// AI_PASSIVE 标志指示调用方打算在对 绑定 函数的调用中使用返回的套接字地址结构。
	// 当将AI_PASSIVE标志设置为 getaddrinfo 函数的 nodename 参数为 NULL 指针时,
	// 套接字地址结构的 IP 地址部分设置为INADDR_ANY IPv4 地址或 IPv6 地址的IN6ADDR_ANY_INIT。
	// 5001 是与客户端将连接到的服务器关联的端口号。

	ZeroMemory(&hints, sizeof (hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;
	// Resolve the local address and port to be used by the server
	// addrinfo 结构由 getaddrinfo 函数使用。
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		printf("getaddrinfo failed: %d\n", iResult);
		WSACleanup();
		return 1;
	}
	
	SOCKET ListenSocket = INVALID_SOCKET;

	// 调用 套接字 函数并将其值返回到 ListenSocket 变量。 
	// 对于此服务器应用程序,请使用调用返回的第一个 IP 地址来匹配提示参数中指定的地址系列、套接字类型和协议的 getaddrinfo。 
	// 在此示例中,请求 IPv4 的 TCP 流套接字具有 IPv4 的地址系列、SOCK_STREAM的套接字类型和IPPROTO_TCP协议。 
	// 因此,为 ListenSocket 请求 IPv4 地址。

	/* 
	   如果服务器应用程序想要侦听 IPv6,则需要将地址系列设置为 提示 参数中的AF_INET6。 
	   如果服务器想要同时侦听 IPv6 和 IPv4,则必须创建两个侦听套接字,一个用于 IPv6,一个用于 IPv4。 
	   这两个套接字必须由应用程序单独处理。
	   Windows Vista 及更高版本提供创建单个 IPv6 套接字的功能,该套接字处于双堆栈模式以侦听 IPv6 和 IPv4。
	*/

	// Create a SOCKET for the server to listen for client connections
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

	// 检查错误以确保套接字是有效的套接字。
	if (ListenSocket == INVALID_SOCKET) {
		printf("create socket failed");
		// freeaddrinfo(result);
		// WSACleanup();
		return false;
	}
	return true;
} 
// 若要使服务器接受客户端连接,它必须绑定到系统中的网络地址。 
// 以下代码演示如何将已创建的套接字绑定到 IP 地址和端口。 
// 客户端应用程序使用 IP 地址和端口连接到主机网络。
int BindSock(){
	// Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
		// 调用 绑定 函数后,不再需要 getaddrinfo 函数返回的地址信息。 
		// 调用 freeaddrinfo 函数以释放 getaddrinfo 函数为此地址信息分配的内存。
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
}


// 调用 侦听 函数,以参数的形式传递所创建的套接字和 积压工作的值、要接受的挂起连接队列的最大长度。
// 在此示例中, 积压工作 参数设置为 SOMAXCONN。 
// 此值是一个特殊常量,指示此套接字的 Winsock 提供程序允许队列中最大合理数量的挂起连接。 
// 检查返回值是否存在常规错误。
int ListenSock(){
	if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
		printf( "Listen failed with error: %ld\n", WSAGetLastError() );
		closesocket(ListenSocket);
		WSACleanup();
    	return 1;
	}
}

int AcceptClient(){
	//通常,服务器应用程序设计为侦听来自多个客户端的连接。 对于高性能服务器,通常使用多个线程来处理多个客户端连接。
	// 使用 Winsock 有多种不同的编程技术可用于侦听多个客户端连接。 
	// 一种编程技术是创建一个连续循环,该循环使用 侦听 函数检查连接请求, (看到 套接字上的侦听) 。
	// 如果发生连接请求,应用程序将调用 accept、 AcceptEx 或 WSAAccept 函数,并将工作传递给另一个线程来处理请求。 
	// 可以采用其他几种编程技术。
	// 请注意,此基本示例非常简单,不使用多个线程。 该示例还仅侦听并仅接受单个连接。
	
	ClientSocket = INVALID_SOCKET;

	// Accept a client socket
	ClientSocket = accept(ListenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET) {
		printf("accept failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	// 接受客户端连接后,服务器应用程序通常会将接受的客户端套接字 (上述示例代码中的 ClientSocket 变量) 
	// 工作线程或 I/O 完成端口,并继续接受其他连接。 在此基本示例中,服务器将继续执行下一步。
	// 有多种其他编程技术可用于侦听和接受多个连接。 这包括使用 select 或 WSAPoll 函数。 
	// Microsoft Windows软件开发 (工具包) 随附的高级 Winsock 示例演示了其中一些各种编程技术的示例。
}
int SendClient(){
	char recvbuf[DEFAULT_BUFLEN];
	int iResult, iSendResult;
	int recvbuflen = DEFAULT_BUFLEN;

	// Receive until the peer shuts down the connection
	do {

		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0) {
			printf("Bytes received: %d\n", iResult);

			// Echo the buffer back to the sender
			iSendResult = send(ClientSocket, recvbuf, iResult, 0);
			if (iSendResult == SOCKET_ERROR) {
				printf("send failed: %d\n", WSAGetLastError());
				closesocket(ClientSocket);
				WSACleanup();
				return 1;
			}
			printf("Bytes sent: %d\n", iSendResult);
		} else if (iResult == 0)
			printf("Connection closing...\n");
		else {
			printf("recv failed: %d\n", WSAGetLastError());
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}

	} while (iResult > 0);

	// 发送和 recv 函数分别返回发送或接收的字节数的整数值或错误。
	// 每个函数还采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及要使用的任何标志。
}
int CloseSer(){
	// shutdown the send half of the connection since no more data will be sent
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		printf("shutdown failed: %d\n", WSAGetLastError());
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}

	// cleanup
	closesocket(ClientSocket);
	WSACleanup();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游戏开发的蝎子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值