1.Winsock的启动和终止
启动
调用int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)函数对Winsock DLL进行初始化,参数说明如下:
wVersionRequested:用于指定要加载的Winsock库版本,高四位是副版本号,第四位是主版本号;
lpWSAData:指定了加载的库版本的相关信息,其格式如下
#define WSADESCRIPTION_LEN 256
#define WSASYS_STATUS_LEN 128
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
#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;
typedef WSADATA FAR *LPWSADATA;
其中
wVersion:为Winsock版本号;
wHighVersion:返回现有Winsock最高版本;
szDescription和szSystemStatus:由特定的Winsock实施方案设定,用户在使用过程中无需设定;
iMaxSockets和iMaxUdpDg:分别为 可同时打开的套接字最大数目 和 数据报最大长度,一般不要使用它们,如果想知道数据报最大长度,应该使用WSAEnumProtocols函数来查询协议信息,而可同时打开套接字的最大数目不是固定的,和可使用内存量有关;
lpVendorInfo:是为Winsock实施方案有关的指定厂商预留的,一般使用不到
终止
调用int WSACleanup(void)函数终止对Winsock DLL的使用,并释放资源,以备下次使用。成功返回0,否则返回错误。
2.错误检查
调用int WSAGetLastError(void)函数可以获得错误代码。
3.流套接字编程模型
(1)服务器进程先于客户进程启动
i)调用SOCKET socket(int af, int type, int protocol)函数创建一个套接字,其中
af:用于指定网络地址类型,一般取AF_INET,标识该套接字在Internet域中进行通信;
type:用于指定套接字类型,若取SOCK_STREAM标识要创建的套接字是流套接字,取SOCK_DGRAM则创建数据报套接字;
protocol:用于指定网络协议,一般取0,表示TCP/IP协议
若套接字创建成功,则返回所创建套接字句柄SOCKET,否则产生INVALID_SOCKET错误。
ii)调用int bind(SOCKET s, const struct sockaddr *name, int namelen)将本地地址绑定到所创建的套接字上,其中
s:为先前创建的套接字;
name:为赋予套接字的地址,结构如下
struct sockaddr
{
u_short 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];
};
sin_family:必须设为AF_INET,标识该socket处于Internet域;
sin_port:用于指定服务端口,如果端口号设置为0,则Winsock将为应用程序分配一个值在1024至5000之间的唯一的端口;
sin_addr:用于把一个IP保存为一个四字节的数,无符号长整型数类型。可以使用函数unsigned long PASCAL FAR inet_addr (IN const char FAR * cp)将IP地址字符串转换为四字节无符号长整型数。该值取为INADDR_ANY将允许监听每个网络接口上的客户机活动;
sin_zero:无实际用处;
bind()函数的作用是将一个套接字和指定的网络地址关联在一起,让套接字等候进入链接的API函数则是listen()函数
iii)调用int listen(SOCKET s, int backlog)键套接字置入监听模式,并准备接受请求,其中
s:为先前创建的套接字;
backlog:指定在等待链接的最大列队长度;
iv)调用SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen)接受客户端请求,并向客户端返回接收信号
s:为处于监听模式的套接字;
addr:SOCKADDR_IN结构,将包含发出链接请求的客户机IP地址信息;
addrlen:为addr的长度;
返回值:一个新的套接字,它对应于已接受的那个客户机链接,对该客户机的后续操作都应该使用该套接字。
(2)客户端
i)调用socket()函数创建客户端套接字;
ii)调用int connect(SOCKET s, const struct sockaddr FAR *name, int namelen)函数想服务器端发送链接请求,其中
s:一个未链接的Socket;
name:针对TCP的套接字地址结构,它标识服务器进程IP地址信息;
namelen:name参数的长度。
常见错误:
WSAEADDRNOTAVAIL->name成员全为零错误;
WSAECONNREFUSED->服务器端没有进程监听指定端口;
WSAETIMEDOUT->链接超时;
(3)数据发送和接收
i)调用int send(SOCKET s, const char * buf, int len, int flags)函数发送数据,其中
s:为已建立链接的套接字;
buf:为要发送的数据内容;
len:要发送数据长度;
flags:[0]、[MSG_DONTROUTE]要求传输层不要将其发出的包路由出去、[MSG_OOB]标志数据应为带外发送,可以是三者按位或;
返回值:发送字节数
常见错误:
WSAECONNABORTED->虚拟回路由于超时或协议中有错误而中断,发生该错误时,应该关闭该套接字;
WSAECONNRESET->远程机上的套接字被强行或意外关闭;
WSAETIMEOUT->网络故障或远程机意外死机而引起的链接中断;
ii)调用int recv(SOCKET s, char *buf, int len, int flags)
s:准备接受数据的套接字;
buf:即将收到数据的字符缓冲区;
len:buf缓冲区长度;
flagship:[0]无特殊行为、[MSG_PEEK]是有用的数据复制到所提供的接收端缓冲、[MSG_OOB],或者他们的按位或
返回值:接收字节数
(4)关闭套接字
i)调用int shutdown(SOCKET s, int how)函数中断链接,其中
s:处于链接状态的Socket;
how:用于描述禁止那些操作,[SD_RECEIVE][SD_SEND][SD_BOTH];
返回值:成功为0,否则返回错误
ii)调用int closesocket(SOCKET s)函数关闭套接字
流程图
4.数据报套接字编程模型
数据报套接字是无链接的,编程模式相对简单。
i)对于接收端,先用socket()函数建立套接字,在通过bind()函数把套接字和准备接受数据的IP地址绑定,这和前面的流套接字是一致的,但不同的是它不必调用listen()和accept()函数,只需等待接收数据。并且是无链接的,因此可以接受网络上的任何一台计算机所发的数据报,常用的接收函数是int recvfrom(SOCKET s, char * buf, int len, int flags, struct sockaddr * from, int * fromlen)前面四个参数和recv()一样,当收到数据时,from中将被填入发送端IP地址
ii)对于发送端,先建立套接字,然后调用int sendto(SOCKET s, const char * buf, int len, int flags, const struct sockaddr *to, int tolen)函数,其中to中包含接受数据端IP地址。
iii)因为数据报套接字没有链接,所有只需直接调用closesocket()函数关闭套接字即可。
流程图