面向对象的select网络模型加上防止粘包、根据数据头所提供的长度进行接收发数据

数据包格式

#ifndef _MessageHeader_hpp_
#define _MessageHeader_hpp_

enum CMD{

    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_NEW_USER_JOIN,
    CMD_ERROR
};

//文件头
struct DataHeader{

    short cmd;
    short dataLength;

};
//登录
struct Login :public DataHeader
{
    Login(){
        dataLength = sizeof(Login);
        cmd = CMD_LOGIN;
    }

    char username[32];
    char password[32];
};
//登录结果
struct LoginResult :public DataHeader {

    LoginResult(){
        dataLength = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
        result = 1;
    }
    int result;

};
//退出
struct Logout :public DataHeader{

    Logout(){
        dataLength = sizeof(Logout);
        cmd = CMD_LOGOUT;
    }
    char userName[32];
};
//退出结果
struct LogoutResult :public DataHeader{

    LogoutResult(){
        dataLength = sizeof(LogoutResult);
        cmd = CMD_LOGOUT_RESULT;
        result = 1;
    }
    int result;
    char data[1024];

};
//新用户加入
struct NewUserJoin :public DataHeader{

    NewUserJoin(){
        dataLength = sizeof(NewUserJoin);
        cmd = CMD_NEW_USER_JOIN;
        scok = 0;
    }
    int scok;

};
#endif

 

client端:主要改进在接收数据的函数


#ifndef _easyClient_hpp_
#define _easyClient_hpp_

#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")//动态链接库
#include<iostream>
#include<stdio.h>
#include<thread>
#include "MH.h"

// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来

class easyClient
{
    SOCKET sock;
public:
    easyClient()
    {
        sock = INVALID_SOCKET;
    }
    //虚析构函数
    virtual ~easyClient()
    {
        Close();
    }
    //初始化socket
    void InitSocket(){
        //启动winsock2.x环境
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
        if (sock != INVALID_SOCKET){
            printf("<SOCKET=%d>关闭旧连接", sock);
            Close();
        }
        //1、建立socket
        sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);


    }

    int Connect(char* ip, unsigned short port){
        if (sock == INVALID_SOCKET){
            InitSocket();
        }

        // 2、连接服务器
        sockaddr_in _sin = {};
        _sin.sin_family = AF_INET;
        _sin.sin_port = htons(port);//host to net unsigned short
        _sin.sin_addr.S_un.S_addr = inet_addr(ip);
        int ret = connect(sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
        if (SOCKET_ERROR == ret)
            std::printf("绑定网络端口失败。。。");
        return ret;
    }

    //关闭socket
    void Close(){

        //关闭winsock 2.x环境
        if (sock != INVALID_SOCKET){
            closesocket(sock);
            WSACleanup();
            sock = INVALID_SOCKET;
        }

    }

    //处理网络消息
    bool  OnRun(){

        if (isRun()){
            fd_set fdReader;
            FD_ZERO(&fdReader);
            FD_SET(sock, &fdReader);
            timeval t = { 1, 0 };

            int ret = select(sock, &fdReader, 0, 0, NULL);
            if (ret < 0){
                std::printf("<SOCKET=%d>select 任务结束1\n", sock);
                return false;
            }

            if (FD_ISSET(sock, &fdReader)){

                FD_CLR(sock, &fdReader);

                if (-1 == RecvData(sock)){
                    std::printf("<SOCKET=%d>select 任务结束2\n", sock);
                    return false;
                }
            }
            return true;
        }
        return false;

    }
    //缓冲区最小单元大小
   #define RECV_BUFF_SIZE 1024
    //接收缓冲区
    char _szRecv[RECV_BUFF_SIZE] ;

    //第二缓冲区
    //char _szMagBuf[RECV_BUFF_SIZE * 10] = {};
    char _szMagBuf[10240] ;


    //消息缓冲区尾部位置
    int _lastPos = 0;
    //接收消息,处理粘包
    int RecvData(SOCKET cSocket){

    
        //5、接收客户端请求数据

        int nLen = recv(cSocket, _szRecv, RECV_BUFF_SIZE, 0);
        //当前消息长度
        DataHeader* header = (DataHeader*)_szRecv;
        if (nLen <= 0){
            std::printf("与服务器断开连接,任务结束。\n", cSocket);
            return -1;
        }
        //将接收到的消息拷贝到当地的消息缓冲区
        memcpy(_szMagBuf+_lastPos, _szRecv, nLen);
        //消息缓冲区的数据尾部位置后移
        _lastPos += nLen;

        //消息缓冲区是否大于消息头长度
        while (_lastPos >= sizeof(DataHeader)){
            //消息的长度
            DataHeader* header = (DataHeader*)_szRecv;
            if (_lastPos > header->dataLength){

                //未处理消息缓冲区的长度
                int nSize = _lastPos - header->dataLength;
                //处理网络消息
                OnNetMag(cSocket,header);
                //将消息缓冲区剩余未处理数据前移
                memcpy(_szMagBuf, _szMagBuf + header->dataLength, nSize);
                //消息的尾部位置前移
                _lastPos = nSize;
            }
            else{
                //消息缓冲区剩余数据不够一条完整的消息
                break;
            }
        }

 

        //recv(_cSocket, _szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);

        //OnNetMag(_cSocket, header);

        return 0;

    }

    //响应网络消息
    void OnNetMag(SOCKET _cSocket, DataHeader* header){
        //6、处理请求

        switch (header->cmd){
        case CMD_LOGIN_RESULT: {

                                   LoginResult* login = (LoginResult*)header;
                                   std::printf("收到服务器=%d消息:CMD_LOGIN_RESULT,数据长度:%d  \n", _cSocket, header->dataLength);

 

        } break;
        case CMD_LOGOUT_RESULT:{

                                   LogoutResult* logout = (LogoutResult*)header;
                                   std::printf("收到服务器=%d消息:CMD_LOGOUT_RESULT,数据长度:%d  \n", _cSocket, header->dataLength);

 

        }break;
        case CMD_NEW_USER_JOIN:{

                                   NewUserJoin* userjoin = (NewUserJoin*)header;
                                   std::printf("收到服务器=%d消息:CMD__NEW_USER_JOIN,新用户加入\n", _cSocket);


        } break;
        case CMD_ERROR:
        {
                          printf("<Socket=%d>收到服务端消息:CMD_ERROR,数据长度:%d\n", _cSocket, header->dataLength);

        }break;
        default:{
                    printf("<Socket=%d>收到服务端未定义消息,数据长度:%d\n", _cSocket, header->dataLength);
        }

        }
        std::printf("发送成功\n");

    }
    //发送网络消息
    int SendData(DataHeader* header){
        if (isRun() && header){
            return send(sock, (const char*)header, header->dataLength, 0);
        }
        return INVALID_SOCKET;
    }


    //是否工作中
    bool isRun(){
        return sock != INVALID_SOCKET;
    }
private:

 

};
#endif

