Winsock基础学习

来源:互联网 发布:淘宝上的泡脚粉有用吗 编辑:程序博客网 时间:2024/06/05 08:12
使用Winsock进行网络编程的基本流程是:

(1)初始化Winsock库
(2)创建套接字
(3)绑定地址
(4)建立连接
(5)收发数据
(6)释放套接字
(7)释放Winsock库


一、初始化和释放Winsock库

使用Winsock需要包含头文件>Winsock2.h。对应的静态库是Ws2_32.lib,动态库是Ws2_32.dll

在使用Winsock之前必须调用WSAStartup初始化,只有初始化成功后才能使用Winsock,即使是WSACleanup也不例外

wVersionRequested用于指定版本号,通常用MAKEWORD(2,2)创建。注意,第一个参数表示的是副版本,第二个参数表示的是主版本。pWSAData用于获取这个版本Winsock的更详细信息。wVersion表示使用的版本,wHightVersion表示系统支持最高版本,szDescription和szSystemStatus意义很明显。在版本2以后,后三个字段没有意义。如下图:


WSAStartup的返回值为0表示成功。如果失败,将返回错误代码,而没有必要也不应该调用WSAGetLastError。(事实上,调用WSAGetLastError不会返回任何错误代码)

返回的错误码:
WSASYSNOTREADY 网络没有准备好
WSAVERNOTSUPPORTED 不支持指定的版本
WSAEINPROGRESS 有阻塞的Winsock1.1操作正在进行
WSAEPROCLIM 达到支持任务的上线?
WSAEFAULT lpWSAData指针无效


int WSACleanup(void);

返回0表示成功,与WSAStartup不同的是:如果失败,需要调用WSAGetLastError获取错误码。

WSAStartup可以使用多次,每次使用增加引用计数,因此,使用完毕之后使用的WSACleanup次数要和WSAStartup一样多。仅仅在最后一次使用WSACleanup的时候真正进行清理。

要注意的是:在最后一次调用之后将所有的阻塞的和异步的socket全部取消(任何线程),不会激活任何事件,也不会发出任何通知消息。重叠的IO也不会激活IO完成例程。对于打开的socket会自动关闭,未发送完成的数据将会丢失。


二、创建套接字

创建套接字使用函数WSASocket或者socket:


af 地址族,AF_INET IPv4或者AF_INET6 IPv6, 其他的选项查看MSDN文档。

type Tcp 使用SOCK_STREAM, UDP使用 SOCK_DGRAM,其他的查看MSDN文档。

protocol  协议名称 IPPROTO_TCP,  IPPROTO_UDP

lpProtocolInfo  协议信息,可以用WSAEnumProtocols函数获取。一般使用NULL即可。如果要使用这个参数,查MSDN。
g 组ID,一般传入0即可。
dwFlags 只介绍一个参数:WSA_FLAG_OVERLAPPED 创建一个支持重叠IO的socket,也可以使用非重叠的方式,在函数的Overlapped参数中传入NULL。

返回值 如果成功返回SOCKET,失败返回INVALID_SOCKET,需要使用 WSAGetLastError获取错误码

使用socket的话, 只有前3个参数,默认支持重叠方式。


三、绑定

在学习绑定之前,首先要了解几个结构和函数:

SOCKADDR_IN结构指定了地址和端口。
sin_family 必须为AF_INET。
sin_port 端口号
IN_ADDR IPv4地址
sin_zero 保留字段。
注意,除了sin_family字段,其他字段都应该使用网络字节序(big-endian)。


网络字节序和主机字节序(little-endian)
little-endian:内存低位保存低位值
big-endian:内存低位保存高位值
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为 :
big-endian little-endian 
0x0000 0x12 0xcd 
0x0001 0x34 0xab 
0x0002 0xab 0x34 
0x0003 0xcd 0x12 
参见:http://blog.csdn.net/jjkkww/article/details/3895893


big-endian和little-endian的转换:

这个函数将32位的无符号整型(IPv4)转为网络字节序。此函数并不检查传入的32位值是否为合法的Ipv4地址。函数从第一个参数传入的SOCKET检查网络字节序,以便于能使Winsock Provider支持不同形式的网路字节序,但是一般都不会改变网络字节序,因此使用没有WSA前缀的htonl函数也是很常见的。
函数成功则返回0,失败返回SOCKET_ERROR。可以用WSAGetLastError查看错误码。
剩下的三个函数是一样的用法,WSANtohs一组是用于转换端口号的。


还有一个十分有用的函数:

这个函数用来将"192.168.1.123"这样的字符串转换成上边提到的IPv4结构(或者看作32位无符号长整型),如果提供了非法字符串,则返回INADDR_NONE.
注意:传入空字符串的话,返回结果可能和Winsock版本有关,需要查看MSDN文档确认。


