tcp缓冲区中的多条消息

时间:2017-08-15 20:01:42

标签: c++ tcp

我实际上是使用tcp协议在c ++中编写一个简单的服务器及其客户端。由于这将集成在多人游戏中,因此每个客户都必须非常快速地发送数据。

问题:服务器的缓冲区有时会在其中收到多条消息。

我尝试了各种各样的东西,例如推迟nagle算法,但我没有设法解决这个问题。这是服务器的代码:

    #ifdef __linux__
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <arpa/inet.h>
    #define SOCKET int
    #define SOCKADDR_IN struct sockaddr_in
#endif
#ifdef _WIN32
    #include <winsock2.h>
#endif
#include <cstdio>
#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include "server.h"
#include "../../Logger/logger.h"
#include "../../AltisCraft.fr/Map/map.h"
#include "../../StringPlus/string_plus.h"
#include "../../AltisCraft.fr/Map/User/User.h"
void connectEvent(), receive(), sendAllUsers(string), closeConnectio(),manageMsg();
vector<SOCKET> clients;
vector<thread> clientsThreads;
vector<string> msg;
SOCKET socketId, newSocketId;
SOCKADDR_IN source;
thread connection;
char buffer[65535] = {0};
int position;

// TODO: crypt every data sendLog/receive
// TODO: whitelist ip serv
// TODO: Auth system
// TODO: timer with packet ? (double receive...)

int sendLog(SOCKET s, const char* c, int i0, int i1)
{
    log("Send:");
    log(c);
    send(s, c, i0, i1);
}

void initializeNetwork()
{
    #ifdef _WIN32
        WSADATA initWin32;
        WSAStartup(MAKEWORD(2, 2),&initWin32);
    #endif
    socketId = socket(AF_INET, SOCK_STREAM, 0);
    source.sin_family = AF_INET;
    source.sin_addr.s_addr = INADDR_ANY;
    source.sin_port = htons(33333);
    bind(socketId, (struct sockaddr*)&source, sizeof(source));
    connection = thread(&connectEvent);
    connection.join();
    closeConnection();
}

void connectEvent()
{
    int error;
    while(1)
    {
        error = 99;
        while(error != 0)
        {
            error = listen(socketId, 1);
        }
        #ifdef _WIN32
            int tempo = sizeof(source);
            newSocketId = accept(socketId, (struct sockaddr*)&source, &tempo);
            clients.push_back(newSocketId);
        #endif
        #ifdef __linux__
            socklen_t tempo;
            newSocketId = accept(socketId, (struct sockaddr *)&source, &tempo);
            clients.push_back(newSocketId);
        #endif
        clientsThreads.push_back(thread(&receive));
    }
}

void receive()
{
    int val = 1;
    position = clients.size() - 1;
    bool connected = 1;
    while(connected)
    {
        buffer[65535] = {0};
        if(recv(clients[position], buffer, 1515, 0) > 0)
        {
            string msg = buffer;
            bool isEmpty = false;
            log(string(inet_ntoa(source.sin_addr)) + ": " + msg);
            if(startsWith(msg, "Connect "))
                addUser(replace(msg, "Connect ", ""));
            else if(msg == "MAJ Map")
            {
                log(elements);
                string toSend = "MAJ Map\n" + elements;
                sendLog(clients[position], toSend.c_str(), strlen(toSend.c_str()), 0);
            }
            else if(startsWith(msg, "MAJ User ")) /// optimize: don't sendLog pos to player who sendLog
            {
                msg = replace(msg, "MAJ User ", "");
                if(startsWith(msg, "Pos "))
                {
                    msg = replace(msg, "Pos ", "");
                    vector<string> elements = split(msg, " ");
                    User user = *getUserByName(elements[0] + " " + elements[1]);
                    user.updateView(user.getView().updatePosition(Position(convertStrToDouble(elements[2]), convertStrToDouble(elements[3]), convertStrToDouble(elements[4]))));
                }
                else if(startsWith(msg, "ViewAngle "))
                {
                    msg = replace(msg, "ViewAngle ", "");
                    vector<string> elements = split(msg, " ");
                    User user = *getUserByName(elements[0] + " " + elements[1]);
                    user.updateView(user.getView().updateViewAngle(ViewAngle(convertStrToDouble(elements[2]), convertStrToDouble(elements[3]))));
                }
            }
            else
                sendAllUsers(string(string(inet_ntoa(source.sin_addr)) + ": " + msg).c_str());
        }
        else
            connected = 0;
    }
    shutdown(clients[position], 2);
    for(int i=0;i<msg.size();i++)
        cout << msg[i] << endl;
    #ifdef _WIN32
        closesocket(clients[position]);
    #endif
    #ifdef __linux__
        close(clients[position]);
    #endif
    clients.erase(clients.begin() + position);

}


void sendAllUsersWithoutOne(string msg, string name)
{
    for(int j = 0; j < (int)clients.size(); j++)
    {
        // only linux here (MSG_DONTWAIT)
        #ifdef __linux__
        if(recv(clients[j], NULL, 1, MSG_PEEK | MSG_DONTWAIT) == 0)
        {
            clients.erase(clients.begin() + j);
            continue;
        }
        #endif
        sendLog(clients[j], msg.c_str(), strlen(msg.c_str()), 0);
    }
}

void sendAllUsers(string msg)
{
    for(int j = 0; j < (int)clients.size(); j++)
    {
        // only linux here (MSG_DONTWAIT)
        #ifdef __linux__
        if(recv(clients[j], NULL, 1, MSG_PEEK | MSG_DONTWAIT) == 0)
        {
            clients.erase(clients.begin() + j);
            continue;
        }
        #endif
        sendLog(clients[j], msg.c_str(), strlen(msg.c_str()), 0);
    }
}

void closeConnection()
{
    for(int i = 0; i < (int)clients.size(); i++)
    {
        shutdown(clients[i], 2);
        #ifdef _WIN32
            closesocket(clients[i]);
        #endif
        #ifdef __linux__
            close(clients[i]);
        #endif
    }
    #ifdef _WIN32
        closesocket(socketId);
        WSACleanup();
    #endif
    #ifdef __linux__
        close(socketId);
    #endif
}

void freeNetwork()
{
    closeConnection();
}`

1 个答案:

答案 0 :(得分:2)

扩展Barmar的评论

TCP是流协议,而不是消息协议。只保证您发送n个字节,您将以相同的顺序接收n个字节。

您可以send 1个100字节的块并接收100个1字节recv,或者您可能会收到20个5字节recv s

你可以发送100个1字节的块并接收4个25字节的消息

您必须自己处理邮件边界。要么有一个标记值来标记开始和结束,要么预先设置一个固定大小的长度(所以你知道你已经阅读了整个长度)。然后循环recv,直到收到整条消息