Windows Socket Programming 网络编程系列 简单客户端与服务器

Windows Socket 2
A socket handle can optionally be a file handle in Windows Sockets 2.
A socket handle from a Winsock provider can be used with other non-Winsock functions such as ReadFile, WriteFile, ReadFileEx, and WriteFileEx.

library: ws2_32.lib(set in Project->Linker->Input->Additional Dependencies)
header: winsock2.h(contains winsock functions, structures and definitions)
ws2tcpip.h contains new functions and structures used to retrieve ip address.
Iphlpapi.h header file is required if an application is using the IP Helper APIs.
Note: #include line for Iphlpapi.h should after the #include line for winsock2.h. windows.h is internally included in winsock2.h. So it is not necessary to include windows.h
For historical reasons, the Windows.h header defaults to including the Winsock.h header file for Windows Sockets 1.1.
So if you include windows.h file will confilt with the socket 2 defined in winsock2.h. In order to prevent this confliction, using WIN32_LEAN_AND_MEAN macro to stop windows.h define windows socket 1.1.

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <iostream>
using namespace std;
int main() {
WSADATA wsaData;
int iRet = 0;
iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iRet != 0)
{
cout<<::GetLastError()<<endl;
return 1;
}
cout<<std::hex<<wsaData.wVersion<<endl; // 202
cout<<std::hex<<wsaData.wHighVersion<<endl; // 202
cout<<std::dec<<wsaData.szDescription<<endl; // WinSock 2.0
cout<<wsaData.szSystemStatus<<endl; // Running
cout<<wsaData.iMaxSockets<<endl; // 0
return 0;
}

Both gai_strerror and WSAGetLastError can return the error code for the last operation for invoking the api in windows socket program, the later is thread-safe.

GetAddInfo is a macro for GetAddrInfoW or getaddrinfo by whether UNICODE or _UNICODE is defined. The parameter type for this macro should use TCHAR and ADDRINFOT.
GetAddrInfoW uses wchar_t and addrinfoW as its parameter; getaddrinfo uses char and addrinfo.
Syntax for getaddrinfo:

int WSAAPI getaddrinfo(
__in const char* nodename,
__in const char* servname,
__in const struct addrinfo* hints,
__out struct addrinfo** res
);
nodename is for host name or ip string(dotted-decimal address ipv4 and hex address ipv6), e.g. “127.0.0.1” for ipv4, “localhost” for host name. If it contains “localhost” or “..localmachine”, all registered addresses are returned.
servname is for the port number, e.g. “11223”.
hints is the addrinfo structure which limit the result of elements in res parameter.
res is the addrinfo structure list, which contains all elements matches the hints.
Syntax for addrinfo:
typedef struct addrinfo
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST
int ai_family; // AF_INET(IPV4), AF_INET6(IPV6), AF_NETBIOS(NETBIOS), AF_BTM(BLUE TOOTH)…
int ai_socktype; // SOCK_STREAM(TCP), SOCK_DGRAM(UDP), SOCK_RAW(RAW)…
int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
size_t ai_addrlen; // Length of ai_addr
char * ai_canonname; // Canonical name for nodename
__field_bcount(ai_addrlen) struct sockaddr * ai_addr; // Binary address
struct addrinfo * ai_next; // Next structure in linked list
}
ADDRINFOA, *PADDRINFOA;
  • A value of AI_PASSIVE for ai_flag indicates the socket returned by bind function as Server end, set node name to be null; Otherwise, the socket returned by connect function for connection-oriented protocol(TCP), or connect, sendto or send functions for a connectionless protocol as client and set nodemane and servname as variable.
  • A value of AF_UNSPEC for ai_family indicates the caller will accept any protocol family. Be aware that AF_UNSPEC and PF_UNSPEC are the same. Note that AF_ prefix addresses are identical to PF_ prefix protocol family.
  • A value of zero for ai_socktype indicates the caller will accept any socket type.
  • A value of zero for ai_protocol indicates the caller will accept any protocol.
  • The ai_addrlen member must be set to zero.
  • The ai_canonname member must be set to NULL.
  • The ai_addr member must be set to NULL.
  • The ai_next member must be set to NULL.