有了上边几个函数和结构的帮助,就可以绑定端口了。

s 表示上边创建的SOCKET
name socket绑定的地址,使用上边说明的SOCKADDR_IN结构。 表示绑定到任意地址,在有多个网卡的时候,如果想绑定所有的地址,使用INADDR_ANY 或者 in6addr_any。对于客户端,最好给port传入0,这样能自动分派一个临时端口,如果固定端口,可能会和其他程序冲突(通常服务端有明确的端口规划,而客户端没有)。
namelen   name 的长度
返回值:返回0表示成功,返回SOCKET_ERROR表示失败,用WSAGetLastError查看错误码。

绑定之后可以用getsockname函数获取主机的地址。但是如果使用的是INADDR_ANY 或者 in6addr_any就只能等socket连接之后才能获取地址(因为有多个地址)。


四、连接

  对于TCP通信,服务端需要进入监听环节,客户端有一个connet过程。对于UDP通信,则无此环节。

int listen(  _In_  SOCKET s,  _In_  int backlog);

s 之前创建的socket

backlog 等待连接队列长度,设为SOMAXCONN,系统会自动选择一个合适的长度。

返回值:0表示成功,SOCKET_ERROR 表示失败。通过WSAGetLastError查看错误码。

int WSAConnect(  _In_   SOCKET s,  _In_   const struct sockaddr *name,  _In_   int namelen,  _In_   LPWSABUF lpCallerData,  _Out_  LPWSABUF lpCalleeData,  _In_   LPQOS lpSQOS,  _In_   LPQOS lpGQOS);
s,name, namelen和bind中的意义相同

lpCallerData和lpCalleeData用于在连接建立的时候传输数据。

lpSQOS和lpGQOS是和传输服务质量有关的。

如果没有后四个参数提供的需求,可以简单使用connect。

返回值:0表示成功,SOCKET_ERROR 表示失败。通过WSAGetLastError查看错误码。


对于TCP,客户端发起连接之后服务端有一个接收连接过程:WSAAccept或者accept

SOCKET WSAAccept(  _In_     SOCKET s,  _Out_    struct sockaddr *addr,  _Inout_  LPINT addrlen,  _In_     LPCONDITIONPROC lpfnCondition,  _In_     DWORD_PTR dwCallbackData);

参数:
        addr 客户端的地址。
        addrlen 地址长度,单位为字节。
        lpfnCondition 连接过滤条件回调函数。在有连接到达的时候调用,返回CF_ACCEPT表示接收连接,返回CF_REJECT表示拒绝连接。。不需要过滤调节的直接传入NULL。
        dwCallbackData 连接过滤条件回调函数的参数,如果回调函数为NULL,这个参数没有用。
返回值:
        成功返回客户的socket,失败返回 INVALID_SOCKET ,使用WSAGetLastError获取错误码。
accept和WSAAccept作用一样,只是没有后两个参数。


下面说一下面向连接(connection-oriented)和无连接(connectionless)的概念:面向连接和无连接指的都是协议层的。无连接协议中的分组被称为数据报(datagram),每个分组都是独立寻址,并由应用程序发送的从协议的角度来看,每个数据报都是一个独立的实体,与在两个相同的对等实体之间传送的任何其他数据报都没有关系。状态是由应用程序,而不是协议来维护的。面向连接的协议则维护了分组之间的状态,使用这种协议的应用程序通常都会进行长期的对话,面向连接的协议层就提供可靠的传输。打个比方,面向连接的协议就像打电话,而使用无连接协议就像寄信。

面向连接(connection-oriented)和无连接(connectionless)的典型协议就是TCP和UDP协议。

TCP连接过程(三次握手):

(1)TCP客户端处于CLOSED状态,发送一个SYN报文,状态变为SYN_SENT。(报文1)

(2)TCP服务端收到SYN报文后,发送一个SYN_ACK报文,状态变为SYN_RCVD。(报文2)

(3)TCP客户端接收服务端的SYN_ACK报文,给服务端回一个ACK报文,状态变为ESTABLISHED。(报文3)

(4)TCP服务端收到客户端的ACK报文之后变为ESTABLISEHED。

TCP断开过程(四次握手):

(1)TCP客户端发送一个FIN报文,进入FIN_WAIT_1状态。(报文4)

(2)TCP服务端接收到FIN报文后,回一个ACK报文,状态变为CLOSE_WAIT。(报文5)

(3)TCP客户端收到ACK报文之后,状态变为FIN_WAIT_2。

(4)TCP服务端也发送一个FIN报文给客户端,状态变为LAST_ACK。(报文6)

(5)TCP客户端收到FIN之后发回ACK报文确认,状态变为TIME_EAIT。(报文7)

