写在前面
此前的closesocket函数,调用后就会完全断开连接。完全断开后,不仅无法传输数据,也无法再接收数据。在某些场景下,完全断开连接可能会有些问题。
若有两台主机正在进行双向通信。主机A发送完最后的数据后,调用closesocket函数断开连接(这里会将该套接字的输出缓存的数据全部发送出去),之后主机A无法再接收主机B正在传输的数据(实际上是完全无法调用与接收数据相关的函数)。
为了解决这类问题,“只关闭一部分数据交换中使用的流(Half-close)”的方法应运而生。断开一部分连接是指,可以传输数据但无法接收,或者可以接收数据但无法传输,及关闭IO流的一半。
shutdown函数
一旦两台主机间建立了套接字连接,每个主机(套接字)都会拥有单独的输入流和输出流,即其中一个主机的输入流和另一个主机的输入流相连,而输出流则和另一主机的输入流相连,以此进行全双工通信。使用shutdown函数可以只断开指定流,而不是全关闭。
函数原型如下:
#include <winsock2.h>
int shutdown(SOCKET sock, int howto);
//返回值:成功返回0,失败返回SOCKET_ERROR
//sock: 要断开的套接字句柄
//howto:断开方式信息
//howto参数可选项:
//SD_RECEIVE: 断开输入流
//SD_SEND: 断开输出流
//SD_BOTH: 同时断开输入输出流
下面给出简单的半关闭服务器/客户端示例。
半关闭服务器
// HalfClose_Server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
/*
Windows半关闭接口
int shutdow(SOCKET sock, int howto);
sock: 要半关闭的套接字句柄
howto: 断开方式的信息.可选:SD_RECEIVE -- 断开输入流,SD_SEND -- 断开输出流,SD_BOTH -- 同时断开IO流
返回值:成功时返回0,失败时返回SOCKET_ERROR
*/
int _tmain(int argc, _TCHAR* argv[])
{
if (argc != 2)
{
return -1;
}
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup Error!");
return -1;
}
SOCKET srvSock = socket(PF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == srvSock)
{
printf("socket error!");
WSACleanup();
return -1;
}
SOCKADDR_IN srvAddr;
memset(&srvAddr, 0, sizeof(srvAddr));
srvAddr.sin_family = PF_INET;
srvAddr.sin_addr.s_addr = htonl(ADDR_ANY);
srvAddr.sin_port = htons(_ttoi(argv[1]));
if (SOCKET_ERROR == bind(srvSock, (sockaddr*)&srvAddr, sizeof(srvAddr)))
{
printf("bind error!");
closesocket(srvSock);
WSACleanup();
return -1;
}
if (SOCKET_ERROR == listen(srvSock, 5))
{
printf("listen error!");
closesocket(srvSock);
WSACleanup();
return -1;
}
SOCKADDR_IN cltAddr;
memset(&cltAddr, 0, sizeof(cltAddr));
int nCltAddrSize = sizeof(cltAddr);
SOCKET cltSock = accept(srvSock, (sockaddr*)&cltAddr, &nCltAddrSize);
if (INVALID_SOCKET == cltSock)
{
printf("accept error!");
closesocket(srvSock);
WSACleanup();
return -1;
}
int nRecvLen = 0;
char Msg[BUF_SIZE] = "0123456789";
while(true)
{
nRecvLen = strlen(Msg);
printf("接收长度: %d\n", nRecvLen);
if (nRecvLen < BUF_SIZE)
{
printf("break\n");
send(cltSock, Msg, nRecvLen, 0);
break;
}
send(cltSock, Msg, BUF_SIZE, 0);
}
//半关闭:输出流
shutdown(cltSock, SD_SEND);
nRecvLen = recv(cltSock, Msg, BUF_SIZE, 0);
printf("Msg From Client: %s\n", Msg);
closesocket(srvSock);
WSACleanup();
fputs("任意键结束...", stdout);
getchar();
return 0;
}
半关闭客户端
// HalfClose_Client.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
int _tmain(int argc, _TCHAR* argv[])
{
if(argc != 3)
{
printf("argc error!\n");
return -1;
}
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup error!\n");
return -1;
}
SOCKET srvSock = socket(PF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == srvSock)
{
printf("socket error!\n");
WSACleanup();
return -1;
}
SOCKADDR_IN srvAddr;
memset(&srvAddr, 0, sizeof(srvAddr));
srvAddr.sin_family = PF_INET;
srvAddr.sin_addr.s_addr = inet_addr(argv[1]);
srvAddr.sin_port = htons(_ttoi(argv[2]));
if (SOCKET_ERROR == connect(srvSock, (sockaddr*)&srvAddr, sizeof(srvAddr)))
{
printf("connect error!\n");
closesocket(srvSock);
WSACleanup();
return -1;
}
int nRecvLen = 0;
char Msg[BUF_SIZE];
while ( (nRecvLen = recv(srvSock, Msg, BUF_SIZE, 0)) != 0 )
{
Msg[nRecvLen] = 0;
printf("Recv From Server: %s\n", Msg);
}
//服务器器半关闭后
send(srvSock, "thank you", 10, 0);
closesocket(srvSock);
WSACleanup();
fputs("任意键结束...", stdout);
getchar();
return 0;
}
运行结果如图:
可以看到服务器半关闭输出流后,还能接收到客户端(输入流)的消息。