Tips:
For retrieving regarding ipv4, set ai_family as AF_INET;
For retrieving TCP, set ai_socktype as SOCK_STREAM;
If ai_family and ai_socktype are specified for IPV4 or IPV6, ai_protocol should be set as IPROTO_TCP/IPROTO_UDP;
For more details about addrinfo, refer MSDN.
So it is a good manner to zero the content of addrinfo before using it.

All information returned by the GetAddrInfoW function pointed to by the ppResult parameter is dynamically allocated, call FreeAddrInfo to release the memory.
Syntax for FreeAddrInfo asci version
void freeaddrinfo(
__in struct addrinfo* ai
);
Note:This continues to free it until ai_next is null.
 
sockaddr, sockaddr_in, in_addr for ipv4;
struct sockaddr {
ushort sa_family;
char sa_data[14];
};


struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Note: struct key word could be ignore with windows vista and later. Otherwise, should keep it.
Defferences between sockaddr and sockaddr_in
  • sa_family is address family, like “AF_XXX”, for internet, using AF_INET. sin_family is the same as sa_family. sin_family must be AF_INET.
  • If sa_family is AF_INET, sa_data in sockaddr is equal to sin_addr and sin_zero in sockaddr_in. sin_zero Padding to make structure the same size as SOCKADDR. So sockaddr could be used for other protocol; sockaddr_in just for internet protocal(TCP/UDP).
  • both of them in network byte order.
sockaddr_in6, sockaddr_in6_old, in6_addr for ipv6;
struct sockaddr_in6 {
short sin6_family;
u_short sin6_port;
u_long sin6_flowinfo;
struct in6_addr sin6_addr;
u_long sin6_scope_id;
};

typedef struct sockaddr_in6 SOCKADDR_IN6;
typedef struct sockaddr_in6 *PSOCKADDR_IN6;
typedef struct sockaddr_in6 FAR *LPSOCKADDR_IN6;



struct sockaddr_in6_old {
short sin6_family;
u_short sin6_port;
u_long sin6_flowinfo;
struct in6_addr sin6_addr;
};
sin6_family in sockaddr_in6 and sockaddr_in6_old must be AF_INET6.
SOCKADDR_STORAGE
typedef struct sockaddr_storage {
ADDRESS_FAMILY ss_family;
CHAR __ss_pad1[_SS_PAD1SIZE];
__int64 __ss_align;
CHAR __ss_pad2[_SS_PAD2SIZE];
} SOCKADDR_STORAGE, *PSOCKADDR_STORAGE;
SOCKADDR_STORAGE can support all family address like sockaddr. And it can contain either IPV4 or IPV6 protocol.
 
