什么是 socket?
socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。 通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。就像把插头插向插座能通电一样,socket就是这个通信的媒介。
套接字类型
使用TCP/IP协议的流格式套接字
可以将这个“流”比喻成一条传送带,只要传送带本身没有问题(不会断网),就能保证数据不丢失;同时,较晚传送的数据不会先到达,较早传送的数据不会晚到达,这就保证了数据是按照顺序传递的。
数据报套接字是一种可靠的、按顺序传递的、以可靠性为目的的套接字。主要应用于http网页请求
使用UDP协议的数据报格式的套接字(SOCK_DGRAM)
针对有些数据需要传输速度快为第一需求,发明了 使用UDP协议的数据报格式套接字,计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
它有以下特征:
- 强调快速传输而非传输顺序;
- 传输的数据可能丢失也可能损毁;
- 限制每次传输的数据大小;
- 数据的发送和接收是同步的(有的教程也称“存在数据边界”)。
数据报套接字是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字。主要应用于网络聊天和视频通话。
Windows 下用C++实现socket连接
Server
服务器分为server 和client ,可接受多个client。
server.h
#ifndef SERVER_H_INCLUDED
#define SERVER_H_INCLUDED
#include <winsock2.h>
#include <winbase.h>
#include <vector>
#include "sclient.h"
#pragma comment(lib, "ws2_32.lib") //动态库函数
/**
* 宏定义
*/
#define SERVER_SETUP_FAIL 1 //启动客户端失败
#define START_SERVER 1 //显示开始输入提示
#define INPUT_DATA 2 //提示输入什么数据
#define SERVERPORT 2022 //服务器TCP端口
#define CONN_NUM 10 //连接客户端数量
#define TIMEFOR_THREAD_SLEEP 500 //等待客户端请求线程睡眠时间
#define TIMEFOR_THREAD_HELP 1500 //清理资源线程退出时间
#define TIMEFOR_THREAD_EXIT 5000 //主线程睡眠时间
using ClIENTVECTOR = std::vector<CClient*>; //向量容器
/**
* 全局变量
*/
extern BOOL bConning; //与客户端的连接状态
extern BOOL clientConn; //连接客户端标记
extern SOCKET sServer; //服务器监听套接字
extern CRITICAL_SECTION cs; //保护数据的临界区对象
extern HANDLE hAcceptThread; //数据处理线程句柄
extern HANDLE hCleanThread; //数据接收线程
extern ClIENTVECTOR clientvector; //存储子套接字
class CServer
{
public:
CServer()
{
}
virtual ~CServer()
{
}
/**
*函数申明
*/
BOOL InitSever(u_short port); //初始化
void InitMember();
bool InitSocket(u_short port); //初始化非阻塞套接字
void ExitServer(); //释放资源
bool StartService(); //启动服务器
BOOL CreateCleanAndAcceptThread(); //开启监控函数
virtual void RecvClient(SOCKET sAccept, sockaddr_in addrClient);
virtual void CleanClient();
};
#endif // SERVER_H_INCLUDED
server.cpp
#include "pch.h"
#include <iostream>
#include "CScope.h"
#include "sclient.h"
#include "server.h"
#include <WinSock2.h>
#include <Ws2tcpip.h>
/**
* 全局变量
*/
//char dataBuf[MAX_NUM_BUF]; //写缓冲区
BOOL bConning; //与客户端的连接状态
BOOL bSend; //发送标记位
BOOL clientConn; //连接客户端标记
SOCKET sServer; //服务器监听套接字
CRITICAL_SECTION cs; //保护数据的临界区对象
HANDLE hAcceptThread; //数据处理线程句柄
HANDLE hCleanThread; //数据接收线程
ClIENTVECTOR clientvector; //存储子套接字
/**
/**
* 初始化
*/
BOOL CServer::InitSever(u_short port)
{
//初始化全局变量
InitMember();
//初始化SOCKET
if (!InitSocket(port))
return FALSE;
return TRUE;
}
/**
* 初始化全局变量
*/
void CServer::InitMember()
{
InitializeCriticalSection(&cs); //初始化临界区
bSend = FALSE;
clientConn = FALSE;
bConning = FALSE; //服务器为没有运行状态
hAcceptThread = nullptr; //设置为NULL
hCleanThread = nullptr;
sServer = INVALID_SOCKET; //设置为无效的套接字
clientvector.clear(); //清空向量
}
/**
* 初始化SOCKET
*/
bool CServer::InitSocket(u_short port)
{
//返回值
int reVal;
//初始化Windows Sockets DLL
WSADATA wsData;
reVal = WSAStartup(MAKEWORD(2, 2), &wsData);
//创建套接字
sServer = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sServer)
return FALSE;
//设置套接字非阻塞模式
unsigned long ul = 1;
reVal = ioctlsocket(sServer, FIONBIO, &ul);
if (SOCKET_ERROR == reVal)
return FALSE;
//绑定套接字
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(port);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
reVal = bind(sServer, (struct sockaddr*)&serAddr, sizeof(serAddr));
if (SOCKET_ERROR == reVal)
return FALSE;
//监听
reVal = listen(sServer, CONN_NUM);
if (SOCKET_ERROR == reVal)
return FALSE;
return TRUE;
}
/**
* 启动服务
*/
bool CServer::StartService()
{
return CreateCleanAndAcceptThread();
}
/**
* 接受客户端连接
*/
DWORD WINAPI acceptThread(LPVOID pParam)
{
SOCKET sAccept; //接受客户端连接的套接字
sockaddr_in addrClient; //客户端SOCKET地址
auto server = static_cast<CServer*>(pParam);
while (bConning) //服务器的状态
{
memset(&addrClient, 0, sizeof(sockaddr_in)); //初始化
int lenClient = sizeof(sockaddr_in); //地址长度
sAccept = accept(sServer, (sockaddr*)&addrClient, &lenClient); //接受客户请求
if (INVALID_SOCKET == sAccept)
{
int nErrCode = WSAGetLastError();
if (nErrCode == WSAEWOULDBLOCK) //无法立即完成一个非阻挡性套接字操作
{
Sleep(TIMEFOR_THREAD_SLEEP);
continue; //继续等待
}
return 0; //线程退出
}
//接受客户端的请求
server->RecvClient(sAccept, addrClient);
}
return 0; //线程退出
}
/**
* 清理资源线程
*/
DWORD __stdcall cleanThread(void* pParam)
{
auto server = static_cast<CServer*>(pParam);
server->CleanClient();
return 0;
}
void CServer::RecvClient(SOCKET sAccept, sockaddr_in addrClient)
{
clientConn = TRUE; //已经连接上客户端
auto pClient = new CClient(sAccept, addrClient);
//pClient->scope =scope;
EnterCriticalSection(&cs);
//显示客户端的IP和端口
char pClientIP[25];
inet_ntop(AF_INET, &addrClient.sin_addr, pClientIP, INET_ADDRSTRLEN);
u_short clientPort = ntohs(addrClient.sin_port);
char ch[255];
strcpy_s(ch, "Accept a client IP: ");
strcat_s(ch, pClientIP);
CScope::AddInfo(ch);
clientvector.push_back(pClient); //加入容器
LeaveCriticalSection(&cs);
pClient->StartRuning();
}
/**
* 清理客户端
*/
void CServer::CleanClient()
{
while (bConning) //服务器正在运行
{
EnterCriticalSection(&cs); //进入临界区
//清理已经断开的连接客户端内存空间
auto iter = clientvector.begin();
for (iter; iter != clientvector.end();)
{
CClient* pClient = *iter;
if (!pClient->IsConning()) //客户端线程已经退出
{
iter = clientvector.erase(iter); //删除节点
delete pClient; //释放内存
pClient = nullptr;
}
else
{
++iter; //指针下移
}
}
if (clientvector.size() == 0)
{
clientConn = FALSE;
}
LeaveCriticalSection(&cs); //离开临界区
Sleep(TIMEFOR_THREAD_HELP);
}
//服务器停止工作
if (!bConning)
{
//断开每个连接,线程退出
EnterCriticalSection(&cs);
auto iter = clientvector.begin();
for (iter; iter != clientvector.end();)
{
auto pClient = *iter;
//如果客户端的连接还存在,则断开连接,线程退出
if (pClient->IsConning())
{
pClient->DisConning();
}
++iter;
}
//离开临界区
LeaveCriticalSection(&cs);
//给连接客户端线程时间,使其自动退出
Sleep(TIMEFOR_THREAD_HELP);
}
clientvector.clear(); //清空链表
clientConn = FALSE;
}
/**
* 产生清理资源和接受客户端连接线程
*/
BOOL CServer::CreateCleanAndAcceptThread()
{
bConning = TRUE; //设置服务器为运行状态
//创建释放资源线程
unsigned long ulThreadId;
//创建接收客户端请求线程
hAcceptThread = CreateThread(nullptr, 0, acceptThread, this, 0, &ulThreadId);
//_beginthread(acceptThread, 0, this);
if (nullptr == hAcceptThread)
{
bConning = FALSE;
return FALSE;
}
CloseHandle(hAcceptThread);
//创建接收数据线程
hCleanThread = CreateThread(nullptr, 0, cleanThread, this, 0, &ulThreadId);
if (nullptr == hCleanThread)
{
return FALSE;
}
CloseHandle(hCleanThread);
return TRUE;
}
/**
* 释放资源
*/
void CServer::ExitServer()
{
closesocket(sServer); //关闭SOCKET
WSACleanup(); //卸载Windows Sockets DLL
}
sclient.h
#ifndef SCLIENT_H_INCLUDED
#define SCLIENT_H_INCLUDED
#include"pch.h"
#include <winsock2.h>
#define TIMEFOR_THREAD_CLIENT 500 //线程睡眠时间
#define MAX_NUM_CLIENT 10 //接受的客户端连接最多数量
#define MAX_NUM_BUF 8*1024 //缓冲区的最大长度
#define INVALID_OPERATOR 1 //无效的操作符
#define INVALID_NUM 2 //分母为零
#define ZERO 0 //零
extern char dataBuf[MAX_NUM_BUF]; //发送缓冲区
class CClient
{
public:
CClient(SOCKET sClient, const sockaddr_in& addrClient);
virtual ~CClient();
public:
BOOL StartRuning(); //创建接收数据线程
BOOL IsConning()
{
//是否连接存在
return m_bConning;
}
void DisConning()
{
//断开与客户端的连接
m_bConning = FALSE;
}
BOOL IsExit()
{
//接收和发送线程是否已经退出
return m_bExit;
}
BOOL IsSend()
{
m_bSend = TRUE;
return m_bSend;
}
public:
static DWORD __stdcall RecvDataThread(void* pParam); //接收客户端数据
public:
bool Send(const char* buf);
protected:
virtual void OnRecv(int len, const char* buf)
{
}
public:
char ip[25];
u_short port;
char recvBuf[MAX_NUM_BUF]; //接收数据二级缓冲区
private:
SOCKET m_socket; //套接字
sockaddr_in m_addr; //地址
HANDLE m_hEvent; //事件对象
HANDLE m_hThreadSend; //发送数据线程句柄
HANDLE m_hThreadRecv; //接收数据线程句柄
CRITICAL_SECTION m_cs; //临界区对象
BOOL m_bConning; //客户端连接状态
BOOL m_bSend; //数据发送状态
BOOL m_bExit; //线程退出
};
#endif // SCLIENT_H_INCLUDED
sclient.cpp
#include "pch.h"
#include "sclient.h"
#include <stdio.h>
#include <Ws2tcpip.h>
#include "server.h"
char dataBuf[MAX_NUM_BUF]; //发送缓冲区
/*
* 构造函数
*/
CClient::CClient(const SOCKET sClient, const sockaddr_in& addrClient)
{
//初始化变量
m_hThreadRecv = nullptr;
m_hThreadSend = nullptr;
m_socket = sClient;
m_addr = addrClient;
inet_ntop(AF_INET, &m_addr.sin_addr, ip, 20);
port= ntohs(m_addr.sin_port);
m_bConning = FALSE;
m_bExit = FALSE;
m_bSend = FALSE;
memset(recvBuf, 0, MAX_NUM_BUF);
//创建事件
m_hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); //手动设置信号状态,初始化为无信号状态
memset(dataBuf, 0, MAX_NUM_BUF);
//初始化临界区
InitializeCriticalSection(&m_cs);
}
/*
* 析构函数
*/
CClient::~CClient()
{
closesocket(m_socket); //关闭套接字
m_socket = INVALID_SOCKET; //套接字无效
DeleteCriticalSection(&m_cs); //释放临界区对象
CloseHandle(m_hEvent); //释放事件对象
}
/*
* 创建接收数据线程
*/
BOOL CClient::StartRuning()
{
m_bConning = TRUE; //设置连接状态
//创建接收数据线程
unsigned long ulThreadId;
m_hThreadRecv = CreateThread(nullptr, 0, RecvDataThread, this, 0, &ulThreadId);
if (nullptr == m_hThreadRecv)
{
return FALSE;
}
CloseHandle(m_hThreadRecv);
return TRUE;
}
/*
* 接收客户端数据
*/
DWORD CClient::RecvDataThread(void* pParam)
{
auto pClient = static_cast<CClient*>(pParam); //客户端对象指针
int reVal; //返回值
char temp[MAX_NUM_BUF]; //临时变量
while (pClient->m_bConning) //连接状态
{
memset(temp, 0, MAX_NUM_BUF);
reVal = recv(pClient->m_socket, temp, MAX_NUM_BUF, 0); //接收数据
//处理错误返回值
if (SOCKET_ERROR == reVal)
{
int nErrCode = WSAGetLastError();
if (WSAEWOULDBLOCK == nErrCode) //接受数据缓冲区不可用
{
continue; //继续循环
}
if (WSAENETDOWN == nErrCode || //客户端关闭了连接
WSAETIMEDOUT == nErrCode ||
WSAECONNRESET == nErrCode)
{
break; //线程退出
}
}
//客户端关闭了连接
if (reVal == 0)
{
break;
}
//收到数据
if (reVal > 0)
{
EnterCriticalSection(&pClient->m_cs);
char clientIP[20];
inet_ntop(AF_INET,&pClient->m_addr.sin_addr, clientIP, 20);
u_short clientPort = ntohs(pClient->m_addr.sin_port);
// cout << "IP: " << clientIP << "\tPort: " << clientPort << ":" << temp << endl; //输出显示数据
pClient->OnRecv(reVal,temp);
LeaveCriticalSection(&pClient->m_cs);//离开临界区
memset(temp, 0, MAX_NUM_BUF); //清空临时变量
}
}
pClient->m_bConning = FALSE; //与客户端的连接断开
return 0; //线程退出
}
/*
* @des: 向客户端发送数据
*/
bool CClient::Send(const char* sendbuf)
{
EnterCriticalSection(&this->m_cs);//进入临界区
strcpy_s(dataBuf, sendbuf);
int iResult = send(m_socket, dataBuf, strlen(dataBuf), 0);
if (iResult == SOCKET_ERROR)
{
closesocket(m_socket);
WSACleanup();
return false;
}
memset(dataBuf, 0, MAX_NUM_BUF); //清空缓冲区
LeaveCriticalSection(&this->m_cs); //离开临界区
return true;
}
Client
client.h
#ifndef CLIENT_H_INCLUDED
#define CLIENT_H_INCLUDED
#include <iostream>
#pragma comment(lib, "WS2_32.lib")
//宏定义
#define SERVERIP "192.168.0.50" //服务器IP
#define SERVERPORT 2022 //服务器TCP端口
#define MAX_NUM_BUF 8*1024 //缓冲区的最大长度
using namespace std;
class CClient
{
public:
CClient(char* ip, char* port);
virtual ~CClient();
//函数声明
BOOL Init(); //初始化
void InitMember(); //初始化全局变量
BOOL InitSocket(); //非阻塞套接字
BOOL ConnectServer(); //连接服务器
bool Send(const char* data);
void Exit(); //退出服务器
BOOL CreateRecvThread();
public:
virtual void OnRecv(int len, const char* buf) //后续子类继承重写
{
}
virtual void AfterClose()//后续子类继承重写,断开连接后的处理
{
}
protected:
char* Ip;
char* Port;
char second_recvBuf[MAX_NUM_BUF]; //接收数据二级缓冲区
};
#endif // SCLIENT_H_INCLUDED
client.cpp
#include "pch.h"
#include <Ws2tcpip.h>
#include "client.h"
#include "Scope.h"
//变量
SOCKET sClient; //套接字
HANDLE hThreadSend; //发送数据线程
HANDLE hThreadRecv; //接收数据线程
char first_recvBuf[MAX_NUM_BUF]; //一级接收数据缓冲区
BOOL bConnecting; //与服务器的连接状态
HANDLE arrThread[2]; //子线程数组
CRITICAL_SECTION cs; //临界区对象,锁定bufSend
CClient::CClient(char* ip, char* port)
{
Ip = ip;
Port = port;
sClient = INVALID_SOCKET;
//初始化数据缓冲区
memset(first_recvBuf, 0, MAX_NUM_BUF);
memset(second_recvBuf, 0, MAX_NUM_BUF);
}
CClient::~CClient()
{
if (sClient != INVALID_SOCKET)
closesocket(sClient);
}
/**
* 初始化
*/
BOOL CClient::Init()
{
//初始化全局变量
InitMember();
//创建SOCKET
if (!InitSocket())
{
return FALSE;
}
return TRUE;
}
/**
* 初始化全局变量
*/
void CClient::InitMember()
{
InitializeCriticalSection(&cs);
sClient = INVALID_SOCKET; //套接字
hThreadRecv = NULL; //接收数据线程句柄
hThreadSend = NULL; //发送数据线程句柄
bConnecting = FALSE; //为连接状态
memset(arrThread, 0, 2);
}
/**
* 创建非阻塞套接字
*/
BOOL CClient::InitSocket()
{
int reVal; //返回值
WSADATA wsData; //WSADATA变量
reVal = WSAStartup(MAKEWORD(2, 2), &wsData);//初始化Windows Sockets Dll
//创建套接字
sClient = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sClient)
return FALSE;
//设置套接字非阻塞模式
unsigned long ul = 1;
reVal = ioctlsocket(sClient, FIONBIO, &ul);
if (reVal == SOCKET_ERROR)
return FALSE;
return TRUE;
}
/**
* 连接服务器
*/
BOOL CClient::ConnectServer()
{
unsigned short u_port = (unsigned short)strtoul(Port, NULL, 0);
//sockaddr_in serAddr;//服务器地址
输入要连接的主机地址
//serAddr.sin_family = AF_INET;
//serAddr.sin_port = htons(u_port);
//serAddr.sin_addr.S_un.S_addr = inet_addr(Ip);
struct addrinfo* result = nullptr, * ptr = nullptr, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int reVal = getaddrinfo(Ip, Port, &hints, &result);
if (reVal != 0)
{
WSACleanup();
return false;
}
for (ptr = result; ptr != nullptr; ptr = ptr->ai_next)
{
// Create a SOCKET for connecting to server
sClient = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (sClient == INVALID_SOCKET)
{
WSACleanup();
return false;
}
reVal = connect(sClient, ptr->ai_addr, static_cast<int>(ptr->ai_addrlen));
if (reVal == SOCKET_ERROR)
{
closesocket(sClient);
sClient = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (sClient == INVALID_SOCKET)
{
bConnecting = false;
WSACleanup();
return false;
}
bConnecting = TRUE;
return CreateRecvThread();
}
/**
* 接收数据线程
*/
DWORD __stdcall RecvDataThread(void* pParam)
{
auto pClient = static_cast<CClient*>(pParam); //客户端对象指针
while (bConnecting) //连接状态
{
memset(first_recvBuf, 0, MAX_NUM_BUF);
const int reVal = recv(sClient, first_recvBuf, MAX_NUM_BUF, 0);//接收数据
if (SOCKET_ERROR == reVal)
{
int nErrCode = WSAGetLastError();
if (WSAEWOULDBLOCK == nErrCode) //接受数据缓冲区不可用
{
continue; //继续接收数据
}
else {
bConnecting = FALSE;
return 0; //线程退出
}
}
if (reVal == 0) //服务器关闭了连接
{
CScope::AddInfo("服务器关闭连接!");
//pClient->AfterClose();//客户端退出
bConnecting = FALSE;
memset(first_recvBuf, 0, MAX_NUM_BUF); //清空接收缓冲区
pClient->Exit();
return 0; //线程退出
}
if (reVal > 0)
{
pClient->OnRecv(reVal, first_recvBuf);
}
}
pClient->AfterClose();//客户端退出
return 0;
}
/**
* 创建发送和接收数据线程
*/
BOOL CClient::CreateRecvThread()
{
//创建接收数据的线程
unsigned long ulThreadId;
hThreadRecv = CreateThread(NULL, 0, RecvDataThread, this, 0, &ulThreadId);
if (NULL == hThreadRecv)
return FALSE;
//添加到线程数组
arrThread[0] = hThreadRecv;
return TRUE;
}
/**
* 客户端退出
*/
void CClient::Exit()
{
DeleteCriticalSection(&cs);
CloseHandle(hThreadRecv);
CloseHandle(hThreadSend);
memset(first_recvBuf, 0, MAX_NUM_BUF);
memset(second_recvBuf, 0, MAX_NUM_BUF);
closesocket(sClient);
WSACleanup();
}
/*
* @des: 向客户端发送数据
*/
bool CClient::Send(const char* sendData)
{
int iResult = send(sClient, sendData, strlen(sendData), 0);
if (iResult == SOCKET_ERROR)
{
closesocket(sClient);
WSACleanup();
return false;
}
return true;
}