Windows Socket套接字(二)-MSDN:winsock入门

来源:互联网 发布:域名符号有什么用 编辑:程序博客网 时间:2024/05/16 16:21

MSDN:winsock入门

一、关于服务器和客户端

服务器

1.初始化-WSAStartup
2.创建套接字-socket
3.绑定套接字-bing
4.在套接字上监听客户端-listen
5.接收来自客户端的连接-accept
6.接收和发送数据-recv,send 
7.断开-WSACleanup

客户

1.初始化-WSAStartup
2.创建套接字-socket
3.连接服务器- connect
4.发送和接收数据-recv,send 
5.断开-WSACleanup

二、头文件介绍

Note  

The Iphlpapi.h header file is required if an application is using the IP Helper APIs. When the Iphlpapi.h header file is required, the #include line for the Winsock2.h header this file should be placed before the #include line for the Iphlpapi.h header file.
winsock2.h头文件应该在iphlpapi.h之前
The Winsock2.h header file internally includes core elements from the Windows.h header file, so there is not usually an #include line for the Windows.h header file in Winsock applications. If an #include line is needed for the Windows.h header file, this should be preceded with the #define WIN32_LEAN_AND_MEAN macro. For historical reasons, the Windows.h header defaults to including the Winsock.h header file for Windows Sockets 1.1. The declarations in the Winsock.h header file will conflict with the declarations in the Winsock2.h header file required by Windows Sockets 2.0. The WIN32_LEAN_AND_MEAN macro prevents the Winsock.h from being included by the Windows.h header. An example illustrating this is shown below.
winsock2.h包含在windows.h,通常winsock应用程序不用windows.h,如果要使用windows.h,则应该在define WIN32_LEAN_AND_MEAN宏之前(?)

由于历史原因,windows.h包含了winsock.h,而winsock.h会与winsock2.h有声明冲突,需要定义WIN32_LEAN_AND_MEAN宏防止Windows.h包含winsock.h

以上可以不#define WIN32_LEAN_AND_MEAN,直接winsock2.h在windows.h 之前

#ifndef WIN32_LEAN_AND_MEAN#define WIN32_LEAN_AND_MEAN#endif#include <windows.h>#include <winsock2.h> #include <ws2tcpip.h>#include <iphlpapi.h>#include <stdio.h>#pragma comment(lib, "Ws2_32.lib")int main() {  return 0;}

或者

#include <winsock2.h>#include <windows.h>#include <ws2tcpip.h>#include <iphlpapi.h>#include <stdio.h>#pragma comment(lib, "Ws2_32.lib")int main() {  return 0;}



三、初始化winsock

1.初始化winsock

创建一个名为WsaData的WSADATA对象

WSADATA wsaData;


2.调用WSAStartup并将其值作为整数返回,并检查错误。

int iResult;//初始化WinsockiResult = WSAStartup(MAKEWORD(2,2),&wsaData);if(iResult!= 0){    printf(“WSAStartup failed:%d \ n”,iResult);    return 1;}

3.WSAStartup函数

int WSAStartup(  _In_ WORD wVersionRequested,  _Out_ LPWSADATA lpWSAData);

参数

wVersionRequested [in]

呼叫者可以使用的最高版本的Windows Sockets规范。高位字节指定次版本号; 低位字节指定主版本号。

lpWSAData [out]

指向WSADATA数据结构的指针, 用于接收Windows Sockets实现的详细信息。

返回值

如果成功,则 WSAStartup函数返回零。否则,它返回以下列出的错误代码之一。

调用WSAStartup函数直接返回在此函数的返回值的扩展错误代码。不需要调用WSAGetLastError函数,不应该使用它。

错误代码含义
WSASYSNOTREADY

底层网络子系统尚未准备好进行网络通信。

WSAVERNOTSUPPORTED

Windows Sockets实现不提供Windows Sockets支持的版本。

WSAEINPROGRESS

阻止Windows Sockets 1.1操作正在进行中。

WSAEPROCLIM

Windows套接字实现支持的任务数量已达到限制。

WSAEFAULT

lpWSAData参数不是一个有效的指针。

