IP地址結果
在winsock中,APP通過SOCKADDR_IN結構來指定IP和Port信息,
其中的sin_zero只充當填充項,以使SOCKADDR_IN結構和SOCKADDR結構的長度一樣。
unsigned long inet_addr(const char FAR* cp)用於將一個點分IP轉換成一個32位無符號長整數(按網絡字節順序)。
字節順序
在computer中把IP & Port指定成多字節數時,是按主機字節(host-byte)順序進行的,
但在網絡上指定IP & Port,標準指定必須使用big-endian即網絡字節(network-byte)順序表示。
主機字節順序轉成網絡字節順序:
u_long WSAAPI htonl( _In_ u_long hostlong);
int WSAAPI WSAHtonl(_In_ SOCKET s,
_In_ u_long hostlong,
_Out_ u_long *lpnetlong
);
u_short WSAAPI htons(_In_ u_short hostshort);
int WSAAPI WSAHtons(_In_ SOCKET s,
_In_ u_short hostshort,
_Out_ u_short *lpnetshort
);
網絡字節順序轉主機字節順序:
u_long WSAAPI ntohl(_In_ u_long netlong);
int WSAAPI WSANtohl(_In_ SOCKET s,
_In_ u_long netlong,
_Out_ u_long *lphostlong
);
u_short WSAAPI ntohs(_In_ u_short netshort);
int WSAAPI WSANtohs(_In_ SOCKET s,
_In_ u_short netshort,
_Out_ u_short *lphostshort
);
IP地址和主機名
IP地址不便於記憶,通常都使用主機名,使用如下這些地址&名稱解析函數,
可以將主機名(如www.somewebsite.com)解析爲IP地址、服務名稱(如FTP)和端口號:
getaddrinfo,
getnameinfo,
gethostbyaddr,
gethostbyname,
gethostname,
getprotobyname,
getprotobynumber,
getservbyname,
getservbyport等,
同時還有這些函數對應的異步版本,如:
WSAAsyncGetHostByAddr,
WSAAsyncGetHostByName,
WSAAsyncGetProtoByName,
WSAAsyncGetProtoByNumber,
WSAAsyncGetServByName,
WSAAsyncGetServByPort等
創建socket
有兩個函數可以創建套接字:
SOCKET WSAAPI socket(
_In_ int af,//IPv4爲AF_INET
_In_ int type,//tcp爲SOCK_STREAM,udp爲SOCK_DGRAM
_In_ int protocol//tcp爲IPPROTO_TCP,udp爲IPPROTO_UDP
);
SOCKET WSASocket(
_In_ int af,
_In_ int type,
_In_ int protocol,
_In_ LPWSAPROTOCOL_INFO lpProtocolInfo,
_In_ GROUP g,
_In_ DWORD dwFlags
);
//爲控制套接字的選項和行爲,提供了4個API:
setsockopt
getsockopt
ioctlsocket
WSAIoctl
綁定
int bind(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
//最常見的錯誤是WSAEADDRINUSE:如果使用的是tcp/ip,那麼表示該IP&Port已經被佔用了,或者該IP&Port處於TIME_WAIT狀態
//對一個已經bind的套接字調用bind,將返回WSAEFAULT錯誤
監聽
int listen(
_In_ SOCKET s,
_In_ int backlog//隊列長度,超出隊列時請求將被拒絕,連接段收到WSAECONNREFUSED錯誤
);
//最常見的錯誤是WSAEINVAL,表示在調用listen之前沒有調用bind
//另外listen也可能接受到WSAEADDRINUSE錯誤,該錯誤通常發生在bind調用
接受連接
接受連接的API:accept, WSAAccept, AcceptEx.
SOCKET accept(
_In_ SOCKET s,//被bind的socket
_Out_ struct sockaddr *addr,
_Inout_ int *addrlen
);
//當監聽套接字爲異步或者非阻塞模式,並且沒有連接被接受時,最常見的錯誤是WSAEWOULDBLOCK
服務器端過程
//1.初始化winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//2.創建監聽套接字
lstSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//3.建立SOCKADDR_IN地址結構
SOCKADDR_IN srvAddr;
srvAddr.sin_faminly = AF_INET;
srvAddr.sin_port = htons(6188);
//4.綁定
bind(lstSocket, (SOCKADDR*)&srvAddr, sizeof(srvAddr));
//5.監聽客戶端連接
listen(lstSocket, 5);
//6.接受新連接(通常循環接受多個連接)
clientSocket = accept(lstSocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
//7.在clientSocket上收發數據
//8.使用完關閉clientSocket
closesocket(clientSocket);
//9.關閉lstSocket
closesocket(lstSocket);
//10.釋放
WSACleanup();
客戶端過程
1)創建socket
2)建立SOCKADDR地址結構,指定服務器IP&Port
3)調用connect, WSAConnect或者ConnectEx建立客戶端和服務器的連接
int connect(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
//常見錯誤
客戶端的完整過程如下:
//1.初始化winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//2.創建客戶端套接字
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//3.建立SOCKADDR_IN地址結構,用於連接服務器
SOCKADDR_IN srvAddr;
srvAddr.sin_faminly = AF_INET;
srvAddr.sin_port = htons(6188);
srvAddr.sin_addr.s_addr = inet_addr("136.149.3.28");
//4.用套接字創建一個到服務器的連接
connect(s, (SOCKADDR*)&srvAddr, sizeof(srvAddr));
//5.使用套接字s收發數據
//6.使用完後,關閉套接字
closesocket(s);
//7.清理
WSACleanup();
TCP狀態
1)對於每個套接字而言,它的初始狀態都是CLOSED.
2)若服務器套接字同本地IP&Port綁定起來,並在它上面進行監聽,那麼套接字的狀態就是LISTEN狀態.
3)若客戶機初始化了一個連接,就會向服務器發送一個SYN包,同時將客戶機套接字狀態置爲SYN_SENT.
4)服務器收到SYN包後,會發送一個SYN_ACK包響應(如果服務器一直不發送SYN_ACK包,客戶機就會超時,並返回CLOSED狀體),服務器的套接字狀態變爲SYN_RCVD;
5)客戶機需要用一個ACK包對它(SYN_ACK包)進行響應,此時客戶機的套接字將處於ESTABLISHED狀態;這個ACK包將服務器套接字的狀態變成ESTABLISHED.
6) 14…
數據傳輸
所有收發數據的緩衝區都屬於簡單的char類型(面向字節的數據),它可以包含任何原始數據,這些原始數據是二進制,還是字符型,是無關緊要的。
發送:send/WSASend
接收:recv/WSARecv
它們出錯返回值都是SOCKET_ERROR,最常見的錯誤是WSAECONNABORTED和WSAECONNRESET,兩者都和正在被關閉相關(要麼由於超時被關閉,要麼由於通信方正在關閉連接). 另一個常見的錯誤是WSAEWOULDBLOCK,一般出現在非阻塞模式或異步狀態時,表示函數暫時不能完成。