(6)TCP服务端收到ACK报文,状态变为CLOSED。

(7)TCP客户端在TIEM_WAIT时间到之后,也变为CLOSED。

下面三幅图摘自:http://www.1398.net/blog/user1/cloudy/archives/2007/682.html

UDP:


五、数据收发

TCP通信在连接建立后,就可以进行数据收发了,UPD通信可以直接进入收发环节。

一般TCP通信使用WSARecv(recv)和WSASend(send),UDP通信使用WSARecvFrom (recvFrom)和WSASendTo(sendTo)(也可以使用WSARecv和WSASend):

int WSARecv(  _In_     SOCKET s,  _Inout_  LPWSABUF lpBuffers,  _In_     DWORD dwBufferCount,  _Out_    LPDWORD lpNumberOfBytesRecvd,  _Inout_  LPDWORD lpFlags,  _In_     LPWSAOVERLAPPED lpOverlapped,  _In_     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
s, 注意,这个socket不是WSASoket创建的socket而是WSAAccept调用成功之后返回的客户端Socket。

lpBuffers, 一个指向WSABUF结构体数组的指针,WSABUF结构体表明了一个带长度的buffer

dwBufferCount 上边说的数组的元素个数。

lpNumberOfBytesRecvd 接收的数据大小,以byte为单位。如果下边的lpOverlapped不为NULL, 最好给这个参数传入NULL。

lpFlags, 在面向消息(类型为SOCK_DGRAM)的socket中,此处就是指UDP通信,MSG_PARTIAL如果被指定,当消息的长度大于缓存区大小时候,将丢弃多余的数据,函数将返回错误码:WSAEMSGSIZE。

lpOverlapped,如果被指定,将使用重叠模式,如果为NULL,将忽略系一个参数。

lpCompletionRoutine,IO完成时回调函数。


int WSARecvFrom(  _In_     SOCKET s,  _Inout_  LPWSABUF lpBuffers,  _In_     DWORD dwBufferCount,  _Out_    LPDWORD lpNumberOfBytesRecvd,  _Inout_  LPDWORD lpFlags,  _Out_    struct sockaddr *lpFrom,  _Inout_  LPINT lpFromlen,  _In_     LPWSAOVERLAPPED lpOverlapped,  _In_     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
多出来的两个参数是
lpFrom,在重叠的socket情况下,提供一个保存数据源的地址的缓存。
lpFromlen,缓存的大小。
下边一组函数的参数意义类似:
int WSASend(  _In_   SOCKET s,  _In_   LPWSABUF lpBuffers,  _In_   DWORD dwBufferCount,  _Out_  LPDWORD lpNumberOfBytesSent,  _In_   DWORD dwFlags,  _In_   LPWSAOVERLAPPED lpOverlapped,  _In_   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

int WSARecvFrom(  _In_     SOCKET s,  _Inout_  LPWSABUF lpBuffers,  _In_     DWORD dwBufferCount,  _Out_    LPDWORD lpNumberOfBytesRecvd,  _Inout_  LPDWORD lpFlags,  _Out_    struct sockaddr *lpFrom,  _Inout_  LPINT lpFromlen,  _In_     LPWSAOVERLAPPED lpOverlapped,  _In_     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

六、服务器客户端的例子(MSDN):
服务器:
// TcpServer.cpp : Defines the entry point for the console application.//#define WIN32_LEAN_AND_MEAN#include "stdafx.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 _tmain(int argc, _TCHAR* argv[]){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 WinsockiResult = 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 portiResult = 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 serverListenSocket = 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 socketiResult = 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 socketClientSocket = 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 socketclosesocket(ListenSocket);// 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 senderiSendResult = 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 doneiResult = shutdown(ClientSocket, SD_SEND);if (iResult == SOCKET_ERROR) {printf("shutdown failed with error: %d\n", WSAGetLastError());closesocket(ClientSocket);WSACleanup();return 1;}// cleanupclosesocket(ClientSocket);WSACleanup();return 0;}
客户端:
// TcpClient.cpp : Defines the entry point for the console application.//#include "stdafx.h"#define WIN32_LEAN_AND_MEAN#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 parametersif (argc != 2) {printf("usage: %s server-name\n", argv[0]);return 1;}// Initialize WinsockiResult = 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 portiResult = 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 succeedsfor(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {// Create a SOCKET for connecting to serverConnectSocket = 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 bufferiResult = 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 sentiResult = 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 connectiondo {iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);if ( iResult > 0 )printf("Bytes received: %d\n", iResult);else if ( iResult == 0 )printf("Connection closed\n");elseprintf("recv failed with error: %d\n", WSAGetLastError());} while( iResult > 0 );// cleanupclosesocket(ConnectSocket);WSACleanup();return 0;}