Winsock

WinSock

      • Winsock
          • 初始化、反初始化
          • 错误检查和处理
      • 使用IP协议创建基本的Winsock调用来建立通信
          • IPv4寻址
          • 字节排序
          • 创建套接字
      • 面向连接的通信
          • 在Winsock中,建立通信的步骤:
            • 服务器:
            • 客户机:
            • 套接字状态

Winsock

  • 编译采用了WINSOCK.H的应用程序时,需要链接到WS2_32.LIB库
  • 使用WINSOCK.H时,需要链接到WSOCK32.LIB
  • 使用MSWSOCK.H时,需要链接到MSWCOCK.DLL
初始化、反初始化

1. 加载Winsock库
示例:

	WSADATA wsaData;
	WSAStartup( MAKEWORD(2, 2), &wsaData);

原型:

int
WSAAPI
WSAStartup(
    __in WORD wVersionRequested,	//用于指定准备加载的Winsock库的版本,高位字节是次版本,低字节是主版本。通常使用MAKEWORD(x,y)来指定【x是高位字节,y是低位字节】
    __out LPWSADATA lpWSAData	//用与其加载的库版本有关的信息填充这个结构
    );
    
//WSADATA
typedef struct WSAData {
        WORD                    wVersion;	//设置为将要使用的Winsock版本
        WORD                    wHighVersion;	//包含了现有的Winsock库的最高版本
#ifdef _WIN64
        unsigned short          iMaxSockets;
        unsigned short          iMaxUdpDg;
        char FAR *              lpVendorInfo;
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
#else
        char                    szDescription[WSADESCRIPTION_LEN+1];
        char                    szSystemStatus[WSASYS_STATUS_LEN+1];
        unsigned short          iMaxSockets;	//可以同时打开的最大套接字数量,不固定,尽量不要用这个值
        unsigned short          iMaxUdpDg;	//数据报的最大长度,不固定,尽量不要用这个值
        char FAR *              lpVendorInfo;
#endif
} WSADATA, FAR * LPWSADATA;

Winsock_第1张图片
2. 释放
使用后需要调用WSACleanup函数,来使Winsock释放所有由Winsock分配的资源,并取消这个应用程序挂起的Winsock调用。
原型:
int WSAAPI WSACleanup( void );

错误检查和处理

出现错误时(比如SOCKET_ERROR),可以调用WSAGetLastError函数来获得一段代码,这段代码专用来说明错误
原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
WSAGetLastError(
    void
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 如果想要手动设置WSAGetLastError获取的错误代码,可以调用WSASetLastError。
  • 注意:WSAStartup函数的调用不能使用WSAGetLastError来确定导致故障的特定错误(因为Winsock没有加载),用WSAStartup的返回状态判断(0是成功)

使用IP协议创建基本的Winsock调用来建立通信

  • Winsock是一种独立于协议的接口
  • IP是一种无连接协议,它不能确保数据传输的成功
  • TCP和UDP通过IP进行面向连接和无连接的数据通信
IPv4寻址

程序通过sockaddr_in结构来指定IP地址和服务端口信息
示例:

	sockaddr_in sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));
	sockAddr.sin_family = PF_INET;		//#define PF_INET         AF_INET,该字段设为AF_INET,以告知Winsock此时正在使用IP地址族。
	sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	sockAddr.sin_port = htons(1234);	//端口号在选择的时候,需要特别注意,某些端口是“已知的”服务保留的

sockaddr_in结构:

struct sockaddr_in {
        short   sin_family;		//协议族
        u_short sin_port;		//端口号
        struct  in_addr sin_addr;	//ip
        char    sin_zero[8];		//填充项,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样
};

inet_addr函数可以将一个点分的IP转换成一个32位的无符号长整数。

字节排序

网络字节(network-byte)顺序:从最有意义到最无意义的字节排序(big-endian)
将一个数从主机字节顺序转换成网络字节顺序API(具体用法用到的时候再补充吧):

