WORD wVersionRequested,//指定Winsock的版本,高位字节为副版本,低位字节为主版本
//用宏MAKEWORD(X,Y)X高字节,Y低字节获得该参数正确是值
LPWSADATA lpWSAData //指向LPWSADATA的结构体的指针,该结构包含了加载库版本有关的信息
);
在调用Winsock函数之前没有加载Winsock库则会返回SOCKET_ERROR错误
typedef struct WSAData{
WORD wVersion;//打算使用Winsock的版本
WORD wHighVersion;//返回现有Winsock的最高版本
char szDecription[WSADESCRIPTION_LEN+1];//没具体作用
char szSystemStatus[WSASYS_STATUS_LEN+1];//没具体作用
usigned short iMaxSockets;//可以同时打开的套接字数
usigned short iMaxUdpDg;//数据报的最大长度
char FAR* lpVendorInfo;//win32没使用这个字段
}WSADATA,*LPWSADATA;
关闭对Winsock DLL的调用
int WSACleanup(void);
若成功则返回0,否则返回错误
不成功的Winsock调用返回最常见的值是SOCKET_ERROR,是值为-1的常量,若错误发生了可用WSAGetLastError来获得一段代码,这段代码明确是表明产生错误的原因
int WSAGetLastError(void);
流套接字编程模型
流套接字的服务进程和客户进程在通信前必须创建各自的套接字并建立连接,然后才能对相应的套接字进行“读”、“写”操作,实现数据的传输,步骤如下:
(1)服务器进程创建套接字
SOCKET socket(
int af,//指定网络类型,一般去AF_INET表示在Internet域中通信
int type,//指定套接字类型,若取SOCK_STREAM表示流套接字,SOCK_DGRAM数据报套接字
int protocol//一般取默认值0,TCP/IP
);
服务进程总先于客户进程启动,服务器进程调用socket函数创建一个流套接字
若成功则返回SOCKET句柄,否则产生INVALID_SOCKET错误
(2)将本地地址绑定到所创建的套接字上已标识该套接字
int bind(
SOCKET s,//标识为绑定套接字的句柄,用以与等待的客户机连接
const struct sockaddr* name,//赋予套接字以地址
int namelen//地址的长度
);
struct sockaddr{
u_short sa_family;
char sa_data[14];
}
此结构随协议的不同而变化,因此一般与下一个该地址结构大小相同的sockaddr_in结构更为常用,
sockaddr_in标识TCP/IP协议下的地址,在TCP/IP协议下可以方便的将sockaddr_in强制转换为sockaddr结构
struct sockaddr_in{
short sin_family;//必须设为AF_INET,表示该socket处于Internet域
unsigned short sin_port;//指定服务端口,注意防止与常用的端口冲突
struct in_addr sin_addr;//无符号长整型,将IP地址保存成一个4字节的数,取INADDR_ANY允许服务器你应用监听主机计算机上每个网络接 //口上的客户机活动
char sin_zero[8];//要传递的由协议决定的地址长度;填充字段,使之与sockaddr结构长度一样
}
inet_addr()函数可把一个点式IP转换成一个32位的无符号长整数
(3)将套接字置入监听模式并准备接受连接请求,bind函数只是将一个套接字和一个指定的地址关联到一起,让一个套接字等候进入连接的API函数是listen
int listen(
SOKECT s,//未连接套接字的描述字,没有描述字是,此函数仍然试图正常的工作,他仍接受请求直至队列变空 有可用描述字是后续
//的一次listen()或accept()调用会将队列按照当前或最近的“后背日志”重新填充,如果可能将恢复监听申请进入连接请求
int backlog//指定正在等待连接的最大队列长度,
)
backlog参数非常重要,以为可能同时出现几个服务器连接请求,倘若此值小于实际的请求数时,那么超出的哪个请求会造成一个WSAECONNREFUSED 错误,一旦服务器接收了一个连接请求,那个请求就会从队列中删去,backlog参数本身由基层的协议提供者决定,若果出现非法值,那么会用与之最接近的一个合法值来取代
若无错误发生listen函数返回0,失败返回SOCKET_ERROR错误
进入监听状态之后,调用accept()函数使套接字做好接受客户连接的准备
SOCKET accept(
SOCKET s,//处于监听模式的套接字
struct sockaddr* addr,//一个有效的SOCKADDR_IN结构的地址
int* addlen//上面地址的长度
)
接受后服务器就可以为等待连接队列中的第一个连接请求提供服务了,accept返回后参数变量中会包含发出连接请求的那个客户机的IP地址信息,若再无连接请求服务进程被阻塞。
(4)客户进程调用socket函数创建客户端套接字
(5)客户端向服务器进程发出连接请求。通过调用connect函数可以建立一个端的连接
int connect(
SOCKET s,//未连接的数据报或流类套接字
const struct sockaddr FAR *name,//针对TCP的套接字地址结构,标识服务进程IP地址信息,若全为0返回WSAEADDRNOTAVAIL错误
int namelen//标识name参数的长度
)
若连接的计算机没有侦听指定端口这一进程,connect调用就去失败,并发生WSAECONNREFUSED错误,还有一个常见的错误WSAETIMEOUT超时
(6)当连接请求到来后,被阻塞服务进程的accept()函数如(3)中所述生成一个新的套接字与客户套接字建立连接,并向客户返回接受接收信号
(7)一旦客户机的套接字收到来自服务器的接收信号,则表示客户机与服务器已实现连接,则可以进行数据传输了。send。recv函数是进行数据收发的函数
int send(
SOCKET s,//已经建立连接的套接字
const char* buf,//字符缓冲区,包含即将发送的数据
int len,//指定即将发送的缓冲区内的字符数
int flags//有值0、MSG_DONTROUTE或MSG_OOB或这些标志的按位或运算
)
MSG_DONTROUTE 标志要求传送层不要将它发出的包路由出去,
MSG_OOB标志数据应该被带外发送
send函数返回发送数据的字节数,若发生错误,就返回SOCKET_ERROR。常见的错误是WSAECONNABORTED一般发生在虚拟回路由于超时或协议有错而中断的时候。发生种情况应该关闭这个套接字。若远程主机上的套接字被强行或意外将关闭则会发生WSAECONNRESET错误。另一个常见的错误是WSAETIMEOUT发生在连接由于网络故障或远程连接系统异常死机而引起的连接中断时。
成功的完成send调用并不意味着数据传送到达,若传送系统的缓冲区空间不够保存需传送的数据,除非套接字处于非阻塞I/O方式,否则send函数将阻塞。对于非阻塞的SOCK_STREAM类型的套接字,实际写的数据数目可能在1到所需大小之间,其值取决于本地和远端主机的缓冲区大小。
收发数据属于简单的char类型,没有Unicode版本,使用unicode时需要把字符串当做char*或转为char*发送,指定长度时,需将这个值乘2,也可以用WideCharToMultiByte将UNICODE转成ASCII码
int recv(
SOCKET s,//准备接受数据的套接字
char* buf,//即将收到的数据的字符缓冲区
int len,//准备接收的字节数或buf缓冲区的长度
int flags//值可以是0,MSG_PEEK或MSG_OOB,可以使用或运算,MSG_PEEK使有的数据复制到所提供的接受端缓冲内,但没有从系统缓 //冲区中将它删除
)
返回接收的字节数
(8)关闭套接字,释放资源closesocket()函数可以达到目的,但closesocket可能导致数据的丢失,因此应该在调用closesocket函数之前使用shutdown函数中断连接
int shutdown(
SOCKET s,//要中断的套接字
int how//描述禁止哪些操作,可取的值有SD_RECEIVE、SD_SEND或SD_BOTH
)
发生错误返回0,否则返回SOCKET_ERROR错误
int closesocket(SOCKET s);调用此函数之后再使用WSAE-OTSOCK错误
下图为流套接字编程模型
数据报套接字编程模型
数据报套接字是无连接的,编程过程比流套接字要简单一些
对于接收端(一般为服务器),先用socket建立套接字,再通过bind函数把这个和准备接收数据的IP地址信息绑定在一起。他不用调用listen和accept函数,只需等待接收数据,由于是无连接的它可以接收网络上任何一台机器所发的数据报。常用的接收数据函数是recvfrom
int recvfrom(
SOCKET s,
char *buf,
int len,
int flags,
struct sockaddr* from,//接收地址
int *fromlen//长度
)
对于发送有两种方法
(1)建立套接字,然后调用sendto函数
int sendto(
SOCKET s,
const char* buf,
int len,
int flags,
const struct sockaddr* to,
int tolen
)
在接收端或发送端结束时收发数据时,调用closesocket()函数释放套接字资源
数据报套接字编程时序如下图