通讯分为面向连接通讯(Connection-Oriented Communication 如TCP)
和非连接通讯(Connectionless Communication 如UDP)。笔记(3)至笔记(6)将介绍前者。
本章介绍如何编写一个 Winsock TCP/IP服务端来接收客户连接请求.
SOCKET 是Winsock中独立的一个类型
它的定义如下:
typedef UINT_PTR SOCKET;
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;
用来表示一个连接的句柄.
在Winsock中,有两种基本通讯方式:面向连接传输模式、无连接传输模式.
IP协议族中,TCP/IP协议做到了面向连接通讯,TCP提供零错误数据传输机制,在发送方和接收方建立一条虚拟的连接.
服务端是一个进程,它等待若干客户连接并相应它们的请求.
服务端在一个周知地址上监听连接,在TCP/IP中,周知端口是本地接口的IP地址和端口号.
不同的协议有不同的地址组成方式因此有不同的地址命名方法.
建立一个服务端的步骤
1.socket()/WSASocket(); //创建一个socket.
2.bind(); //绑定一个周知地址.
3.listen(); //设置监听模式.
4.accept()/WSAAccept(); //接收连接.
1.socket()/WSASocket(); //创建一个socket.
有两个创建Socket的方法:socket()、WSASocket().
SOCKET socket(
int af,
int type,
int protocol
);
int af:一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。
int type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
int protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
Winsock提供了四个很有用的函数来控制socket的选项和行为:setsockopt()、getsockopt()、ioctlsocket()、WSAIoctl().将在后面详细介绍.
2.bind(); //绑定一个周知地址.
int bind(
SOCKET s, //服务端socket
const struct sockaddr FAR * name, //SOCKADDR
int namelen //name长度
);
使用代码如下:
SOCKET s;
SOCKADDR_IN InternetAddr;
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //创建socket
INT nPortID=5150;
InternetAddr.sin_family=AF_INET;
InternetAddr.sin_addr.s_addr=htonl(INADDR_ANY); //INADDR_ANY参数表示该服务端接收任意地址往该端口发送的消息
InternetAddr.sin_port=htons(nPortID);
bind(s,(SOCKADDR*)&InternetAddr,sizeof(InternetAddr)); //显式转换
错误时返回SOCKET_ERROR.
常见错误码:
WSAEADDRINUSE:该IP端口已被占用或者处于TIME_WAIT状态.
WSAEFAULT:已经绑定过后再一次绑定.
3.listen(); //设置监听模式.
int listen(
SOCKET s,
int backlog //挂起连接队列最大长度
);
设置backlog参数后仍然需要由程序底层来进行适当替换,替换为一个适合的接近值,并且用户无法得知确切的值.一般设置为5.
当挂起队列满时,连接请求会失败,返回WSAECONNREFUSED.
常见错误码:
WSAEADDRINUSE:该IP端口已被占用或者处于TIME_WAIT状态.
WSAEFAULT:listen()之前会进行bind().
4.accept()/WSAAccept(); //接收连接.
其实还有AcceptEx()函数,是accept()的扩展,在后面会介绍.
SOCKET accept(
socket s, //监听状态绑定的socket
struct sockaddr FAR * addr, //有效的SOCKADDR_IN地址
int FAR * addrlen //SOCKADDR_IN的长度
);
该函数返回一个关联到请求连接的客户的socket描述符,然后使用这个socket来进行后续一系列操作.
服务端监听socket将持续存在来监听其他客户的请求.
发生错误时,返回INVALID_SOCKET错误.
常见错误码:
WSAEWOULDBLOCK:监听socket在异步或非阻塞模式并且无连接可以接受.
一个完整的 Winsock TCP/IP 服务端代码(为了简洁将错误处理部分删去了)如下:
int main(void)
{
WSADATA wsaData;
SOCKET ListeningSocket;
SOCKET NewConnection;
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int port=5150;
int ClientAddrLen;
WSAStartup(MAKEWORD(2,2),&wsaData); //初始化 Winsock 2.2 版本
ListeningSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //创建一个 socket 来监听客户连接
ServerAddr.sin_family=AF_INET; //填充 SOCKADDR_IN 数据结构
ServerAddr.sin_port=htons(port);
ServerAddr.sin_addr.s_addr=htonl(ADDR_ANY);
bind(ListeningSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr)); //绑定一个周知地址
listen(ListeningSocket,5); //设置监听模式
ClientAddrLesizeof(ClientAddr); //显示指定ClientAddrLen大小
NewConnection=accept(ListeningSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);
//接受一个到来的连接,注意!最后一个参数需要自己显式指定!
//这里你通过这些socket可以做两件事
//1.通过ListeningSocket再次调用accept()来接受其他连接
//2.通过NewConnection来发送/接受数据
//当你做完这两件事情时必须要关闭这些socket
//socket的关闭将在后面介绍
closesocket(ListeningSocket); //关闭ListeningSocket
closesocket(NewConnection); //关闭NewConnection
WSACleanup(); //关闭Winsock
return 0;
}
注意 accept()函数的最后一个参数需要显式指定大小!
ClientAddrLesizeof(ClientAddr); //显式指定ClientAddrLen大小
好了,到目前为止你已经可以编写一个 Winsock TCP/IP服务端来接收客户连接请求了.