Winsock库学习2-服务器端编程

为服务器创建套接字

  1. 所述的getaddrinfo函数被用来确定该值的sockaddr结构

    • AF_INET用于指定IPv4地址族。
    • SOCK_STREAM用于指定流套接字。
    • IPPROTO_TCP用于指定TCP协议。
    • AI_PASSIVE标志指示调用者打算在对bind函数的调用中使用返回的套接字地址结构。当设置了AI_PASSIVE标志并且getaddrinfo函数的nodename参数为NULL指针时,套接字地址结构的IP地址部分对于IPv4地址设置为INADDR_ANY或对于IPv6地址设置为IN6ADDR_ANY_INIT。
    • 27015是与客户端将连接到的服务器关联的端口号。
    #define DEFAULT_PORT "27015"
    
    struct addrinfo *result = NULL,hints;
    
    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
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        cout << "WSAStartup failed:" << iResult << endl;
        WSACleanup();
        return 1;
    }
    
  2. 为服务器创建一个名为ListenSocket的SOCKET对象,以侦听客户端连接。

    SOCKET ListenSocket = INVALID_SOCKET;
    
  3. 调用套接字函数,并将其值返回到ListenSocket变量。对于此服务器应用程序,请使用调用返回的第一个IP地址获取与addsinfo参数(在hints参数中指定的地址),套接字类型和协议相匹配的getaddrinfo。在此示例中,使用IPv4的地址族,SOCK_STREAM的套接字类型和IPPROTO_TCP的协议请求了IPv4的TCP流套接字。因此,为ListenSocket请求了一个IPv4地址。

    如果服务器应用程序希望侦听IPv6,则需要在hints参数中将地址族设置为AF_INET6 。如果服务器要同时监听IPv6和IPv4,则必须创建两个监听套接字,一个用于IPv6,另一个用于IPv4。这两个套接字必须由应用程序分别处理。

    Windows Vista和更高版本提供了创建单个IPv6套接字的功能,该套接字被置于双堆栈模式下,可以同时监听IPv6和IPv4。有关此功能的更多信息,请参见Dual-Stack Sockets。

    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    
  4. 检查是否有错误,以确保该套接字是有效的套接字。

    if (ListenSocket == INVALID_SOCKET) {
        cout << "Error at socket():" << WSAGetLastError() << endl;
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    

绑定套接字

为了使服务器接受客户端连接,必须将其绑定到系统内的网络地址。以下代码演示了如何将已经创建的套接字绑定到IP地址和端口。客户端应用程序使用IP地址和端口连接到主机网络。

该sockaddr的结构保存有关家庭地址,IP地址和端口号的信息。

调用bind函数,将从getaddrinfo函数返回的创建的套接字和sockaddr结构作为参数传递。检查一般错误。

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        cout << "bind failed with error:" << WSAGetLastError() << endl;
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

一旦绑定函数被调用,在返回的地址信息的getaddrinfo功能不再需要。该freeaddrinfo函数被调用,以释放该分配的内存的getaddrinfo这个地址信息的功能。

C ++复制

    freeaddrinfo(result);

在套接字上侦听

将套接字绑定到系统上的IP地址和端口后,服务器必须随后在该IP地址和端口上侦听传入的连接请求。

调用listen函数,将创建的套接字和backlog的值作为参数传递,backlog的值是要接受的未决连接队列的最大长度。在此示例中,backlog参数设置为SOMAXCONN。此值是一个特殊的常数,它指示Winsock提供程序为此套接字允许在队列中允许最大数量的合理挂起连接。检查返回值是否存在一般错误。

if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
    cout << "Listen failed with error:" << WSAGetLastError() << endl;
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

接收连接

套接字侦听连接后,程序必须处理该套接字上的连接请求。

  1. 创建一个名为ClientSocket的临时SOCKET对象,以接受来自客户端的连接。

    SOCKET ClientSocket;
    
  2. 通常,服务器应用程序将被设计为侦听来自多个客户端的连接。对于高性能服务器,通常使用多个线程来处理多个客户端连接。

    使用Winsock有几种不同的编程技术,可用于侦听多个客户端连接。一种编程技术是创建一个连续循环,使用listen函数检查连接请求(请参见在Socket上侦听)。如果发生连接请求,则应用程序将调用acceptAcceptExWSAAccept函数,并将工作传递给另一个线程来处理该请求。其他几种编程技术也是可能的。

    请注意,此基本示例非常简单,并且不使用多个线程。该示例还仅侦听并接受单个连接。

    ClientSocket = INVALID_SOCKET;
    
    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        cout << "accept failed:" << WSAGetLastError() << endl;
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    
  3. 接受客户端连接后,服务器应用程序通常会将接受的客户端套接字(上述示例代码中的ClientSocket变量)传递给工作线程或I / O完成端口,然后继续接受其他连接。在此基本示例中,服务器继续进行下一步。

    还有许多其他编程技术可用于侦听和接受多个连接。这些包括使用selectWSAPoll函数。Microsoft Windows软件开发工具包(SDK)附带的Advanced Winsock Samples中说明了其中各种编程技术的示例。

在服务器上接收和发送数据

以下代码演示了服务器使用的recvsend函数。

#define DEFAULT_BUFLEN 512

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);