htol
WSAHtol
htons
WSAHtons
创建套接字
SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
__checkReturn
SOCKET
WSAAPI
socket(
    __in int af,			//协议的地址族
    __in int type,		//协议的套接字类型。TCP/IP对应SOCK_STREAM;UDP/IP对应SOCK_DGRAM
    __in int protocol	//用于在给定地址族和套接字类型具有多重入口时,对具体的传送作限定。TCP--IPPROTO_TCP;UDP--IPPROTO_UDP
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */

面向连接的通信

  • 在IP中,面向连接的通信是通过TCP/IP协议完成的。
  • TCP提供两个计算机间可靠无误的数据传输。
在Winsock中,建立通信的步骤:
服务器:

1. 用Socket或WSASocker将给定的协议的套接字绑定到它已知的名称上

bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
bind(
    __in SOCKET s,		//等待客户机连接的套接字
    __in_bcount(namelen) const struct sockaddr FAR * name,	//缓冲区,根据使用的协议,将实际地址填充到一个地址缓冲区,并在调用bind的时候将其转换为一个struct sockaddr
    __in int namelen	//要传递的、由协议决定的地址结构的长度
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 一旦出错,bind会返回SOCKET_ERROR。
  • 对于bind而言,最常见的错误是WSAEADDINUSE。
  • 如果使用的是TCP/IP,WSAEADDINUSE表示的是另一个进程已经同本地的IP接口及端口号绑定到了一起,或者那个IP和端口号处于TIME_WAIT状态。
  • 假如对一个已被绑定的套接字调用bind,返回的会是WSAEFAULT错误。

2. 将套接字置为监听模式
listen(servSock, 20);
函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
int
WSAAPI
listen(
    __in SOCKET s,	//被绑定的套接字
    __in int backlog		//指定了被搁置的连接的最大队列长度。
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 因为在同一时间,可能会有多个服务器的连接请求,如果请求的数量过backlog,那么前面的backlog个请求被放在一个“挂起”队列中,以便应用程序(服务器)依次为他们提供服务。而backlog之后的连接请求都会失败,错误为WSAECONNREFUSED。
  • 一旦服务器接收了一个连接,那个连接请求就会从队列中删去,以便别人可以继续发出请求。
  • WSAEINVAL通常意味着,在调用listen之前没有调用bind。

3. 若一台客户机试图创立连接,服务器必须通过accept或WSAAccept调用来接受连接

	SOCKADDR clntAddr;
	int nSize = sizeof(SOCKADDR);
	SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);

函数原型:

#if INCL_WINSOCK_API_PROTOTYPES
WINSOCK_API_LINKAGE
__checkReturn
SOCKET		//返回值:一个新的套接字,用于已经被接受的那个客户机的连接。(后续的消息发送什么的应该用这个套接字)
WSAAPI
accept(
    __in SOCKET s,	//监听的套接字
    __out_bcount_opt(*addrlen) struct sockaddr FAR * addr,	//新连接的地址信息(输出)
    __inout_opt int FAR * addrlen	//连接结构体的大小
    );
#endif /* INCL_WINSOCK_API_PROTOTYPES */
  • 对于连接成功的客户机,在后续的操作中应该使用accept返回的新的套接字
  • 原本的监听套接字仍然用于接受其他客户机的连接,并且仍处于监听模式
  • WSAAccept可以根据指定条件接受一个连接
客户机:

客户机的创建步骤:

  1. 创建一个套接字
  2. 建立一个SOCKADDR地址结构,结构名称为准备连接到的服务器名。(对于TCP/IP,这是客户机应用程序锁监听的服务器的IP地址和端口号)
  3. 用connect或WSAConnect初始化客户机与服务器的连接
套接字状态

连接:

  • 对于每个套接字来说,它的初始状态都是CLOSED。
  • 若客户机初始化了一个连接,就会向服务器发送一个SYN包,同时将客户机套接字的状态置为SYN_SENT。
  • 服务器收到SYN包后,会发出一个SYN-ACK包,客户机需要用一个ACK包对它做出响应。此时,客户机的套接字将处于ESTABLISHED状态。
  • 如果服务器一直不发送SYN-ACK包,客户机就会超时,并返回CLOSED状态。
  • 服务器的套接字同本地接口及端口绑定起来,并在它上面进行监听,那么套接字的状态便是LISTEN。
  • 客户机试图与服务器连接时,服务器就会收到一个SYN包,并用一个SYN-ACK包作出回应。此时,服务器套接字的状态就编程SYN_RCVD。
  • 最后,客户机发出一个ACK包,它将使服务器套接字的状态变成ESTABLISHED。

关闭连接:
一旦应用程序处于ESTABLISHED状态,就可以通过两种方法来关闭它。
主动关闭:

  • 应用程序会发出一个FIN包(应用程序调用closesocket或shutdown),并且套接字编程FIN_WAIT_1。
  • 通信的对方会用一个ACK包作为回应,套接字的状态随之编程FIN_WAIT_2

你可能感兴趣的:(网络)