【问题标题】:Continuously sending data from server to receiver using c++使用c ++连续将数据从服务器发送到接收器
【发布时间】:2020-05-01 02:27:26
【问题描述】:

我正在使用 winsock api 在 C++ 中进行编码。我正在进行多客户端服务器聊天。我在代码中遇到的问题是我的服务器只能向客户端发送一次消息。但我希望这种情况发生多次。我不能在服务器代码中将 accept() 函数置于无限循环之外。我已将 select() 用于多客户端。我是在没有线程的情况下做的。

服务器:

#include <iostream>
#include <WS2tcpip.h>
#include <string>
#include <sstream>

#pragma comment (lib, "ws2_32.lib")

using namespace std;

void main()
{
// Initialze winsock
   WSADATA wsData;
   WORD ver = MAKEWORD(2, 2);

   int wsOk = WSAStartup(ver, &wsData);
   if (wsOk != 0)
  {
    cerr << "Can't Initialize winsock! Quitting" << endl;
    return;
  }

// Create a socket
SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
if (listening == INVALID_SOCKET)
{
    cerr << "Can't create a socket! Quitting" << endl;
    return;
}

// Bind the ip address and port to a socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
hint.sin_addr.S_un.S_addr = INADDR_ANY; // Could also use inet_pton .... 

bind(listening, (sockaddr*)&hint, sizeof(hint));

// Tell Winsock the socket is for listening 
listen(listening, SOMAXCONN);

// Create the master file descriptor set and zero it
fd_set master;
FD_ZERO(&master);

// Add our first socket that we're interested in interacting with; the listening socket!
// It's important that this socket is added for our server or else we won't 'hear' incoming
// connections 
FD_SET(listening, &master);

// this will be changed by the \quit command (see below, bonus not in video!)
bool running = true;

while (running)
{
    // Make a copy of the master file descriptor set, this is SUPER important because
    // the call to select() is _DESTRUCTIVE_. The copy only contains the sockets that
    // are accepting inbound connection requests OR messages. 

    // E.g. You have a server and it's master file descriptor set contains 5 items;
    // the listening socket and four clients. When you pass this set into select(), 
    // only the sockets that are interacting with the server are returned. Let's say
    // only one client is sending a message at that time. The contents of 'copy' will
    // be one socket. You will have LOST all the other sockets.

    // SO MAKE A COPY OF THE MASTER LIST TO PASS INTO select() !!!

    fd_set copy = master;

    // See who's talking to us
    int socketCount = select(0, &copy, nullptr, nullptr, nullptr);

    for (int i = 0; i < socketCount; i++) {

        //Accept a new connection

        SOCKET sock = copy.fd_array[i];
        if (sock == listening) {
            //Accept a new connection

            SOCKET client = accept(listening, nullptr, nullptr);

            //Add a new connection

            FD_SET(client, &master);

            string mssg = "Welcome to the awesome chat server\n";
            //Send a welcome message to the connected client
            send(client, mssg.c_str(), mssg.size() + 1, 0);
        }

        //Send a new message

        string mssg;
        getline(cin, mssg);

        int bytes = send(sock, mssg.c_str(), mssg.size() + 1, 0);

        for (int i = 0; i < master.fd_count; i++) {
            SOCKET outsock = master.fd_array[i];
            if (outsock != listening && outsock != sock) {
                send(outsock, mssg.c_str(), mssg.size() + 1, 0);
            }
        }

    }

}   


// Remove the listening socket from the master file descriptor set and close it
// to prevent anyone else trying to connect.
FD_CLR(listening, &master);
closesocket(listening);

// Message to let users know what's happening.
string msg = "Server is shutting down. Goodbye\r\n";

while (master.fd_count > 0)
{
    // Get the socket number
    SOCKET sock = master.fd_array[0];

    // Send the goodbye message
    send(sock, msg.c_str(), msg.size() + 1, 0);

    // Remove it from the master file list and close the socket
    FD_CLR(sock, &master);
    closesocket(sock);
}

// Cleanup winsock
WSACleanup();

system("pause");
     }

