服务端
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define SER_IP "127.0.0.1"
#define SER_PORT 26000
#define RECV_BUFFER_MAX_LEN 1024
#define SEND_BUFFER_MAX_LEN 1024
// Handle Client to Server Connect
DWORD WINAPI HandleC2SCnn(LPVOID lpParameter)
{
SOCKET CliSocket = (SOCKET)lpParameter;
if (CliSocket == INVALID_SOCKET) return -1;
sockaddr_in CliAddr;
memset(&CliAddr, 0, sizeof(CliAddr));
int len = sizeof(CliAddr);
if(getpeername(CliSocket, (struct sockaddr*)&CliAddr, &len) != 0) {
printf("Get IP address failed! Error %d", GetLastError());
closesocket(CliSocket);
return -1;
}
char RecvBuffer[RECV_BUFFER_MAX_LEN];
while (true) {
// waiting to receive data from client
memset(RecvBuffer, 0, RECV_BUFFER_MAX_LEN);
int Ret = recv(CliSocket, RecvBuffer, RECV_BUFFER_MAX_LEN, 0);
if (Ret == 0) {
printf("%s:%d Client have closed the connection!\n", inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port);
break;
} else if (Ret == SOCKET_ERROR) {
printf("Receive %s:%d Client data Failed! Error %d\n", inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port, GetLastError());
break;
}
printf("Receive %s:%d Client data : %s\n", inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port, RecvBuffer);
if (strcmp(RecvBuffer, "蓝鲸") == 0) {
char szWarning[] = "there is forbidden word!";
send(CliSocket, szWarning, strlen(szWarning), 0);
printf("Server closed the %s:%d Client connection because of forbidden word %s\n", inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port, RecvBuffer);
break;
}
// 把消息原样返回给客户端
if (send(CliSocket, RecvBuffer, strlen(RecvBuffer), 0) == SOCKET_ERROR) {
printf("Send %s to %s:%d Client Failed! Error %d\n", RecvBuffer, inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port, GetLastError());
break;
}
}
closesocket(CliSocket);
return 0;
}
// Create Server
bool CreateServer()
{
// Init Windows Socket
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) {
printf("Init Windows Socket Failed! Error %d\n", WSAGetLastError());
return false;
};
// Create Socket
SOCKET SerSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == SerSocket) {
printf("Create Socket Failed! Error %d\n", WSAGetLastError());
closesocket(SerSocket);
WSACleanup();
return false;
}
// Init SerAddr
struct sockaddr_in SerAddr;
memset(&SerAddr, 0, sizeof(SerAddr));
SerAddr.sin_family = AF_INET;
SerAddr.sin_addr.s_addr = inet_addr(SER_IP);
SerAddr.sin_port = htons(SER_PORT);
// Bind Socket
if (bind(SerSocket, (struct sockaddr*)&SerAddr, sizeof(SerAddr)) != 0) {
printf("Bind Socket Failed! Error %d\n", WSAGetLastError());
closesocket(SerSocket);
WSACleanup();
return false;
}
// listen
if (listen(SerSocket, 10) != 0) {
printf("Listen Socket Failed! Error %d\n", WSAGetLastError());
closesocket(SerSocket);
WSACleanup();
return false;
}
printf("Start Server Success!\n");
sockaddr_in CliAddr;
int len = sizeof(CliAddr);
while (true) {
memset(&CliAddr, 0, sizeof(CliAddr));
// waiting to client connect
SOCKET CliSocket = accept(SerSocket, (struct sockaddr*)&CliAddr, &len);
if (CliSocket == INVALID_SOCKET) {
printf("Accept Socket Failed! Error %d\n", WSAGetLastError());
continue;
}
printf("%s:%d Client Connect!\n", inet_ntoa(CliAddr.sin_addr), CliAddr.sin_port);
// Create thread to handle connect
HANDLE hThread = CreateThread(NULL, 0, HandleC2SCnn, (LPVOID)CliSocket, 0, NULL);
if (!hThread) {
printf("Create Thread Failed! Error %d\n", GetLastError());
continue;
}
CloseHandle(hThread);
}
// close socket
closesocket(SerSocket);
// clean socket
WSACleanup();
return true;
}
客户端
什么是“优雅”的关闭socket?
对于recv函数,如果没有错误发生,recv()返回收到的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
如果套接字是面向有连接的, 并且远程方已“优雅”地关闭了连接,所有数据也已经被接收,则recv()立即返回,接收0字节数据。如果连接被复位,recv()将失败,错误码为WSAECONNRESET。
客户端逻辑代码
// client connect socket
SOCKET CliCnnSocket;
bool bExit;
// Handle Server to Client Connect
DWORD WINAPI HandleS2CCnn(LPVOID lpParameter)
{
if (CliCnnSocket == INVALID_SOCKET) return -1;
char RecvBuffer[RECV_BUFFER_MAX_LEN];
while (true) {
if (bExit) break;
// waiting to receive data from server
memset(RecvBuffer, 0, RECV_BUFFER_MAX_LEN);
int Ret = recv(CliCnnSocket, RecvBuffer, RECV_BUFFER_MAX_LEN, 0);
if (Ret == 0) {
printf("Server have closed the connection!\n");
break;
} else if (Ret == SOCKET_ERROR) {
printf("Receive Server data Failed! Error %d\n", WSAGetLastError());
break;
}
printf("Receive Server data : %s\n", RecvBuffer);
}
closesocket(CliCnnSocket);
WSACleanup();
return 0;
}
// 客户端“优雅”的断开连接
void ClientDoExit()
{
bExit = true;
char szExit[] = "bye";
send(CliCnnSocket, szExit, strlen(szExit), 0);
}
BOOL HandleCloseConsole(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_CLOSE_EVENT) {
ClientDoExit();
// 延迟一下 让子线程HandleS2CCnn能够执行完closesocket
Sleep(100);
return TRUE;
}
return FALSE;
}
// Create Client
bool CreateClient()
{
// Init Windows Socket
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) {
printf("Init Windows Socket Failed! Error %d\n", WSAGetLastError());
return false;
};
// Create Socket
CliCnnSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (CliCnnSocket == INVALID_SOCKET) {
printf("Create Socket Failed! Error %d\n", WSAGetLastError());
closesocket(CliCnnSocket);
WSACleanup();
return false;
}
// Init SerAddr
struct sockaddr_in SerAddr;
memset(&SerAddr, 0, sizeof(SerAddr));
SerAddr.sin_family = AF_INET;
SerAddr.sin_addr.s_addr = inet_addr(SER_IP);
SerAddr.sin_port = htons(SER_PORT);
// connect server
if(connect(CliCnnSocket,(struct sockaddr*)&SerAddr, sizeof(SerAddr)) == 0){
printf("Connect %s:%d Server Success!\n", SER_IP, SER_PORT);
} else {
printf("Connect %s:%d Server Failed! Error %d\n", SER_IP, SER_PORT, WSAGetLastError());
closesocket(CliCnnSocket);
WSACleanup();
return false;
}
// exit flag
bExit = false;
// Create thread to handle connect
HANDLE hThread = CreateThread(NULL, 0, HandleS2CCnn, (LPVOID)CliCnnSocket, 0, NULL);
if (!hThread) {
printf("Create Thread Failed! Error %d\n", GetLastError());
}
CloseHandle(hThread);
char SendBuffer[SEND_BUFFER_MAX_LEN];
while (true) {
printf("Please Input : ");
memset(SendBuffer, 0, SEND_BUFFER_MAX_LEN);
cin.getline(SendBuffer, SEND_BUFFER_MAX_LEN);
if (strlen(SendBuffer) == 0) {
printf("The input cannot be empty! Please re-enter it!\n");
continue;
}
// client exit
if (strcmp(SendBuffer,"exit") == 0) {
ClientDoExit();
break;
}
// send data to server
if (send(CliCnnSocket, SendBuffer, strlen(SendBuffer), 0) == SOCKET_ERROR) {
printf("Send data Failed! Error %d\n", WSAGetLastError());
continue;;
}
// 延迟一下 等待服务器的回包在子线程中打印出来
Sleep(100);
}
return true;
}
主函数
1.编译生成Server
int main()
{
CreateServer();
system("pause");
return 0;
}
2.编译生成Client
int main()
{
// 禁用控制台关闭按钮
//DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE, MF_BYCOMMAND);
//DrawMenuBar(GetConsoleWindow());
// 处理控制台消息
SetConsoleCtrlHandler((PHANDLER_ROUTINE)HandleCloseConsole, TRUE);
CreateClient();
system("pause");
return 0;
}
测试截图
下面来测试一下在主线程closesocket出现的“不优雅”的关闭连接
CreateClient()修改如下:
从while循环break后 执行closesocket(CliCnnSocket); WSACleanup();
char SendBuffer[SEND_BUFFER_MAX_LEN];
while (true) {
printf("Please Input : ");
memset(SendBuffer, 0, SEND_BUFFER_MAX_LEN);
cin.getline(SendBuffer, SEND_BUFFER_MAX_LEN);
if (strlen(SendBuffer) == 0) {
printf("The input cannot be empty! Please re-enter it!\n");
continue;
}
// client exit
if (strcmp(SendBuffer,"exit") == 0) {
ClientDoExit();
break;
}
// send data to server
if (send(CliCnnSocket, SendBuffer, strlen(SendBuffer), 0) == SOCKET_ERROR) {
printf("Send data Failed! Error %d\n", WSAGetLastError());
continue;;
}
Sleep(100);
}
closesocket(CliCnnSocket);
WSACleanup();
HandleS2CCnn修改如下:
注释掉WSACleanup()即可
// Handle Server to Client Connect
DWORD WINAPI HandleS2CCnn(LPVOID lpParameter)
{
if (CliCnnSocket == INVALID_SOCKET) return -1;
char RecvBuffer[RECV_BUFFER_MAX_LEN];
while (true) {
if (bExit) break;
// waiting to receive data from server
memset(RecvBuffer, 0, RECV_BUFFER_MAX_LEN);
int Ret = recv(CliCnnSocket, RecvBuffer, RECV_BUFFER_MAX_LEN, 0);
if (Ret == 0) {
printf("Server have closed the connection!\n");
break;
} else if (Ret == SOCKET_ERROR) {
printf("Receive Server data Failed! Error %d\n", WSAGetLastError());
break;
}
printf("Receive Server data : %s\n", RecvBuffer);
}
closesocket(CliCnnSocket);
//WSACleanup();
return 0;
}
实验截图
查看错误码:
//
// MessageId: WSAECONNABORTED
//
// MessageText:
//
// An established connection was aborted by the software in your host machine.
//
#define WSAECONNABORTED 10053L
//
// MessageId: WSAECONNRESET
//
// MessageText:
//
// An existing connection was forcibly closed by the remote host.
//
#define WSAECONNRESET 10054L
10053:您的主机中的软件中止了一个已建立的连接
10054:一个连接被远程方强制关闭
分析原因:
客户端发送完“bye”之后立即关闭了连接,服务端收到“bye”之后再想回包时发现连接已被客户端关闭,所以服务端报出10054错误。而客户端子线程recv一直处于阻塞等待接收数据状态,在主线程关闭连接后,recv收到相关信息后,就报出了10053,连接已被主机中止。