#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>// Need to link with Ws2_32.lib#pragma comment(lib, "ws2_32.lib")int __cdecl main(){    WORD wVersionRequested;    WSADATA wsaData;    int err;/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */    wVersionRequested = MAKEWORD(2, 2);    err = WSAStartup(wVersionRequested, &wsaData);    if (err != 0) {        /* Tell the user that we could not find a usable */        /* Winsock DLL.                                  */        printf("WSAStartup failed with error: %d\n", err);        return 1;    }/* Confirm that the WinSock DLL supports 2.2.*//* Note that if the DLL supports versions greater    *//* than 2.2 in addition to 2.2, it will still return *//* 2.2 in wVersion since that is the version we      *//* requested.                                        */    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {        /* Tell the user that we could not find a usable */        /* WinSock DLL.                                  */        printf("Could not find a usable version of Winsock.dll\n");        WSACleanup();        return 1;    }    else        printf("The Winsock 2.2 dll was found okay\n");        /* The Winsock DLL is acceptable. Proceed to use it. *//* Add network programming using Winsock here *//* then call WSACleanup when done using the Winsock dll */        WSACleanup();}


四、创建Winsock客户端应用程序

1.创建套接字

(1).声明一个 addrinfo对象,它包含一个sockaddr 结构并初始化这些值。对于此应用程序,Internet地址系列未指定,以便可以返回IPv6或IPv4地址。应用程序请求套接字类型为TCP协议的流套接字。

struct addrinfo *result = NULL,                *ptr = NULL,                hints;ZeroMemory( &hints, sizeof(hints) );hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;

(2).调用getaddrinfo函数,请求在命令行中传递的服务器名称的IP地址。客户端将连接到的服务器上的TCP端口在此示例中由DEFAULT_PORT定义为27015。


该getaddrinfo函数返回其值是错误检查的整数。
#define DEFAULT_PORT "27015"// Resolve the server address and portiResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);if (iResult != 0) {    printf("getaddrinfo failed: %d\n", iResult);    WSACleanup();    return 1;}

(3).创建一个名为ConnectSocket 的SOCKET对象


SOCKET ConnectSocket = INVALID_SOCKET;


(4).调用套接字函数并将其值返回给ConnectSocket变量



在此示例中,使用SOCK_STREAM的套接字​​类型和IPPROTO_TCP协议指定TCP流套接字。地址系列未指定(AF_UNSPEC),因此返回的IP地址可能是服务器的IPv6或IPv4地址。
如果客户端应用程序只想使用IPv6或IPv4进行连接,那么在hints参数中,地址系列需要设置为AF_INET6为IPv6或AF_INET for IPv4 。
// Attempt to connect to the first address returned by// the call to getaddrinfoptr=result;// Create a SOCKET for connecting to serverConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,     ptr->ai_protocol);


(5).检查错误以确保套接字是有效的套接字

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

错误检测是成功的网络代码的关键部分。如果套接字调用失败,则返回INVALID_SOCKET。以前代码中的if语句用于捕获创建套接字时可能发生的任何错误。WSAGetLastError返回与发生的最后一个错误相关联的错误号。


2.连接到socket



 调用connect函数,传递创建的套接字和sockaddr 结构作为参数。检查一般错误
// Connect to server.iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);if (iResult == SOCKET_ERROR) {    closesocket(ConnectSocket);    ConnectSocket = INVALID_SOCKET;}// Should really try the next address returned by getaddrinfo// if the connect call failed// But for this simple example we just free the resources// returned by getaddrinfo and print an error messagefreeaddrinfo(result);if (ConnectSocket == INVALID_SOCKET) {    printf("Unable to connect to server!\n");    WSACleanup();    return 1;}


3.当客户端应用程序完成接收数据时,调用closesocket函数来关闭套接字。



使用Windows Sockets DLL完成客户端应用程序时,将调用WSACleanup函数来释放资源。
#define DEFAULT_BUFLEN 512int recvbuflen = DEFAULT_BUFLEN;char *sendbuf = "this is a test";char recvbuf[DEFAULT_BUFLEN];int iResult;// Send an initial bufferiResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);if (iResult == SOCKET_ERROR) {    printf("send failed: %d\n", WSAGetLastError());    closesocket(ConnectSocket);    WSACleanup();    return 1;}printf("Bytes Sent: %ld\n", iResult);// shutdown the connection for sending since no more data will be sent// the client can still use the ConnectSocket for receiving dataiResult = shutdown(ConnectSocket, SD_SEND);if (iResult == SOCKET_ERROR) {    printf("shutdown failed: %d\n", WSAGetLastError());    closesocket(ConnectSocket);    WSACleanup();    return 1;}// Receive data until the server closes the connectiondo {    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);    if (iResult > 0)        printf("Bytes received: %d\n", iResult);    else if (iResult == 0)        printf("Connection closed\n");    else        printf("recv failed: %d\n", WSAGetLastError());} while (iResult > 0);


