1、Windows Sockets已经封装好了具体的实现方法,在这里不深究它的机制,只是能理解和熟练使用就可以。
AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B127.0.0.1
,它是一个特殊IP地址,表示本机地址
服务器:
建立WSADATA(保存接收的数据) -> 调用WSAStartup函数 -> 创建socket -> 填写IP地址和端口 -> bind()绑定IP地址和端口(判断绑定成功) -> 监听scoket -> 发送和接收数据 -> 断开连接 -> 关闭
使用DLL之前必须把DLL加载到当前程序,你可以在编译时加载,也可以在程序运行时加载
方法1:
#pragma comment (lib, "ws2_32.lib")
方法2:
WORD wVersionRequested;
wVersionRequested = MAKEWORD( 2, 2 ); // 请求2.2版本的WinSock库
WSAStartup是任何使用Winsock的应用程序或者DLL首先必须调用Winsock库函数.一方面它初始化 ws2_32.dll,另一方面他用于在应该程序DLL与系统Winsock库版本协商.
4.1、socket()函数 建立socket
int socket(int af, int type, int protocol);
af:协议域或协议簇。常用AF_INET、AF_INET6,决定socket的地址类型。
type:指socket类型,SOCK_STREAM、SOCK_DGRAM,即TCP、UDP。
protocol:指定协议,常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,但是大部分设为0即可,会自动匹配。
return:返回一个文件的描述符,为int型,成功返回生成的SOCKET,失败返回INVALID_SOCKET
4.2、 bind() 绑定socket
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
sock:是socket的文件描述符,addr是sockaddr结构体变量的指针,addlen是addr变量的大小,可由sizeof()计算
4.3、 connect() 连接socket
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
参数跟bind()一致
4.4、listen() 监听socket
int listen(SOCKET sock, int backlog);
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度
缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。
如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。
注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。
4.4、 accept() 接收socket
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
4.5、send()和recv()
从服务器端发送数据使用 send() 函数,它的原型为:
int send(SOCKET sock, const char *buf, int len, int flags);sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项。
返回值和前三个参数不再赘述,最后的 flags 参数一般设置为 0 或 NULL,初学者不必深究。
在客户端接收数据使用 recv() 函数,它的原型为:
int recv(SOCKET sock, char *buf, int len, int flags);
4.6、返回值
socket() accept()
如果成功就返回生成的SOCKET,如果失败就返回INVALID_SOCKET.
#define INVALID_SOCKET (SOCKET)(~0)
实际上是 0xFFFFFFFF 4bytes
bind() listen() connect()
如果成功就返回0,如果失败就返回SOCKET_ERROR,需要通过WSAGetLastError获得进一步的错误信息.
#define SOCKET_ERROR (-1)
实际上是 0xFFFFFFFF 4bytes
send() sendto()
如果成功就返回发送的字节数,如果失败就返回SOCKET_ERROR,需要通过WSAGetLastError获得进一步的错误信息.
recv() recvfrom()
如果成功就返回收到的字节数,如果如果失败就返回SOCKET_ERROR,需要通过WSAGetLastError获得进一步的错误信息.
如果连接被温和的关闭,返回0,但是recvfrom通常是用于无连接的UDP socket.