在C/S(客户端/服务端)模式中,包含基于连接和基于非连接两种通信模式。基于连接的通信是可靠通信,其依赖的协议是TCP/IP协议。
基于TCP/IP协议的服务端工作流程包括创建套接字、绑定套接字、接听套接字和接收客户端的连接等步骤。
1 创建套接字
创建套接字的方法请参考《Winsock网络编程创建套接字》。
2 绑定套接字
使用“1创建套接字”中介绍的方法创建了指定协议的套接字之后,此时的套接字在指定的地址族中。但是,并没有为创建的套接字分配“名字”。而接下来的绑定套接字,就是将“名字”分配给未命名的套接字。
2.1 bind()函数
通过bind()函数实现套接字与地址的绑定。该函数的格式是
int bind( SOCKET s, const sockaddr *addr, int namelen );
其中,参数s是要绑定的套接字;addr是sockaddr结构的指针,该指针指向的内容就是要为套接字分配的“名字”,包含三个部分:分别是地址族、主机IP地址和用于端口号,不同的应用程序使用不同的端口号;namelen是addr指向内容的大小。如果函数调用成功,则bind()函数返回值是0,否则返回SOCKET_ERROR值。
2.2 sockaddr结构
sockaddr结构中包含了绑定到套接字上的“名字”。其格式为
struct sockaddr {
ushort sa_family;
char sa_data[14];
};
其中,sa_family是套接字使用的地址族;sa_data是一个字符数组,包含了IP地址和端口等信息。
除了sockaddr结构,还有sockaddr_in结构也包含了绑定到套接字上的“名字”。该结构的格式为
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中,sa_family是套接字使用的地址族;sin_port是端口号;sin_addr是in_addr结构的变量,包含了IP地址;sin_zero是一个长度为8个字节的字符数组,该数组的作用是使得sockaddr_in结构与sockaddr结构的大小相同。需要注意的是,在sockaddr_in结构与sockaddr结构中,除了sin_family成员变量外,其它的成员变量都是按网络字节序进行存储的。in_addr结构和网络字节序的相关知识请参考《主机字节序与网络字节序》。
使用bind()函数将“名字”与套接字绑定的代码如下所示。
SOCKET s;
SOCKADDR_IN tcpaddr;
int port = 5150;
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
tcpaddr.sin_family = AF_INET;
tcpaddr.sin_port = htons(port);
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));
其中,使用socket()套接字创建了一个使用IP协议的流套接字,之后将“名字”即SOCKADDR_IN结构的对象tcpaddr的各成员变量,使用htons()和htonl()函数将主机字节序转换为网络字节序,INADDR_ANY表示来自任意客户端的连结都能够被服务端接收,最后使用bind()函数对套接字进行绑定。