1. 套接字的创建和关闭
使用套接字之前,必须调用socket函数创建一个套接字对象,此函数调用成功将返回套接字句柄。
SOCKET socket(
int af, // 用来指定套接示使用的地址格式,WinSock 中只支持AF_INET
int type, // 用来指定套接字的类型
int protocol // 配合type 参数使用,用来指定使用的协议类型。可以是IPPROTO_TCP 等
);
type 参数用来指定套接字的类型。套接字有流套接字、数据报套接字和原始套接字等,下面是常见的几种套接字类型定义。
SOCK_STREAM 流套接字,使用TCP 提供有连接的可靠的传输
SOCK_DGRAM 数据报套接字,使用UDP 提供无连接的不可靠的传输
SOCK_RAW 原始套接字,Winsock 接口并不使用某种特定的协议去封装它,而是由程序自行处理数据报以及协议首部
当 type 参数指定为SOCK_STREAM 和 SOCK_DGRAM 时,系统已经明确使用TCP 和UDP 来工作,所以protocol 参数可以指定为0。函数执行失败返回
INVALID_SOCKET(即-1),可以通过调用WSAGetLastError 取得错误代码。也可以使用 Winsock2 的新函数WSASocket 来创建套接字,与socket 相比,它提供了更多的参
数,如可以自己选择下层服务提供者、设置重叠标志等,后面再具体讨论它。当不使用 socket 创建的套接字时,应该调用closesocket 函数将它关闭。如果没有错误发生,函
数返回0,否则返回SOCKET_ERROR。函数用法如下。
int closesocket(SOCKET s); // 函数惟一的参数就是要关闭的套接字的句柄
2. 绑定套接字到指定的IP地址和端口号
为套接字关联本地地址的函数是bind,说明如下
int bind(
SOCKET s, // 套接字句柄
const struct sockaddr* name, // 要关联的本地地址
int namelen // 地址的长度
);
bind 函数用在没有建立连接的套接字上,它的作用是绑定面向连接的或者无连接的套接字。套接字被socket 函数创建以后,存在于指定的地址家族里,但它是未命名的。bind
函数通过安排一个本地名称到未命名的socket 而建立此socket 的本地关联。本地名称包含3 部分:主机地址、协议号(分别为UDP 或TCP)和端口号。
使用方法如下:
// 填充sockaddr_in 结构
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定这个套接字到一个本地地址
if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("Failed bind() \n");
return 0;
}
sockaddr_in 结构中的sin_familly 字段用来指定地址家族,该字段和socket 函数中的af 参数的含义相同,所以惟一可以使用的值就是AF_INET。sin_port 字段和sin_addr 字段
分别指定套接字需要绑定的端口号和IP 地址。放入这两个字段的数据的字节顺序必须是网络字节顺序。因为网络字节顺序和Intel CPU 的字节顺序刚好相反,所以必须首先使用htons 函数进行转换。
如果应用程序不关心所使用的地址,可以指定Internet 地址为INADDR_ANY,指定端口号为0。如果Internet 地址等于INADDR_ANY,系统会自动使用当前主机配置的所有IP 地
址,简化了程序设计;如果端口号等于0,程序执行时系统会为这个应用程序分配惟一的端口号,其值在1024~5000 之间。应用程序可以在bind 之后使用getsockname 来知
道为它分配的地址。但是要注意,直到套接字连接上之后getsockname 才可能填写Internet 地址,因为对一个主机来说可能有多个地址是可用的。
TCP 客户端程序也可以在不显式绑定地址和端口号的情况下发送数据或者连接。在这种情况下,系统也会默认地为套接字绑定一个本地端口(1024~5000 之间)。
3. 设置套接字进入监听
listen函数设置套接字进入监听状态
int listen(
SOCKET s, // 套接字句柄
int backlog // 监听队列中允许保持的尚未处理的最大连接数量
);
为了接受连接,首先使用socket 函数创建套接字,然后使用bind 函数将它绑定到本地地址,再用listen 函数为到达的连接指定backlog,最后使用accept 接受请求的连接。
listen 仅应用在支持连接的套接字上,如SOCK_STREAM 类型的套接字。函数执行成功后,套接字s 进入了被动模式,到来的连接会被通知要排队等候接受处理。
在同一时间处理多个连接请求的服务器通常使用 listen 函数,如果一个连接请求到达,并且排队已满,客户端将接收到WSAECONNREFUSED 错误。
4. 接收连接请求
accept函数用于接受到来的连接
SOCKET accept(
SOCKET s, // 套接字句柄
struct sockaddr* addr, // 一个指向sockaddr_in 结构的指针,用于取得对方的地址信息
int* addrlen // 一个指向地址长度的指针
);
该函数在s 上取出未处理连接中的第一个连接,然后为这个连接创建新的套接字,返回它的句柄。新创建的套接字是处理实际连接的套接字,它与s 有相同的属性。
程序默认工作在阻塞模式下,这种方式下如果没有未处理的连接存在,accept 函数会一直等待下去,直到有新的连接发生才返回。
addrlen 参数用于指定addr 所指空间的大小,也用于返回地址的实际长度。如果addr 或者addrlen 是NULL,则没有关于远程地址的信息返回。
客户端程序在创建套接字之后,要使用connect 函数请求与服务器连接,函数原型如下。
int connect(
SOCKET s, // 套接字句柄
const struct sockaddr FAR * name, // 一个指向 sockaddr_in 结构的指针,包含了要连接的服务器的地址信息。
int namelen // sockaddr_in 结构的长度
);
第一个参数s 是此连接使用的客户端套接字,另两个参数name 和namelen 用来寻址远程套接字(正在监听的服务器套接字)。
5. 收发数据
对于流套接字来说,一般使用send和recv函数来收发数据。
int send(
SOCKET s, // 套接字句柄
const char FAR * buf, // 要发送数据的缓冲区地址
int len, // 缓冲区长度
int flags // 指定了调用方式,通常设位0
);
int recv( SOCKET s, char FAR * buf, int len, int );