client端的main.cpp

主函数:

 

#include<iostream>
#include<stdio.h>
#include<thread>
#include "client1.h"
#include"MH.h"
using namespace std;

 


// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来


bool g_bRun = true;
void cmdThread()
{
    while (true){

        char cmdBuf[256] = {};
        scanf("%s", cmdBuf);
        if (0 == strcmp(cmdBuf, "exit")){
    
            std::printf("退出thread线程\n");
            break;
        }
        
        else{
            printf("不支持的命令\n");
        }
    }

 

}

int main(){
    const int ncount = 10;
    easyClient* client[ncount];
    
    for (int n = 0; n < ncount; n++){
        client[n] = new easyClient();
        client[n]->Connect("127.0.0.1", 4567);
    }
    //启动线程

    thread t1(cmdThread);
    t1.detach();

    Login login;
    strcpy(login.username, "lyd");
    strcpy(login.password, "123");

    while (g_bRun){
        for (int n = 0; n < ncount; n++){
            client[n]->SendData(&login);
        }


    }
    for (int n = 0; n < ncount; n++){

        client[n]->Close();
        delete client[n];
    }
    

 

    std::printf("已退出\n");
    std::system("pause");

    return 0;
}

 

server端:

//将接受的client端封装到ClientSocket中,对client的对象进行处理。

//注意:因为栈内存很小,所以在生成对象的时候,最好使用指针来完成。指针指向实际对象存储在堆内存上。

#ifndef _servers_hpp_
#define _servers_hpp_

#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<vector>
#include"MH.h"
#pragma comment(lib,"ws2_32.lib")//动态链接库
//可以在属性的链接库中添加
//using namespace std;

 

// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来


#define RECV_BUFF_SIZE 1024
//class ClientSocket,纪录每个链接服务器的客户端
    
class ClientSocket{
public:

    ClientSocket(SOCKET sockfd=INVALID_SOCKET){
        _sockfd = sockfd;
        memset(_szMagBuf, 0, sizeof(_szMagBuf));
        _lastPos = 0;
    }
    SOCKET sockfd(){

        return _sockfd;
    }
    char* msgBuf(){

        return _szMagBuf;
    }