4.断开客户端连接



(1).当客户端完成向服务器发送数据时,可以调用关闭功能,指定SD_SEND来关闭套接字的发送端。这允许服务器释放这个套接字的一些资源。客户端应用程序仍然可以在套接字上接收数据。



// shutdown the send half of the connection since no more data will be sentiResult = shutdown(ConnectSocket, SD_SEND);if (iResult == SOCKET_ERROR) {    printf("shutdown failed: %d\n", WSAGetLastError());    closesocket(ConnectSocket);    WSACleanup();    return 1;}


(2).当客户端应用程序完成接收数据时,调用closesocket函数来关闭套接字。

使用Windows Sockets DLL完成客户端应用程序时,将调用WSACleanup函数来释放资源。


// cleanupclosesocket(ConnectSocket);WSACleanup();return 0;




客户端完整代码:



#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdlib.h>#include <stdio.h>// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib#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;    SOCKET ConnectSocket = INVALID_SOCKET;    struct addrinfo *result = NULL,                    *ptr = NULL,                    hints;    char *sendbuf = "this is a test";    char recvbuf[DEFAULT_BUFLEN];    int iResult;    int recvbuflen = DEFAULT_BUFLEN;        // Validate the parameters    if (argc != 2) {        printf("usage: %s server-name\n", argv[0]);        return 1;    }    // Initialize Winsock    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);    if (iResult != 0) {        printf("WSAStartup failed with error: %d\n", iResult);        return 1;    }    ZeroMemory( &hints, sizeof(hints) );    hints.ai_family = AF_UNSPEC;    hints.ai_socktype = SOCK_STREAM;    hints.ai_protocol = IPPROTO_TCP;    // Resolve the server address and port    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);    if ( iResult != 0 ) {        printf("getaddrinfo failed with error: %d\n", iResult);        WSACleanup();        return 1;    }    // Attempt to connect to an address until one succeeds    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {        // Create a SOCKET for connecting to server        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,             ptr->ai_protocol);        if (ConnectSocket == INVALID_SOCKET) {            printf("socket failed with error: %ld\n", WSAGetLastError());            WSACleanup();            return 1;        }        // Connect to server.        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);        if (iResult == SOCKET_ERROR) {            closesocket(ConnectSocket);            ConnectSocket = INVALID_SOCKET;            continue;        }        break;    }    freeaddrinfo(result);    if (ConnectSocket == INVALID_SOCKET) {        printf("Unable to connect to server!\n");        WSACleanup();        return 1;    }    // Send an initial buffer    iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );    if (iResult == SOCKET_ERROR) {        printf("send failed with error: %d\n", WSAGetLastError());        closesocket(ConnectSocket);        WSACleanup();        return 1;    }    printf("Bytes Sent: %ld\n", iResult);    // shutdown the connection since no more data will be sent    iResult = shutdown(ConnectSocket, SD_SEND);    if (iResult == SOCKET_ERROR) {        printf("shutdown failed with error: %d\n", WSAGetLastError());        closesocket(ConnectSocket);        WSACleanup();        return 1;    }    // Receive until the peer closes the connection    do {        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);        if ( iResult > 0 )            printf("Bytes received: %d\n", iResult);        else if ( iResult == 0 )            printf("Connection closed\n");        else            printf("recv failed with error: %d\n", WSAGetLastError());    } while( iResult > 0 );    // cleanup    closesocket(ConnectSocket);    WSACleanup();    return 0;}


五、创建Winsock服务器应用程序

1.创建套接字

(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地址。
#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 serveriResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);if (iResult != 0) {    printf("getaddrinfo failed: %d\n", iResult);    WSACleanup();    return 1;}


(2).为服务器创建一个名为ListenSocket 的SOCKET对象来侦听客户端连接。

SOCKET ListenSocket = INVALID_SOCKET;


(3).调用套接字函数并将其值返回给ListenSocket变量

如果服务器应用程序想要监听IPv6,则需要在hints参数中将地址族设置为AF_INET6 。如果服务器要在IPv6和IPv4上侦听,则必须创建两个侦听套接字,一个用于IPv6,一个用于IPv4。这两个插槽必须由应用程序单独处理。
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;}


