Windows socket是一套在Windows操作系统下的网络编程接口。它不是一种网络协议,而是一个开放的、支持多个协议的Windows下的网络编程接口
Windows socket是以Unix socket为基础,因此Windows socket中的许多函数名与Unix都是一样的。除此之外它还允许开发人员充分利用Windows的消息驱动机制进行程序设计开发。
套接字是应用层到运输层的接口。套接字用以表示一条连接的两端。每一个端点由ip和端口组成。因此套接字是由两端点的ip和端口组成。
端口是运输层的概念,每个端口对应一个进程。因此一条连接表示一个进程与另一个进程建立联系。
应用程序可以使用两种套接字。流套接字和数据包套接字。分别对应TCP和UDP。
TCP提供面向连接的可靠的、无重复、有序的数据流服务。而UDP提供面向数据包的,不保证数据是可靠的、有序的和无重复的。
在Windows环境下,使用Windows socket api进行网络程序开发时,需要调用Windows操作系统的Windows socket动态库。在应用程序中需要包含Windows sockets头文件。windows sockets 2.2版本需要包含WINSOCK2.h头文件(不区分大小写)。同时还需要添加动态库。一种是在头文件中添加。如:
#pragma comment(lib,"WS2_32.lib")
另一种是在vc中添加。可以选择project -》settting,在link标签下添加wsock32.lib字符串。
Windows sockets中定义的套接字类型SOCKET来表示套接字:
typedef unsigned int u_int;
typedef u_int SOCKET;
实所谓的SOCKET的类型只不过是unsigned int的别名罢了。INVALID_SOCKET表示一个无效的套接字,除此之外的0–INVALID_SOCKET-1都表示一个有效的套接字。因此在创建套接字后,都需要与INVALID_SOCKET比较,看创建的套接字是否有效。
SOCKET s=socket(...);
if(INVALID_SOCKET==s)
{
//创建失败。
}
Windows SOCKET可以支持多种不同的网络协议,并且提供与协议无关的编程接口。因此开发人员就可以相同的格式开发使用任一协议的网络应用程序,而不去关心各种协议的不同。
每种协议都有一套不同的IP定址方案(即表示主机地址的方式)。TCP协议和UDP协议通过IP协议传输数据。而Windows SOCKET通过AF_INET地址家族为IP协议定址。
#define AF_INET 2
网络中每台主机都有一个IP地址,用32位数字来表示。TCP和UDP必须指定端口号。在Windows SOCKET中sockaddr_in 结构被用来指定IP和端口号。
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
sin_family表示地址家族。使用TCP/IP协议的应用程序必须为aF_INET,来告诉系统使用IP地址家族 。
sin_port指定服务的端口号。1024–49151范围内的数据被作为服务端口号,可以由用户自定义。 sin_zero字段作为填充字段。以便使得该结构与SOCKADDR结构长度相同。
in_addr的定义如下:
struct in_addr {
union {
struct{
u_char s_b1,s_b2,s_b3,s_b4;
}S_un_b;
struct {
u_short s_w1,s_w2;
} S_un_w;
u_long S_addr;
} S_un;
}
很显然它是一个存储ip地址的联合体,有三种表达方式:
第一种用四个字节来表示IP地址的四个数字;
第二种用两个双字节来表示IP地址;
第三种用一个长整型来表示IP地址。
给in_addr赋值的一种最简单方法是使用inet_addr函数,它可以把一个代表IP地址的字符串赋值转换为in_addr类型,如addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
其反函数是inet_ntoa,可以把一个in_addr类型转换为一个字符串。
sockaddr类型sockaddr类型是用来表示socket地址的类型,同上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。
sockaddr的定义如下:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
可知sockaddr有16个字节,而sockaddr_in也有16个字节,所以sockaddr_in是可以强制类型转换为sockaddr的。事实上也往往使用这种方法。
不同cpu处理多字节时处理方式不同。Intel x86cpu对多字节的处理方式为高对高低对低。但是在网络上采用的是高对低,低对高的方式。因此也就存在所谓主机字节序和网络字节序的处理问题。
Htonl和htons函数实现主机字节顺序和网络字节序的转换功能。H代表host,主机。N代表net。L代表long。S代表short。不要使用htonl转换short哦,楼主就有一次犯了这个错误,找了好久才找到原因。
当然也有从网络字节序到主机字节序的转换函数:ntohl和ntohs。
除了支持TCP/IP协议之外,Windows SOCKET还支持IPX/SPX、ATM和红外线通信协议等等,它们都有自己的定址方法,感兴趣的同学可以参考其他资料。
基本的套接字编程。
在使用套接字进行编程之前,无论是服务器还是客户端都必须加载Windows SOCKET动态库。函数WSAStartup就实现了此功能。它是套接字应用程序必须调用的第一个函数。
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpwsadata);
第一个参数指定准备加载的Windows SOCKET动态库的版本。一般使用宏MAKEWORD构造。如MAKEWORD(2,2)表示加载2.2版本。
WSADATA会返回被加载动态链接库的基本信息。如是否加载成功,以及当前实际使用的版本。具体结构不再介绍。
初始化socket之后,需要创建套接字。socket函数和WSASocket函数可以实现此功能。
SOCKET socket(
int af,
int type,
int protocao);
af表示使用协议的地址家族。创建TCP或UDP的套接字是使用AF_INET。
type表示套接字的类型。有SOCK_STREAM、SOCK_DGRAM和SOCK_RAM三种类型。分别表示流、数据包、原始套接字。
protocol,指示使用的协议。对于SOCK_STREAM套接字类型,该字段可以为IPPROTO_TCP或0。对于SOCK_DGRAM套接字类型,该字段为IPPROTO_UDP或0。
当函数创建成功时,返回一个新建的套接字句柄。否则将返回INVALID_SOCKET。如
SOCKET s=socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET)
{
//失败。
}
bind函数
在创建套接字之后就需要调用bind函数将其绑定到一个已知的地址上。
int bind(SOCKET s,
const struct sockaddr*name,
int namlen);
s为要绑定的套接字,name为要绑定的地址。namelen为sockaddr长度。
函数调用成功将返回0,否则返回值为SOCKET_ERROR。如果程序不关心分配给它的地址,可使用INADDR_ANY或将端口号设为0。端口号为0时,Windows SOCKET将给应用程序分配一个值在1024-5000之间唯一的端口号。
示例:
struct sockaddr_in addr;
int nServerPort=5500;
int nErrorCode;
addr.family=AF_INET;
addr.port=htons(nServerPort);
addr.sin_addr.S_addr=htonl(INADDR_ANY);
SOCKET s=socket(AF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET)
{
//失败。
}
nErrorCode=bind(s,(SOCKADDR*)&addr,sizeof(addr));
if(nErrorCode==SOCKET_ERROR)
{
//失败。
}
listen函数将套接字设定为监听模式。
int listen (
SOCKET s,
int backlog);
s为要设置监听模式的套接字。
backlog指定等待连接的最大队列长度。
当函数成功时返回0,否则返回SOCKET_ERROR。
假如backlog为3,则说明最大等待连接的最大值为3.如果有四个客户端同时向服务器发起请求,那么第四个连接将会发生WSAEWOULDBLOCK错误。当服务器接受了一个请求,就将该请求从请求队列中删去。
int ret=listen(s,3);
if(ret==SOCKET_ERROR)
{
//失败。
}
accept函数接受客户端的一个连接请求。
SOCKET accept{
SOCKET s,
struct sockaddr*addr,
int *addrlen);
s为监听套接字。
addr返回客户端地址。
addrlen返回addr的长度。
函数执行成功时:
1:主机接受了等待队列的第一个请求。
2:addr结构返回客户端地址。
3:返回一个新的套接字句柄。服务器可以使用该套接字与客户端进行通信。而监听套接字仍用于接受客户端连接。
SOCKET sListen;//监听套接字。
SOCKET sAccept;//接受套接字。
sockaddr_in addrClient;//客户端地址。
int addrClientLen=sizeof(addrClient);
sAccept=accept(sListen,(SOCKADDR*)&addrClient,&addrClientLent);
if(INVALID_SOCKET==sAccept)
{
//失败。
}
recv()函数
recv()和WSARecv()函数用于接收数据。
int recv(
SOCKET s,
char *buf,
int len,
int flags);
s为接收数据套接字。
buff接受缓冲区。
len缓冲区长度。
flags:该参数影响函数的行为。它可以是0,MEG_PEEK和MSG_OOB。0表示无特殊行为。MSG_PEEK会使有用的数据被复制到buff中,但没有从系统缓冲区内将这些数据删除。MSG_OOB表示处理外带数据。
recv函数返回接收的字节数。当函数执行失败时返回SOCKET_ERROR。
SOCKET s;
char buff[128];
nReadlen=recv(s,buff,128,0);
if(SOCKET_ERROR==nReadlen)
{
}
send函数
send()和WSASend()用于发送数据。
int send(
SOCKET s,
const char *buff,
int len,
int flags);
s为发送套接字。
buff为发送缓冲区。
len发送数据长度。
flags可以是0或MSG_DONTROUTE或MSG_OOB。0表示无特殊行为。MSG_DONTROUTEyaoqiu传输层不要将此数据路由出去。MSG_OOB表示该数据应该被外带发送。
函数成功时返回实际发送的字节数。失败返回SOCKET_ERROR。
SOCKET s;
char buff[128];
int ret;
strcpy(buff,”sendData”);
ret=send(s,strlen(buff),0);
if(SOCKET_ERROR==ret)
{
}
closesocket()函数
closesocket()函数用以关闭套接字。释放套接字所占资源。
int closesocket(
SOCKET s);
调用过closesocket函数的套接字继续使用时会返回WSAENOTSOCK错误。
shutdown()函数。
Shutdown()函数用于通知对方不再发送数据或者不再接收数据或者既不发送也不接收数据。
int shutdown(
SOCKET s,
int how);
how参数可以是:
SD_RECEIVE表示不再接收数据,不允许再调用接收数据函数。
SD_SEND表示不再发送数据。
SD_BOTH表示既不发送也不接收数据。
connect函数实现连接服务器的功能
int connect(
SOCKET s,
const struct sockaddr*name,
int namelen);
s为套接字。
name为服务器地址。
namelen为sockaddr结构长度。
函数执行成功返回0,否则返回SOCKET_ERROR。