SOCKET
Definition
typedef UINT_PTR        SOCKET;
Initialize
SOCKET socket = INVALID_SOCKET;
INVALID_SOCKET is defined in winsock2.h as –1.
Retrieve
socket function
using the first result matched with hints passed in GetAddrInfo function in this way.
SOCKET WSAAPI socket(
_In_ int af,
_In_ int type,
_In_ int protocol
);
you can use the address result retrieving from GetAddrInfo funciton, like this.
_srvsck = socket(ret->ai_family, ret->ai_socktype, ret->ai_protocol);
Or directly define the address family, socket type and protocol type.
_srvsck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind function
The bind function associates a local address with a socket.
int bind(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
you can use the address result retrieving from GetAddrInfo funciton, like this.
iRet = bind( _srvsck, ret->ai_addr, (int)ret->ai_addrlen);
Or just directly define an SOCKADDR_IN structure and then bind it.
sockaddr_in undef;
ZeroMemory(&undef, sizeof(sockaddr_in));
undef.sin_family = AF_INET;
undef.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
undef.sin_port = ::htons(11221);
iRet = bind(_srvsck, (sockaddr*)&undef, sizeof(sockaddr_in));
if using specified address and port in the address info, should use getsockname function to get address info.
undef.sin_addr.S_un.S_addr = INADDR_ANY;
iRet = bind(_srvsck, (sockaddr*)&undef, sizeof(sockaddr_in));
sockaddr_in _info;
ZeroMemory(&_info, sizeof(sockaddr_in));
int _len = sizeof(sockaddr_in);
iRet = ::getsockname(_srvsck, (sockaddr*)&_info, &_len);
if (iRet == SOCKET_ERROR)
{
iRet = WSAGetLastError();
wcout<<"Failed to getsockname:. (ErrCode: "<<iRet<<")."<<endl;
}
else
{
cout<<"length of sock address:"<<_len<<endl;
char * _paddr = ::inet_ntoa(_info.sin_addr);
if (_paddr != nullptr)
cout<<_paddr<<endl;
else
cout<<"empty ip address"<<endl;
int _port = _info.sin_port;
_port = ::htons(_info.sin_port);
cout<<"port:"<<_port<<endl;
}

listen function
The listen function places a socket in a state in which it is listening for an incoming connection.
int listen(
_In_ SOCKET s,
_In_ int backlog
);
If set backlog to SOMAXCONN, the underlying service provider responsible for socket s will set the backlog to a maximum reasonable value. If a connection request arrives and the queue is full, the client will receive an error with an indication of WSAECONNREFUSED. If the listen function is called on an already listening socket, it will return success without changing the value for the backlog parameter.
accept function
The accept function permits an incoming connection attempt on a socket.
SOCKET cltSck = INVALID_SOCKET;
SOCKADDR_IN cltAddr;
int cltAddrLen = sizeof(SOCKADDR_IN);
cltSck = accept(_srvsck, (SOCKADDR*)&cltAddr, &cltAddrLen);
if (cltSck == INVALID_SOCKET)
{
iRet = ::WSAGetLastError();
wcout<<"Failed to accept: "<<gai_strerror(iRet)<<" (ErrCode: "<<iRet<<")."<<endl;
break;
}
The accept function is used with connection-oriented socket types such as SOCK_STREAM. Set the second and third argument as nullptr, then no information about the remote address of the accepted socket is returned.
connect function
The connect function establishes a connection to a specified socket.
ADDRINFOT *res = nullptr, hints;
ZeroMemory(&hints, sizeof(ADDRINFOT));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

iRet = GetAddrInfo(_straddr.c_str(), _strport.c_str(), &hints, &res);
_cltSck = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
iRet = connect(_cltSck, res->ai_addr, res->ai_addrlen);

recvfrom function
The recvfrom function receives a datagram and stores the source address.
iRet = recvfrom(cltSck, (char*)msg, sizeof(TCHAR)*MAX_PATH, 0, (PSOCKADDR)&senderSck, &senderSckSize);
If no error occurs, recvfrom returns the number of bytes received. If the connection has been gracefully closed, the return value is zero. Otherwise, a value of SOCKET_ERROR is returned.
send function
The send function is used to write outgoing data on a connected socket.
send(cltSck, (char*)sendmsg, (::_tcslen(sendmsg) + 1) * 2, 0);

For complete code for server and client, please click the link.

APPENDIX

Winsock Kernel Socket Categories

Basic Sockets
Basic sockets are used only to get and set transport stack socket options or to perform socket I/O control operations. Basic sockets cannot be bound to a local transport address and do not support sending or receiving network data.
Listening Sockets
Listening sockets are used to listen for incoming connections from remote transport addresses. The functionality of a listening socket includes all of the functionality of a basic socket.
Datagram Sockets
Datagram sockets are used to send and receive datagrams. The functionality of a datagram socket includes all of the functionality of a basic socket.
Connection-Oriented Sockets
Connection-oriented sockets are used to send and receive network data over established connections. The functionality of a connection-oriented socket includes all of the functionality of a basic socket.

AF_INET and AF_INET6

  AF_INET AF_INET6
Socket Address Structure SOCKADDR_IN SOCKADDR_IN6
Socket Type SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW
Combinations
Basic Sockets
SOCK_STREAM + IPPROTO_TCP
SOCK_DGRAM + IPPROTO_UDP
SOCK_RAW + IPPROTO_XXX
Listening Sockets
SOCK_STREAM + IPPROTO_TCP
Datagram Sockets
SOCK_DGRAM + IPPROTO_UDP
SOCK_RAW + IPPROTO_XXX
Connection-Oriented Sockets
SOCK_STREAM + IPPROTO_TCP
Same as AF_INET

System supported socket catalog

On Windows XP and later, the following command can be used to list the Windows Sockets catalog to determine the service providers installed and the address family, socket type, and protocols that are supported.
netsh winsock show catalog

原文地址:https://www.cnblogs.com/rogerroddick/p/2857594.html