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,一般出现在非阻塞模式或异步状态时,表示函数暂时不能完成。