Socket通信

什么是 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;
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值