第一章 Winsock简介
Winsock是一种标准的API(应用程序编程接口),主要用于网络中的数据通信,它允许两个或多个应用程序(或进程)在同一台机器上或通过网络相互通信。
1.1 Winsock头文件及库文件
WINSOCK2.H , WINSOCK.H , MSWSOCK.H(此头文件用于微软专用编程扩展,这些扩展常用于高效Winsock应用程序的开发)
在编译采用了WINSOCK2.H的应用程序时,须链接到WS2_32.LIB库。使用WINSOCK.H(比如在Windows CE中)时须使用WSOCK32.LIB。如果从MSWSOCK.H中使用扩展API,还必须链接MSWSOCK.DLL。一旦包含了必需的头文件和链接环境,就可以开始编写应用程序代码了,这时需要初始化Winsock。
1.2 Winsock的初始化
每个Winsock应用都必须加载合适的Winsock DLL版本。加载Winsock库是通过调用WSAStartup函数实现的。这个函数的定义如下:
int WSAStartup(
WORD wVersionRequested , //用于指定准备加载的Winsock库的版本
LPWSADATA lpWSAData //指向LPWSADATA结构的指针
);
在使用Winsock接口编写好应用程序之后,应该调用WSACleanup函数。这个函数能使Winsock释放所有由Winsock分配的资源,并取消这个应用程序挂起的Winsock调用。WSACleanup函数的定义为:
int WSACleanup(void);
1.3 错误检查和处理
如果调用Winsock函数时出现了错误,可以用WSAGetLastError函数来获得一段代码,这段代码专用来说明错误。该函数定义为:int WSAGetLastError(void);
1.4 协议寻址
在Winsock中,应用程序通过SOCKADDR_IN结构来指定IP地址和服务端口信息,该结构的格式如下:
struct sockaddr_in
{
short sin_family; //必须设为AF_INET,以告知Winsock此时正在使用IP地址族
u_short sin_port;
struct in_addr sin_addr;//把IPv4地址作为一个4字节的量存储起来,它是无符号长整数类型
char sin_zero[8];//只充当填充项,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样
};
unsigned long inet_addr(//此函数可把一个点分IP地址转换为一个32位的无符号长整数
const char FAR *cp //空终止字符串,用于接受点分表示法的IP地址
);
1.5 创建套接字
熟悉Winsock的人应该知道,API是建立在套接字概念基础上的。套接字是传输提供程序的句柄。
SOCKET socket(int af , int type , int protocol);
1.6 面向连接的通信
1.6.1 服务器API函数
服务器:socket()/WSASocket()-->绑定-->监听-->Accept()/WSAAccept()
客户机:socket()/WSASocket()-->地址解析-->Connect()/WSAConnect()
1.6.1.1 绑定
int bind(
SOCKET s, //代表用来等待客户机连接的那个套接字
const struct sockaddr FAR* name, //一个普通的缓冲区
int namelen //代表要传递的,由协议决定的地址结构的长度
);
1.6.1.2 监听
int listen(
SOCKET s, //被绑定的套接字
int backlog //指定了被搁置的连接的最大队列长度
);
注意:一旦服务器接受了一个连接,那个连接请求就会从队列中删去,以便别人可继续发出请求。
1.6.1.3 接受连接
SOCKET accept(
SOCKET s, //被绑定的套接字,它处于监听模式
struct sockaddr FAR* addr, //一个有效的SOCKADDR_IN结构的地址
int FAR* addrlen //SOCKADDR_IN结构的长度
);
accept函数返回后,addr结构中会包含发出连接请求的那个客户机的IPv4地址信息,而addrlen参数则指出addr结构的长度。此外,accept会返回一个新的套接字描述符,它对应于已经被接受的那个客户机连接。该客户机后续的所有操作都应该使用这个新的套接字。至于原来那个监听套接字,它仍然用于接受其他客户机连接,而且仍处于监听模式。
1.6.2 客户端API函数
创建客户机只需3步操作:
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时(把SD_SEND当作第2个参数),会向通信对方发出一个FIN包,而且套接字的状态将变成FIN_WAIT_1。正常情况下,通信对方会用一个ACK包作为回应,套接字的状态随之变成FIN_WAIT_2。如通信对方也关闭了连接,它会发出一个FIN包,我们的机器则会以一个ACK包作为回应,并将套接字的状态置为TIME_WAIT。
TIME_WAIT状态的主要作用是,在TCP连接处于2MSL等待状态的时候,定义那个连接的一对套接字不可以被重新使用。这对套接字由本地IP端口以及远程IP端口组成。某些TCP实施方案不允许重新使用处于TIME_WAIT状态下的套接字对中的任何端口号。
int connect(
SOCKET s,
const struct sockaddr FAR* name,//TCP的套接字地址结构(SOCKADDR_IN)表示要连接到的服务器
int namelen
);
1.6.3 数据传输
注意:所有关系到收发数据的缓冲区都属于简单的char类型,即面向字节的数据。事实上,它可能是一个包含任何原始数据的缓冲区,至于这个原始数据是二进制数据,还是字符型数据,则无关紧要。
1.6.3.1 send和WSASend
int send(
SOCKET s, //用于发送数据的套接字
const char FAR * buf, //指向字符缓冲区的指针,该缓冲区中包含即将发送的数据
int len, //指定即将发送的缓冲区内的字符数
int flags
);
int WSASend(
SOCKET s,
LPWSABUF lpBuffers, //指向一个或多个WSABUF结构的指针
DWORD dwBufferCount, //准备传递的WSABUF结构数量
LPOWORD lpNumberOfBytesSent,//指向DWORD(WSASend调用返回)的指针,包含已发送字节数
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
1.6.3.2 WSASendDisconnect
int WSASendDisconnect(
SOCKET s,
LPWSABUF lpOutboundDisconnectData
);
1.6.3.3 recv和WSARecv
int recv(
SOCKET s,
char FAR* buf, //用于接收数据的字符缓冲区
int len, //准备接收的字节数或buf缓冲区的长度
int flags
);
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers, //指向一个或多个WSABUF结构的指针
DWORD dwBufferCount, //准备传递的WSABUF结构数量
LPOWORD lpNumberOfBytesRecvd,//指向DWORD(WSASend调用返回)的指针,包含已发送字节数
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
1.6.3.4 WSARecvDisconnect
int WSARecvDisconnect(
SOCKET s,
LPWSABUF lpInboundDisconnectData
);
1.6.4 流协议
1.6.5 中断连接
1.6.5.1 shutdown
int shutdown(
SOCKET s,
int how //SD_RECEIVE , SD_SEND , SD_BOTH
);
1.6.5.2 closesocket
int closesocket(SOCKET s);
1.7 无连接通信
1.7.1 接收端
int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR* from,
int FAR* fromlen
);
int WSARecvFrom(
SOCKET s,
LPWSABUF lpBuffers, //指向一个或多个WSABUF结构的指针
DWORD dwBufferCount, //准备传递的WSABUF结构数量
LPOWORD lpNumberOfBytesRecvd,//指向DWORD(WSASend调用返回)的指针,包含已发送字节数
LPDWORD lpFlags,
struct sockaddr FAR * lpFrom,
LPINT lpFromlen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
1.7.2 发送端
int sendto(
SOCKET s,
const char FAR * buf,
int len,
int flags,
const struct sockaddr FAR * to,
int tolen
);
int WSASendTo(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
const struct sockaddr FAR * lpTo,
int iToLen,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
1.7.3 基于消息的协议
1.7.4 释放套接字资源
1.8 其他API函数
1.8.1 getpeername
该函数用于获得通信方的套接字地址信息,该信息是关于已建立连接的那个套接字的。
int getpeername(
SOCKET s,
struct sockaddr FAR* name,
int FAR* namelen
);
1.8.2 getsockname
该函数是对应getpeername的。它返回的是给定套接字的本地接口的地址信息。
int getsockname(
SOCKET s,
struct sockaddr FAR* name,
int FAR* namelen
);
1.8.3 WSADuplicateSocket
此函数用来建立WSAPROTOCOL_INFO结构,该结构可传递到另一个进程,这样就可用另一个进程打开指向同一个下层套接字的句柄,如此一来,这个进程也能对该资源进行操作。注意,这一点只在进程之间的操作时才有必要;同一个进程中的线程可自由传递套接字描述符。
int WSADuplicateSocket(
SOCKET s, //准备复制的套接字句柄
DWORD dwProcessId, //打算使用所复制的套接字为进程的ID
LPWSAPROTOCOL_INFO lpProtocolInfo
);
1.9 Windows CE
winsock.h , WinMain