sendrecv的功能都返回分 别发送或接收的,字节数,或错误的一个整数值。每个函数还具有相同的参数:活动套接字,char缓冲区,要发送或接收的字节数以及要使用的任何标志。

断开服务器

服务器完成从客户端接收数据并将数据发送回客户端后,服务器将与客户端断开连接并关闭套接字。

断开和关闭套接字

  1. 服务器完成向客户端的数据发送后,可以通过指定SD_SEND调用shutdown函数来关闭套接字的发送端。这允许客户端释放该套接字的一些资源。服务器应用程序仍可以在套接字上接收数据。

    // 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;
    }
    
  2. 客户端应用程序完成数据接收后,将调用closesocket函数关闭套接字。

    使用Windows套接字DLL完成客户端应用程序后,将调用WSACleanup函数以释放资源。

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

示例

#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<winsock2.h>
#include<ws2tcpip.h>
#include<stdlib.h>
#include<stdio.h>

#define DEFAULT_PORT "27015"
#define DEFAULT_BUFLEN 512
//#define WIN32_LEAN_AND_MEAN
using namespace std;
#pragma comment(lib,"ws2_32.lib")

//赋值操作
int main()
{
	//初始化Winsock
	WSADATA wsadata;
	int iResult;
	iResult = WSAStartup(MAKEWORD(2, 2), &wsadata);
	if (iResult != 0) {
		cout << "WSAStartup failed:" << iResult << endl;
		return 1;
	}
	//为服务器创建套接字
	//SOCKET serversocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct addrinfo* result = NULL, hints;
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;
	iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
	if (iResult != 0) {
		cout << "getaddrinfo failed:" << iResult << endl;
		WSACleanup();
		return 1;
	}
	SOCKET ListenSocket = INVALID_SOCKET;
	ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (ListenSocket == INVALID_SOCKET) {
		cout << "Error at socket():" << WSAGetLastError() << endl;
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	//绑定套接字
	iResult = ::bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR) {
		cout << "bind failed with error:" << WSAGetLastError() << endl;
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	freeaddrinfo(result);
	//进入监听状态
	if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
		cout << "Listen failed with error:" << WSAGetLastError() << endl;
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	//接收客户端请求
	SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
	if (ClientSocket == INVALID_SOCKET) {
		cout << "accept failed:" << WSAGetLastError() << endl;
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	//在服务器上接收和发送数据
	char recvbuf[DEFAULT_BUFLEN];
	int iSendResult;
	int recvbuflen = DEFAULT_BUFLEN;
	do {
		iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
		if (iResult > 0) {
			cout << "Bytes received:" << iResult << endl;
			iSendResult = send(ClientSocket, recvbuf, iResult, 0);
			if (iSendResult == SOCKET_ERROR) {
				cout << "send failed:" << WSAGetLastError() << endl;
				closesocket(ClientSocket);
				WSACleanup();
				return 1;
			}
			cout << "Bytes sent:" << iSendResult << endl;
		}
		else if (iResult == 0) {
			cout << "Connection closing..." << endl;

		}
		else {
			cout << "recv failed:" << WSAGetLastError() << endl;
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}
	} while (iResult > 0);
	iResult = shutdown(ClientSocket, SD_SEND);
	if (iResult == SOCKET_ERROR) {
		cout << "shutdown failed: " << WSAGetLastError() << endl;
		closesocket(ClientSocket);
		WSACleanup();
		return 1;
	}
	closesocket(ClientSocket);
	WSACleanup();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值