2.绑定socket

要使服务器接受客户端连接,它必须绑定到系统内的网络地址。以下代码演示了如何将已创建的套接字绑定到IP地址和端口。客户端应用程序使用IP地址和端口连接到主机网络。
调用绑定函数,传递从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;    }


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


  freeaddrinfo(result);




3.监听



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


if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {    printf( "Listen failed with error: %ld\n", WSAGetLastError() );    closesocket(ListenSocket);    WSACleanup();    return 1;}


4.接收连接



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



SOCKET ClientSocket;




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



使用Winsock可以使用多种不同的编程技术来监听多个客户端连接。一种编程技术是创建一个连续循环,使用监听功能检查连接请求(请参阅在套接字上听)。如果发生连接请求,应用程序将调用accept,AcceptEx或WSAAccept函数,并将工作传递给另一个线程来处理请求


ClientSocket = INVALID_SOCKET;// Accept a client socketClientSocket = accept(ListenSocket, NULL, NULL);if (ClientSocket == INVALID_SOCKET) {    printf("accept failed: %d\n", WSAGetLastError());    closesocket(ListenSocket);    WSACleanup();    return 1;}


(3).当客户端连接被接受时,服务器应用程序通常会将接受的客户端套接字(上述示例代码中的ClientSocket变量)传递给工作线程或I / O完成端口,并继续接受其他连接。在这个基本的例子中,服务器继续下一步。

有许多其他编程技术可以用于监听和接受多个连接。这些包括使用select或WSAPoll函数。


5.在服务器上接收和发送数据-recv和send函数。



#define DEFAULT_BUFLEN 512char 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);




6.断开服务器连接

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



// shutdown the send half of the connection since no more data will be sentiResult = shutdown(ClientSocket, SD_SEND);if (iResult == SOCKET_ERROR) {    printf("shutdown failed: %d\n", WSAGetLastError());    closesocket(ClientSocket);    WSACleanup();    return 1;}




(2).当客户端应用程序完成接收数据时,调用closesocket函数来关闭套接字。

使用Windows Sockets DLL完成客户端应用程序时,将调用WSACleanup函数来释放资源。
// cleanupclosesocket(ClientSocket);WSACleanup();return 0;


服务器完整代码:

#undef UNICODE#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <winsock2.h>#include <ws2tcpip.h>#include <stdlib.h>#include <stdio.h>// Need to link with Ws2_32.lib#pragma comment (lib, "Ws2_32.lib")// #pragma comment (lib, "Mswsock.lib")#define DEFAULT_BUFLEN 512#define DEFAULT_PORT "27015"int __cdecl main(void) {    WSADATA wsaData;    int iResult;    SOCKET ListenSocket = INVALID_SOCKET;    SOCKET ClientSocket = INVALID_SOCKET;    struct addrinfo *result = NULL;    struct addrinfo hints;    int iSendResult;    char recvbuf[DEFAULT_BUFLEN];    int recvbuflen = DEFAULT_BUFLEN;        // Initialize Winsock    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);    if (iResult != 0) {        printf("WSAStartup failed with error: %d\n", iResult);        return 1;    }    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 server address and port    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);    if ( iResult != 0 ) {        printf("getaddrinfo failed with error: %d\n", iResult);        WSACleanup();        return 1;    }    // Create a SOCKET for connecting to server    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);    if (ListenSocket == INVALID_SOCKET) {        printf("socket failed with error: %ld\n", WSAGetLastError());        freeaddrinfo(result);        WSACleanup();        return 1;    }    // 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;    }    freeaddrinfo(result);    iResult = listen(ListenSocket, SOMAXCONN);    if (iResult == SOCKET_ERROR) {        printf("listen failed with error: %d\n", WSAGetLastError());        closesocket(ListenSocket);        WSACleanup();        return 1;    }    // Accept a client socket    ClientSocket = accept(ListenSocket, NULL, NULL);    if (ClientSocket == INVALID_SOCKET) {        printf("accept failed with error: %d\n", WSAGetLastError());        closesocket(ListenSocket);        WSACleanup();        return 1;    }    // No longer need server socket    closesocket(ListenSocket);    // 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 with error: %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 with error: %d\n", WSAGetLastError());            closesocket(ClientSocket);            WSACleanup();            return 1;        }    } while (iResult > 0);    // shutdown the connection since we're done    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);    WSACleanup();    return 0;}



原创粉丝点击