    int getLastPos(){
        return _lastPos;
    }

    void setLastPos(int pos){
        
        _lastPos = pos;
    }
private:
    SOCKET _sockfd;


    //第二缓冲区  消息缓冲区
    char _szMagBuf[RECV_BUFF_SIZE * 10];

    //char _szMagBuf[2048] = {};
    //消息缓冲区尾部位置
    int _lastPos ;
};

 

// new 堆内存
// int a=0; 栈上面
class servers{

private:
    std::vector<ClientSocket*> g_clients;

    SOCKET sock;

public:
    servers(){
        sock = INVALID_SOCKET;
    }

    virtual ~servers(){

        Close();
    }

    //初始化Socket
    SOCKET InitSocket(){

        //启动winsock2.x环境
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
        if (sock != INVALID_SOCKET){
            printf("<SOCKET=%d>关闭旧连接", sock);
            Close();
        }
        //1、建立socket
       sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        return sock;
    }

    //绑定端口号
    int Bind(const char* ip, unsigned short port){

        if (sock == INVALID_SOCKET){
            InitSocket();
        }
        // 2、绑定网络端口
        sockaddr_in _sin = {};
        _sin.sin_family = AF_INET;
        _sin.sin_port = htons(port);//host to net unsigned short
        _sin.sin_addr.S_un.S_addr = inet_addr(ip);

        int ret = bind(sock, (sockaddr*)&_sin, sizeof(sockaddr_in));

        if (SOCKET_ERROR == ret)
            printf("绑定网络端口失败。。。");

        return ret;

 

    }


    //监听端口号
    int Listen(int n){

        //3、listen 监听端口
        int ret = listen(sock, n);
        if (SOCKET_ERROR == ret)
            printf("<SOCKET=%d>监听网络端口失败。。。", sock);

        return ret;
    }

 

    //接收客户端连接
    SOCKET Accept(){
        //4、accept 等待客户端连接
        sockaddr_in clientAddr = {};
        int nAddrlen = sizeof(sockaddr_in);
        SOCKET _cSocket = INVALID_SOCKET;

        _cSocket = accept(sock, (sockaddr*)&clientAddr, &nAddrlen);

        if (INVALID_SOCKET == _cSocket){
            printf("<SOCKET=%d>错误,接收到无效客户端。。。\n", (int)_cSocket);
        }
        else
        {
            NewUserJoin userjoin;
            SendDataToAll(&userjoin);
            g_clients.push_back(new ClientSocket(_cSocket));

            printf("新客户端加入:socket=%d,IP=%s\n", (int)_cSocket, inet_ntoa(clientAddr.sin_addr));

        }
        return _cSocket;
    }

    //关闭socket
    void Close(){

        //关闭winsock 2.x环境
        if (sock != INVALID_SOCKET){
            for (int n = (int)g_clients.size() - 1; n >= 0; n--){
                closesocket(g_clients[n]->sockfd());
                delete g_clients[n];
            }

            // 7、关闭服务端套接字
            closesocket(sock);
            WSACleanup();
            sock = INVALID_SOCKET;
        }

    }


    

    //处理网络消息
    bool OnRun(){
        if (isRun()){

            fd_set fdRead;
            fd_set fdWrite;
            fd_set fdExp;

            //清理集合
            FD_ZERO(&fdRead);
            FD_ZERO(&fdWrite);
            FD_ZERO(&fdExp);

            //加入集合
            FD_SET(sock, &fdRead);
            FD_SET(sock, &fdWrite);
            FD_SET(sock, &fdExp);

            SOCKET maxSock = sock;
            for (int n = (int)g_clients.size() - 1; n >= 0; n--){
                //判断集合是否在集合中
                FD_SET(g_clients[n]->sockfd(), &fdRead);
                if (maxSock < g_clients[n]->sockfd())
                    maxSock = g_clients[n]->sockfd();
            }
            timeval t = { 1, 0 };
            int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &t);
            if (ret < 0){
                printf("select 客户端已退出。。。\n");
                Close();
                return false;
            }

            if (FD_ISSET(sock, &fdRead)){

                FD_CLR(sock, &fdRead);
                Accept();
            }

 

            for (int n = (int)g_clients.size() - 1; n >= 0; n--)
            {
                if (FD_ISSET(g_clients[n]->sockfd(), &fdRead)){
                    {
                        if (-1 == RecvData(g_clients[n]))
                        {
                            //auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
                            auto iter = g_clients.begin() + 1;
                            if (iter != g_clients.end())
                            {
                                delete g_clients[n];
                                g_clients.erase(iter);
                            }
                        }
                    }
                }

            }
            return true;
        }