客户端代码:

  #include<iostream>
  #include<ws2tcpip.h>
  #include<string>
  using namespace std;
   #pragma comment(lib,"ws2_32.lib")
   void main() {
string ipAddress = "127.0.0.1";     //IP Address of the server
int port = 54000;                   //Listening port on the sever
//Initialize Winsock 
WSADATA data;
WORD ver = MAKEWORD(2, 2);
int wsResult = WSAStartup(ver, &data);
if (wsResult != 0) {
    cerr << " Can't initialize winsock " << endl;
    return;
}
//Create socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
    cerr << "Can't create a socket " << WSAGetLastError() << endl;
    closesocket(sock);
    WSACleanup();
    return;
}
//Fill in a hint structure
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
inet_pton(AF_INET, ipAddress.c_str(), &hint.sin_addr);
//Connect to the server
int connResult = connect(sock, (sockaddr*)&hint, sizeof(hint));
if (connResult == SOCKET_ERROR) {
    cerr << " Can't connect to the server " << WSAGetLastError() << endl;
    closesocket(sock);
    WSACleanup();
    return;
}
//Do-While loop to send and receive data
//char b[4096];
//int bytes = recv(sock,b,4096, 0);

//cout << string(b, 0, bytes) << endl;

char buff[4096];
string userInput;


do {
    //Prompt the user
    //cout << ">";
    //getline(cin, userInput);
    //Send the result
    //int sendResult = send(sock, userInput.c_str(), userInput.size() + 1, 0);
    //if (sendResult != SOCKET_ERROR) {
    //ZeroMemory(buff, 0);
    int bytesrecieved = recv(sock, buff, 4096, 0);
    if (bytesrecieved > 0) {
    //Echo response to console
        cout << "SERVER> " << string(buff, 0, bytesrecieved) << endl;
    }
    //}
} while (true);
//Shut down everything
closesocket(sock);
WSACleanup();
  }

【问题讨论】:

  • 在我看来,您无法为sock 写任何新内容,因为它被锁定在循环中。您可能应该从在循环中实现等待期开始。我不认为它可以在没有额外线程的情况下完成......
  • 你不应该也可以等待带有WaitForMultipleObjects 的套接字吗?然后,您可以等待任何超时为零的套接字(即,只需检查是否有要读取的内容)。或检查将套接字设置为非阻塞模式。您还必须以非阻塞方式检查用户输入。看看conio.h。你可以检查e。 G。如果有用户输入,则使用 kbhit(getline 本身也被阻塞!)。
  • 每当我包含 conio 时都会出错。

标签: c++ sockets client-server winsock2


【解决方案1】:

编辑:

你应该做一些修改:

  1. 使用 timeval 进行选择以避免阻塞选择(等到 已建立新连接或有需要阅读的内容)。
  2. 将读取/发送消息部分移出 for 循环。
  3. 在另一个线程中单独处理键输入。
  4. 使用安全队列在输入线程和通信线程(主线程)之间共享输入。

这是一个例子:

#include <iostream>
#include <WS2tcpip.h>
#include <string>
#include <sstream>

#include <thread>
#include <mutex>
#include <list>


#pragma comment (lib, "ws2_32.lib")

using namespace std;

class safe_queue {
    mutex m;
    list<string> str_queue;

public:
    safe_queue() {};
    void add(const string &s) {
        const lock_guard<mutex> lock(m);
        str_queue.push_back(s);
    }

    bool pop( string &s ) {
        const lock_guard<mutex> lock(m);
        if (!str_queue.empty()) {
            s = str_queue.front();
            str_queue.pop_front();
            return true;
        }

        return false;
    }
};



