0:函数库头文件
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib ")
1:WSAStartup 初始化Ws2_32.dll的函数
WSAStartup 函数用于初始化供进程调用的Winsock相关的dll
int WSAStartup(
2 __in WORD wVersionRequested,
3 __out LPWSADATA lpWSAData
4 );
参数
标识了用户调用的Winsock的版本号。高字节指明辅版本编号,低字节指明主版本编号。通常使用MAKEWORD来生成一个版本号。 当前Winsock sockets的版本号为2.2,用到的dll是 Ws2_32.dll。
lpWSAData
指向WSADATA结构体的指针,lpWSAData返回了系统对Windows Sockets 的描述。
返回值
如果调用成功,WSAStartup 函数返回0。否则,将返回五种错误代码之一。但绝对不能使用WSAGetLastError获取错误代码。
WSAData wsa;
2 if (::WSAStartup(MAKEWORD(2,2),&wsa) != 0)
3 {
4 cout<<"WSAStartup error"<<endl;
5 return 0;
6 }
2:WSACleanup 释放Ws2_32.dl的l函数
该函数释放对Winsock链接库的调用
int WSACleanup(void);
返回值0表示正常退出,返回值SOCKET_ERROR表示异常。返回值是SOCKET_ERROR,可以调用 WSAGetLastError.查看错误代码。需要注意的是,在多线程环境下,WSACleanup 函数将终止所有线程的socket操作
3:socket 创建socket的函数
SOCKET WSAAPI socket(
2 __in int af,
3 __in int type,
4 __in int protocol
5 );
指明地址簇类型,常用的地址簇如下,其余地址簇在Winsock2.h中定义。
af
AF_UNSPEC(未指明)、AF_INET(IPv4)、AF_NETBIOS(NETBIOS地址簇)、AF_INET6(IPv6)、AF_IRDA(Infrared Data Association (IrDA)地址簇)、AF_BTM(Bluetooth)。
type
SOCK_STREAM(流套接字,使用TCP协议)、SOCK_DGRAM(数据报套接字,使用UDP协议)、SOCK_RAW(原始套接字)、SOCK_RDM(提供可靠的消息数据报文,reliable message datagram)、SOCK_SEQPACKET(Provides a pseudo-stream packet based on datagrams,在UDP的基础上提供了伪流数据包)。
protocol
指明数据传输协议,该参数取决于af和type参数的类型。protocol参数在Winsock2.h and Wsrm.h定义。通常使用如下3中协议
IPPROTO_TCP(TCP协议,使用条件,af是AF_INET or AF_INET6、type是SOCK_STREAM )
IPPROTO_UDP(UDP协议,使用条件,af是AF_INET or AF_INET6、type是SOCK_DGRAM)
IPPROTO_RM(PGM(Pragmatic General Multicast,实际通用组播协议)协议,使用条件,af是AF_INET 、type是SOCK_RDM)
返回值
如果不出错,socket函数将返回socket的描述符(句柄),否则,将返回INVALID_SOCKET。
SOCKET s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (s == INVALID_SOCKET) {
int er = WSAGetLastError();
return 0;
}
4:bind 服务端将socket与地址关联
bind函数将socket关联一个本地地址。
1 int bind(
2 __in SOCKET s,
3 __in const struct sockaddr* name,
4 __in int namelen
5 );
s 指定一个未绑定的socket。
name 指向sockaddr地址的指针,该结构含有IP和PORT
namelen 参数name的字节数。
无错误返回0,又错误返回SOCKET_ERROR。
1 sockaddr_in service;
2 service.sin_family = AF_INET;
3 service.sin_addr.s_addr = inet_addr("127.0.0.1");
4 service.sin_port = htons(27015);
5
6 //----------------------
7 // Bind the socket.
8 if (bind( ListenSocket, (SOCKADDR*) &service,sizeof(service)) == SOCKET_ERROR) {
9 closesocket(ListenSocket);
10 return;
11 }
htons(host to unsigned short)和htonl(host to unsigned long)
各个机器cpu对数据存储和表示的方法不通,intel机器用littele-endian存数据,而IBM机器用big-endian存数据。网络协议为取消这种差异,一致采用big-endian方式。htons用于将unsigned short的数值从littele-endian转换为big-endian,由于short只有2字节,常用语port数值的转换。htons用于将unsigned long的数值从littele-endian转换为big-endian,long有4字节,常用于ipv4地址的转换。
1 u_short htons( __in u_short hostshort);
2 u_long htonl(__in u_long hostlong);
inet_addr和inet_ntoa
inet_addr用于将ipv4格式的字符串转换为unsigned long的数值。inet_ntoa用于将struct in_addr的地址转换为ipv4格式的字符串。
1 unsigned long inet_addr( __in const char* cp);
2 char* FAR inet_ntoa( __in struct in_addr in);
INADDR_ANY
用INADDR_ANY来配置IP地址,意味着不需要知道当前服务器的IP地址。对于多网卡的服务器,INADDR_ANY允许你的服务接收一个服务器上所有网卡发来的数据。下面例子也有写,如果某个socket使用INADDR_ANY和8000端口,那么它将接收该所有网卡传来的数据。而其他socket将无法再使用8000端口。
1 sockaddr_in service;
2 ZeroMemory((char *)&service,sizeof(sockaddr_in));
3 service.sin_family = AF_INET;
4 //service.sin_addr.S_un.S_addr =/*INADDR_ANY*/ inet_addr("127.0.0.1");
5 service.sin_addr.s_addr = INADDR_ANY;
6 service.sin_port = htons(8278);
7 if (bind(s,(sockaddr*)&service,sizeof(service)) == SOCKET_ERROR)
8 {
9 int er = WSAGetLastError();
10 closesocket(s);
11 }
4:listen 服务端网络监听
int listen(
__in SOCKET s,
__in int backlog
);
s socket描述符,该socket是一个未连接状态的socket
backlog 挂起连接的最大长度,如果该值设置为SOMAXCONN,负责socket的底部服务提供商将设置该值为最大合理值。并没有该值的明确规定。
返回值 没有错误发生将返回0,否则返回SOCKET_ERROR
5:accept 服务端connect接收
s listen函数用到的socket。accept函数将创建连接。
addr 指向通信层连接实体地址的指针。addr 的格式取决于bind函数内地址簇的类型。
addrlen addr的长度。
返回值 如果不发生错误,accept将返回一个新的SOCKET描述符,即新建连接的socket句柄。否则,将返回INVALID_SOCKET。传进去的addrlen应该是参数addr的长度,返回的addrlen是实际长度。
1 SOCKET ac = accept(s,NULL,NULL);
2 if (ac == INVALID_SOCKET)
3 {
4 int er = WSAGetLastError();
5 closesocket(s);
6 }
6:connect 客户端请求服务端连接
1 int connect(
2 __in SOCKET s,
3 __in const struct sockaddr* name,
4 __in int namelen
5 );
s 当前未处于连接状态的socket
name 与bind函数的name参数类似,指明待连接的地址
namelen name长度
返回值 0表示正确,否则,将返回SOCKET_ERROR。如果是阻塞式的socket连接,返回值代表了连接正常与失败
1 sockaddr_in server;
2 server.sin_family = AF_INET;
3 server.sin_port = htons(8828);
4 server.sin_addr.s_addr = inet_addr("127.0.0.1");
5 if (connect(cnetsocket,(sockaddr*)&server,sizeof(server)) == SOCKET_ERROR)
6 {
7 break;
8 }
7:send、recv 发送接收数据
1 int send(
2 __in SOCKET s,
3 __in const char* buf,
4 __in int len,
5 __in int flags
6 );
1 int recv(
2 __in SOCKET s,
3 __out char* buf,
4 __in int len,
5 __in int flags
6 );
s 一个已经连接的socket描述符
buf 数据buffer
len 待发送(接受)数据长度
flags send(recv)函数的发送(接收)数据方式。
send:
1:MSG_DONTROUTE(告诉内核,目标主机在本地网络,不用查路由表)
2:MSG_OOB(指明发送的是带外信息)
3: MSG_DONTWAIT(将单个I/O操作设置为非阻塞模式)
4:0:与write无异
recv:
1:0:常规操作,与read()相同
2:MSG_OOB:指明发送的是带外信息
3:MSG_PEEK:可以查看可读的信息,在接收数据后不会将这些数据丢失
4:MSG_WAITALL:通知内核直到读到请求的数据字节数时,才返回。
返回值:
send的返回值标识已发送数据的长度,这个值可能比参数len小,这也意味着数据缓冲区没有全部发出去,要进行后续处理。返回SOCKET_ERROR标识send出错。
recv的返回值标识已接收数据的长度。如果连接已关闭,返回值将是0。返回SOCKET_ERROR标识recv出错。
//客户端例子
1 sockaddr_in server;
2 server.sin_family = AF_INET;
3 server.sin_port = htons(8828);
4 server.sin_addr.s_addr = inet_addr("127.0.0.1");
5 if (connect(cnetsocket,(sockaddr*)&server,sizeof(server)) == SOCKET_ERROR)
6 {
7 break;
8 }
9 str += "windows socket test!";
10 while (1)
11 {
12 int len = send(cnetsocket,str.c_str(),str.length(),0);
13 cout<<"send data:"<<str.c_str()<<" ,length = "<<str.length()<<endl;
14 if (len < str.length())
15 {
16 cout<<"data send uncompleted"<<endl;
17 str = str.substr(len+1,str.length());
18 len = send(cnetsocket,str.c_str(),str.length(),0);
19 cout<<"send data uncomplete,send remaining data :"<<str.c_str()<<" ,length = "<<str.length()<<endl;
20 }
21 else if (len == SOCKET_ERROR)
22 {
23 break;
24 }
25 Sleep(1);
26 }
//服务端例子
1 SOCKET acp = accept(listensocket,NULL,NULL);
2 if (acp == INVALID_SOCKET)
3 {
4 cout<<"accept error,error code "<<WSAGetLastError()<<endl;
5 break;
6 }
7 while (1)
8 {
9 char buf[1024];
10 int len = recv(acp,buf,1024,0);
11 if (len == 0)
12 {
13 cout<<"connection has been closed "<<endl;
14 break;
15 }
16 else if (len == SOCKET_ERROR)
17 {
18 cout<<"recv error,error code "<<WSAGetLastError()<<endl;
19 break;
20 }
21 else
22 {
23 char* outbuf = new char[len+1];
24 memcpy(outbuf,buf,len);
25 outbuf[len] = 0;
26 cout<<"recv data,"<<outbuf<<endl;
27 delete outbuf;
28 }
29 }
8:closesocket 关闭socket
1 int closesocket(
2 __in SOCKET s
3 );
s 需要关闭的socket描述符
返回值 如果无错误发生,函数返回0。否则,返回SOCKET_ERROR。
9:shutdown 禁止在socket收发数据
1 int shutdown(
2 __in SOCKET s,
3 __in int how
4 );
s 需要操作的socket描述符
how 1:SD_RECEIVE,不允许在socket上recv数据。
2:SD_SEND,不允许在socket上send数据。
3:SD_BOTH,不允许在socket上recv和send数据。
返回值
无错误返回0,否则返回SOCKET_ERROR,错误码可被WSAGetLastError调用。
shutdown并不关闭socket,只是禁止掉socket的recv或send行为。为保证数据收发完整性,在关闭socket之前应调用shutdown。
10 :getsockname 获取本地IP和PORT
1 int getsockname(
2 __in SOCKET s,
3 __out struct sockaddr* name,
4 __in_out int* namelen
5 );
s socket文件描述符
name 指向结构体sockaddr的指针,接收socket的地址
namelen name字节数
返回值 无错误返回0,否则返回SOCKET_ERROR,错误码可被WSAGetLastError调用。
11 :getpeername 获取对端IP和PORT
1 int getpeername(
2 __in SOCKET s,
3 __out struct sockaddr* name,
4 __in_out int* namelen
5 );
参考getsockname
12:ioctlsocket 控制套接口的模式。可用于任一状态的任一套接口
int ioctlsocket( int s, long cmd, u_long * argp);
s 一个标识套接口的描述符。
cmd: 对套接口s的操作命令。
argp: 指向cmd命令所带参数的指针。
返回值 成功后,ioctlsocket()返回0。否则的话,返回-1错误,应用程序可通过WSAGetLastError()获取相应错误代码。
cmd:FIONBIO:允许或禁止套接口s的非阻塞模式。
FIONREAD:确定套接口s自动读入的数据量。
SIOCATMARK:确实是否所有的带外数据都已被读入。