        else{
            return false;
        }
    }
    //发送指定socket网络消息
    int SendData(SOCKET _cSocket, DataHeader* header){
        if (isRun() && header){
            return send(_cSocket, (const char*)header, header->dataLength, 0);
        }
        return INVALID_SOCKET;
    }

    //发送指定socket网络消息
    void SendDataToAll(DataHeader* header){

        if (isRun() && header){
            for (int n = (int)g_clients.size() - 1; n >= 0; n--){
                SendData(g_clients[n]->sockfd(), header);
            }
        }

    }

    //是否工作中
    bool isRun(){
        return sock != INVALID_SOCKET;
    }


    //消息缓冲区
    char _szRecv[RECV_BUFF_SIZE] ;
    //接收消息,处理粘包
    int RecvData(ClientSocket* pClient){
    
        //5、接收客户端请求数据

        int nLen = recv(pClient->sockfd(), _szRecv, RECV_BUFF_SIZE, 0);

        
        if (nLen <= 0){
            std::printf("客户端<SOCKET=%d>已退出\n", pClient->sockfd());
            return -1;
        }

        //将接收到的消息拷贝到当地的消息缓冲区
        memcpy(pClient->msgBuf() + pClient->getLastPos(), _szRecv, nLen);

        //消息缓冲区的数据尾部位置后移
        pClient->setLastPos(pClient->getLastPos() + nLen);


        //消息缓冲区是否大于消息头长度
        while (pClient->getLastPos() >= sizeof(DataHeader)){

            //消息的长度
            DataHeader* header = (DataHeader*)pClient->msgBuf();

            //收到消息大于消息头,开始处理数据
            if (pClient->getLastPos() > header->dataLength){

                //未处理消息缓冲区的长度
                int nSize = pClient->getLastPos() - header->dataLength;
                //处理网络消息
                OnNetMag(pClient->sockfd(), header);
                //将消息缓冲区剩余未处理数据前移
                memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);
                //消息的尾部位置前移
                pClient->setLastPos(nSize);
            }
            else{
                //消息缓冲区剩余数据不够一条完整的消息
                break;
            }
        }


    
        return 1;

    }

    //响应网络消息
    virtual void OnNetMag(SOCKET _cSocket, DataHeader* header){

        switch (header->cmd){
        case CMD_LOGIN: {


                            Login* login = (Login*)header;
                            std::printf("收到<SOCKET=%d>命令:CMD_LOGIN,数据长度:%d ,username:%s password:%s \n", _cSocket, login->dataLength, login->username, login->password);

                            std::printf("姓名为:%s上线了。。。\n", login->username);

                            LoginResult ret;
                            SendData(_cSocket, (DataHeader*)&ret);

        }break;

        case CMD_LOGOUT:{


                            Logout* logout = (Logout*)header;
                            std::printf("收到<SOCKET=%d>命令:CMD_LOGOUT,数据长度:%d  username:%s \n", _cSocket, logout->dataLength, logout->userName);

                            std::printf("姓名为:%s 已下线。。。\n", logout->userName);

                            LogoutResult ret;
                            SendData(_cSocket, (DataHeader*)&ret);


        }
            break;

        default:
        {    
                   printf("<Socket=%d>收到未定义消息,数据长度:%d\n", _cSocket, header->dataLength);
                   DataHeader header ;
                   SendData(_cSocket, &header);
        }
            break;

        }

 

        std::printf("发送成功\n");
    }

 

};

 

#endif

 

server的main.cpp

主函数


#include<iostream>
#include"server1.h"
#include<thread>

 

// 利用當地的消息緩存區來將存储接收的消息,先将消息的头部存储,然后通过对消息长度的提取,提取出消息。
//进而处理消息。处理粘包,将发送的消息分离开来


bool g_bRun = true;
void cmdThread(){
    while (true){
        char cmdBuf[256] = {};
        scanf("%s", cmdBuf);
        if (0 == strcmp(cmdBuf, "exit")){
            g_bRun = false;
            printf("退出cmdThread服务器\n");
            break;
        }
        else{
            printf("不支持的命令\n");
        }
    }
}


int main01(){

    servers server;
    server.InitSocket();
    server.Bind("127.0.0.1", 4567);
    server.Listen(5);


    while (g_bRun){

        server.OnRun();

    }
    server.Close();

 

    printf("已退出,任务结束");
    system("pause");
    return 0;
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值