int main()
{
    // Initialze winsock
    WSADATA wsData;
    WORD ver = MAKEWORD(2, 2);

    int wsOk = WSAStartup(ver, &wsData);
    if (wsOk != 0)
    {
        cerr << "Can't Initialize winsock! Quitting" << endl;
        return 0;
    }

    // Create a socket
    SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
    if (listening == INVALID_SOCKET)
    {
        cerr << "Can't create a socket! Quitting" << endl;
        return 0;
    }

    // Bind the ip address and port to a socket
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(54000);
    hint.sin_addr.S_un.S_addr = INADDR_ANY; // Could also use inet_pton .... 

    bind(listening, (sockaddr*)&hint, sizeof(hint));

    // Tell Winsock the socket is for listening 
    listen(listening, SOMAXCONN);

    // Create the master file descriptor set and zero it
    fd_set master;
    FD_ZERO(&master);

    // Add our first socket that we're interested in interacting with; the listening socket!
    // It's important that this socket is added for our server or else we won't 'hear' incoming
    // connections 
    FD_SET(listening, &master);

    // this will be changed by the \quit command (see below, bonus not in video!)
    bool running = true;

    safe_queue sq;

    auto io_thread = thread([&] {
        string s;
        while (running && getline(std::cin, s, '\n')){
            sq.add(s);
        }
    });//thread.

    while (running)
    {
        // Make a copy of the master file descriptor set, this is SUPER important because
        // the call to select() is _DESTRUCTIVE_. The copy only contains the sockets that
        // are accepting inbound connection requests OR messages. 

        // E.g. You have a server and it's master file descriptor set contains 5 items;
        // the listening socket and four clients. When you pass this set into select(), 
        // only the sockets that are interacting with the server are returned. Let's say
        // only one client is sending a message at that time. The contents of 'copy' will
        // be one socket. You will have LOST all the other sockets.

        // SO MAKE A COPY OF THE MASTER LIST TO PASS INTO select() !!!

        fd_set copy = master;

        timeval tv = {0,0};

        // See who's talking to us
        int socketCount = select(0, &copy, nullptr, nullptr, &tv);

        for (int i = 0; i < socketCount; i++) {

            //Accept a new connection

            SOCKET sock = copy.fd_array[i];
            if (sock == listening) {
                //Accept a new connection

                SOCKET client = accept(listening, nullptr, nullptr);

                //Add a new connection

                FD_SET(client, &master);

                string mssg = "Welcome to the awesome chat server\n";
                //Send a welcome message to the connected client
                send(client, mssg.c_str(), mssg.size() + 1, 0);
            }
        }//for.


        string mssg;
        if (sq.pop(mssg) ) {

            std::cout << "Send :" << mssg << endl;

            for (u_int i = 0; i < master.fd_count; i++) {
                SOCKET outsock = master.fd_array[i];
                if (outsock != listening) {
                    send(outsock, mssg.c_str(), mssg.size() + 1, 0);
                }
            }
        }

    }//while


    // Remove the listening socket from the master file descriptor set and close it
    // to prevent anyone else trying to connect.
    FD_CLR(listening, &master);
    closesocket(listening);

    // Message to let users know what's happening.
    string msg = "Server is shutting down. Goodbye\r\n";

    while (master.fd_count > 0)
    {
        // Get the socket number
        SOCKET sock = master.fd_array[0];

        // Send the goodbye message
        send(sock, msg.c_str(), msg.size() + 1, 0);

        // Remove it from the master file list and close the socket
        FD_CLR(sock, &master);
        closesocket(sock);
    }

    // Cleanup winsock
    WSACleanup();

    system("pause");
    return 0;
}

【讨论】:

  • 那有什么解决办法呢?如何停止阻塞或如何在其中使用线程?
  • @RishabhMalhotra:这取决于您的应用程序大小,如果您只想通过控制台进行简单的聊天客户端-服务器,您可以结合使用 kbhit() 和 getch()/getline()。如果您的应用程序要包含 UI,您应该使用线程来分离通信和输入。
  • 如何使用线程制作独立客户端?我没有在项目中使用线程的经验。
猜你喜欢
  • 2015-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-02
相关资源
最近更新 更多