Winsock使用之Winsock服务器程序

创建服务器Socket

初始化以后, SOCKET对象必须由服务器实例化
1.getaddrinfo函数用于确定sockaddr结构体的值
  • AF_INET 用于指定 IPv4 地址族
  • SOCK_STREAM 用于指定流套接字
  • IPPROTO_TCP 用于指定 TCP 协议
  • AI_PASSIVE 标志表明调用方打算使用 bind 函数调用中返回的套接字地址结构。当设置了 AI_PASSIVE 标志,getaddrinfo 函数节点名称参数是 NULL 指针时,套接字地址结构的 IP 地址部分设置为 INADDR_ANY 为 IPv4 地址或 IN6ADDR_ANY_INIT 为IPv6 地址。
  • 27015 是与客户端将连接到该服务器关联的端口号。

   addrinfo 结构是由 getaddrinfo 函数使用。

#define DEFAULT_PORT "27015"

struct addrinfo *result = NULL, *ptr = 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) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}
2.创建一个称为 ListenSocket 的服务器以侦听客户端连接的套接字对象。

SOCKET ListenSocket = INVALID_SOCKET;

3.调用socket函数,并将其值返回到 ListenSocket 变量。此服务器应用程序中,使用由getaddrinfo 返回第一个IP地址来匹配参数hints的地址族、socket类型和指定协议。 在此示例中,TCP 流套接字与 IPv4 的地址族、 套接字类型为SOCK_STREAM 和 协议 IPPROTO_TCP。IPv4 地址为 ListenSocket。
如果服务器应用程序想要在 侦听 IPv6 ,则需要hints参数中设置为 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);
4.检查有错误,以确保套接字是一个有效的套接字。

if (ListenSocket == INVALID_SOCKET) {
  printf("Error at socket(): %ld\n", WSAGetLastError());
  freeaddrinfo(result);
  WSACleanup();
  return 1;
}

绑定套接字

对于一个接受客户端连接的服务器,必须将它绑定到系统内的网络地址。下面的代码演示如何将已经创建的套接字绑定到的 IP 地址和端口。客户端应用程序使用的 IP 地址和端口来连接到主机的网络。
1.调用 bind 函数,传递 创建的socket和从getaddrinfo 函数返回的sockaddr结构体作为参数。检查一般错误。
// 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());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
2.一旦调用 bind 函数,不再需要由 getaddrinfo 函数返回的地址信息。调用freeaddrinfo函数释放 getaddrinfo 函数为地址信息分配的内存。

freeaddrinfo(result);

侦听套接字

套接字绑定到系统 的 IP 地址和端口后,服务器必须侦听 IP 地址和端口上传入的连接请求。
调用listen函数,传递创建的套接字和等待连接队列的最大长度,比方说,你将backlog定为10, 当有15个连接请求的时候,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝。在此示例中,用SOMAXCONN则由系统确定。检查一般错误的返回值。
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
    printf( "Listen failed with error: %ld\n", WSAGetLastError() );
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}

接受连接

一旦套接字侦听连接,程序必须处理上那套接字的连接请求。
1.创建一个临时的套接字对象称为ClientSocket 接受来自客户端的连接。
SOCKET ClientSocket;
2.通常会设计一个服务器应用程序以侦听来自多个客户端的连接。对于高性能服务器,多个线程通常用于处理多个客户端连接。
   有几个使用 Winsock的不同的编程技术 可以用于侦听多个客户端连接。一种编程技术是创建一个连续的循环使用listen函数来检查连接请求(见侦听套接字)。如果连接请求发生,应用程序调用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;
}
3.当客户端连接已被接受时,服务器应用程序通常会将接受客户端套接字 (在上面的示例代码中的 ClientSocket 变量) 传递给工作线程或 I/O 完成端口,并继续接受额外的连接。在这个基本的示例中,服务器会继续下一步。
在这里有很多其他的编程技术,可以用于侦听并接受多个连接。这些包括使用选择或WSAPoll 函数。一些这些各种各样的编程技术的示例所示包含与微软 Windows 软件开发工具包 (SDK) 的先进的 Winsock 示例。
注:
在 Unix 系统上,对于服务器来说一种常用的编程技术是应用程序侦听连接。当连接被接受时,父进程将调用fork函数,以创建一个新的子进程来处理客户端连接,从父级继承套接字。由于fork函数不支持,因此不会在 Windows 上支持这种编程技术。这种技术通常也不适合于高性能的服务器,因为创建新的进程所需的资源是远远大于所需的线程。

服务器接收和发送数据


下面的代码演示如何接收和发送服务器所使用的功能。
#define DEFAULT_BUFLEN 512

char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;

// Receive until the peer shuts down the connectiondo {

  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);
小结:send 和recv都会返回一个整性值表示各自发送和接收的整数值,或者错误。每一个函数都采用相同的参数:活动socket、char型buffer、发送或接受字节数量和使用标志。

断开服务器端

一旦服务器已完成接收和发送数据,服务器断开客户端的连接和关闭socket。
1.当服务器完成发送数据到客户端时,shutdown函数被调用指定 SD_SEND来关闭socket的发送方。这使得客户端为此socket释放一些资源。服务器应用程序仍然可以接收socket上的数据。
// 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函数被调用来关闭socket。当客户端应用程序完成后使用WS2_32.dll,WSACleanup 函数被调用以释放资源。
// cleanup
closesocket(ClientSocket);
WSACleanup();

return 0;

编辑于2016年8月4日 By Seefun_Zhu

猜你喜欢

转载自blog.csdn.net/zhuxipan1990/article/details/52098270