Я вижу ряд проблем в вашем коде
Одна из самых больших проблем - вы неправильно читаете / отправляете std::string
объекты. Вы не можете использовать такие вещи, как
send(..., &string, sizeof(string), ...)
и
recv(..., &string, sizeof(string), ...)
на std::string
объектах.
Для send()
вам нужно
send(..., string.c_str(), string.length(), ...)
Для recv()
это немного сложнее. Вам нужно что-то вроде:
size = ...;
string.resize(size);
recv(..., &string[0], size, ...);
, что означает знание того, что size
отправляется, прежде чем вы прочитаете данные. Таким образом, вам нужно отправить фактическую строку length
перед отправкой ее данных.
Вам необходимо определить протокол , то есть набор правил, которые точно описывают, как клиент и сервер должны взаимодействовать друг с другом, как они должны форматировать данные, возвращаясь назад и вперед, и т. Д.
Другие проблемы, которые я вижу в вашем коде:
неправильно передает информацию о клиенте pthread_create()
.
неправильная разблокировка мьютекса при обработке таблицы лидеров после победы в игре.
вы явно используете C ++ 11 или более позднюю версию (из-за использования std::to_string()
), но вы не используете какие-либо функции C ++ 11, такие как std::thread
и std:mutex
вместо pthread_create
и p_thread_mutex
, RAII для очистки, auto
, циклические for
петли, <random>
и т. Д.
Сказав это, попробуйте что-то вроде этого:
MY_COMMON_CODE.H:
#ifndef MY_COMMON_CODE_H
#define MY_COMMON_CODE_H
#include <string>
#include <cstdint>
#include <memory>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
void throwRuntimeError(const char *msg, int err);
void throwRuntimeError(const char *msg);
void sendRaw(int sock, const void *data, size_t size);
void readRaw(int sock, void *data, size_t size);
void sendChar(int sock, char value);
char readChar(int sock);
void sendBool(int sock, bool value);
bool readBool(int sock);
void sendInt32(int sock, int32_t value);
int32_t readInt32(int sock);
void sendString(int sock, const std::string &value);
std::string readString(int sock);
struct socket_deleter {
typedef int pointer;
void operator()(int sock) const { if (sock != -1) close(sock); }
};
using socket_ptr = std::unique_ptr<int, socket_deleter>;
#endif
MY_COMMON_CODE.CPP:
#include <sstream>
#include <stdexcept>
#include "my_common_code.h"
void throwRuntimeError(const char *msg, int err)
{
std::ostringstream oss;
if (msg && *msg) oss << msg << ": ";
oss << strerror(err);
throw std::runtime_error(oss.str());
}
void throwRuntimeError(const char *msg)
{
std::ostringstream oss;
if (msg && *msg) oss << msg;
throw std::runtime_error(oss.str());
}
void sendRaw(int sock, const void *data, size_t size)
{
const char *pdata = static_cast<const char *>(data);
while (size > 0) {
ssize_t sent = send(sock, pdata, size, 0);
if (sent < 0)
throwRuntimeError("send", errno);
pdata += sent;
size -= sent;
}
}
void readRaw(int sock, void *data, size_t size)
{
char *pdata = static_cast<char *>(data);
while (size > 0) {
ssize_t recvd = recv(sock, pdata, size, 0);
if (recvd < 0)
throwRuntimeError("recv", errno);
if (recvd == 0)
throwRuntimeError("disconnected");
pdata += sent;
size -= sent;
}
return true;
}
void sendChar(int sock, char value)
{
sendRaw(sock, &value, sizeof(value));
}
char readChar(int sock)
{
char value;
readRaw(sock, &value, sizeof(value));
return value;
}
void sendBool(int sock, bool value)
{
sendRaw(sock, &value, sizeof(value));
}
bool readBool(int sock)
{
bool value;
readRaw(sock, &value, sizeof(value));
return value;
}
void sendInt32(int sock, int32_t value)
{
value = htonl(value);
sendRaw(sock, &value, sizeof(value));
}
int32_t readInt32(int sock)
{
int32_t value;
readRaw(sock, &value, sizeof(value));
return ntohl(value);
}
void sendString(int sock, const std::string &value)
{
int32_t size = value.length();
sendInt32(sock, size);
sendRaw(sock, s.c_str(), size);
}
std::string readString(int sock)
{
std::string value;
int32_t size = readInt32(sock);
if (size > 0) {
value.resize(size);
readRaw(sock, &value[0], size);
}
return value;
}
КОД КЛИЕНТА:
#include <iostream>
#include <limits>
#include <cstring>
#include <cstdlib>
#include "my_common_code.h"
int main(int argc, char *argv[]) {
try {
// check for correct number of arguments
if (argc != 3)
throwRuntimeError("Invalid argument count");
// store arguments
char *ipAddress = argv[1];
char *port = argv[2];
// check if port number argument valid
int portNumber = std::atoi(port);
if ((portNumber <= 0) || (portNumber > 65535))
throwRuntimeError("Invalid port number");
sockaddr_in address = {};
address.sin_family = AF_INET;
address.sin_port = htons(static_cast<uint16_t>(portNumber));
// convert address
if (inet_pton(AF_INET, ipAddress, &address.sin_addr) != 1)
throwRuntimeError("Invalid IP address");
//////////////////////// CREATE CLIENT SOCKET //////////////////////////////
socket_ptr clientSocketPtr( socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) );
int clientSocket = clientSocketPtr.get();
if (clientSocket == -1)
throwRuntimeError("socket", errno);
/////////////////////// CONNECT ////////////////////////////////////////////
if (connect(clientSocket, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0)
throwRuntimeError("connect", errno);
///////////////////////////// start game ///////////////////////////////////
std::cout << "\nWelcome to hangman game! \n";
std::cout << "Please, Enter your name: ";
std::string player;
std::getline(std::cin, player);
std::cout << std::endl;
// send the name to the server
sendString(clientSocket, player);
char guess;
bool correct;
int numGuess = 0;
int32_t numCorrect = 0;
std::string dashed;
do {
++numGuess;
dashed = readString(clientSocket);
std::cout << "Turn: " << numGuess << std::endl;
std::cout << "Word: " << dashed << std::endl;
std::cout << std::endl;
// ask the user for a guess
std::cout << "Guess a letter: " << std::endl;
if (!(std::cin >> guess))
throwRuntimeError("Input error");
// send the guess to the server
sendChar(clientSocket, guess);
// receive the guess response from the server
correct = readBool(clientSocket);
numCorrect = readInt32(clientSocket);
if (correct)
std::cout << "Correct guess!" << std::endl;
else
std::cout << "Incorrect guess!" << std::endl;
std::cout << std::endl;
}
while (numCorrect < dashed.length());
std::cout << "Congratulations! You guessed the word " << dashed << "!\n";
std::cout << "It took " << numGuess << " turn(s) to guess the word correctly." << std::endl;
std::cout << std::endl;
std::string leaderBoard = readString(clientSocket);
std::cout << "LEADER BOARD" << std::endl;
std::cout << leaderBoard << std::endl;
}
catch (const std::exception &ex) {
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
КОД СЕРВЕРА:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <thread>
#include <mutex>
#include <random>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include "my_common_code.h"
const std::string PATHNAME = "words.txt";
struct leaderBoard {
float score;
std::string playerName;
};
std::vector<std::string> words;
std::vector<leaderBoard> lb;
std::mutex mutex1;
std::string getRandomWord();
void handleClientFunction(socket_ptr clientSocketPtr);
std::string addToLeaderBoard(const std::string &playerName, float score);
int main(int argc, char *argv[]) {
try {
// check for correct number of arguments
if (argc != 2)
throwRuntimeError("Invalid argument count");
// store the passed argument
char *port = argv[1];
// check if the argument contains all digits
// convert the passed argument to ints
int portNumber = std::atoi(port);
if ((portNumber < 0) || (portNumber > 65535))
throwRuntimeError("Invalid port number");
std::ifstream inFile(PATHNAME);
std::string line;
while (inFile >> line) {
words.push_back(line);
}
inFile.close();
if (words.empty())
throwRuntimeError("No words read from file");
int listeningSocket;
int clientSocket;
socklen_t addressLength;
sockaddr_in serverAddress;
sockaddr_in clientAddress;
int opt = 1;
//////////////////////////////// Socket creation //////////////////////////////////////////////////////////////////
socket_ptr listeningSocketPtr( socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) );
listeningSocket = listeningSocketPtr.get();
if (listeningSocket == -1)
throwRuntimeError("socket", errno);
if (setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
throwRuntimeError("setsockopt", errno);
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(portNumber);
////////////////////////////// Socket binding /////////////////////////////////////////////////////////////////////
if (::bind(listeningSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) < 0)
throwRuntimeError("bind", errno);
if (portNumber == 0) {
addressLength = sizeof(serverAddress);
if (getsockname(listeningSocket, reinterpret_cast<sockaddr*>(&serverAddress), &addressLength) < 0)
throwRuntimeError("getsockname", errno);
}
////////////////////////////////// listening //////////////////////////////////////////////////////////////////////
if (listen(listeningSocket, 5) < 0)
throwRuntimeError("listen", errno);
///////////////////////////// start game ///////////////////////////////////
std::cout << "\nWelcome to hangman game server! \n";
std::cout << "\nListening on port: " << ntohs(serverAddress.sin_port) << " \n";
std::cout << std::endl;
do {
///////////////////////////////// accepting /////////////////////////////////////////////////////////////////////
addressLength = sizeof(clientAddress);
socket_ptr clientSocketPtr( accept(listeningSocket, reinterpret_cast<sockaddr*>(&clientAddress), &addressLength) );
if (clientSocketPtr.get() == -1)
throwRuntimeError("accept", errno);
std::thread t(handleClientFunction, std::move(clientSocketPtr));
t.detach();
}
while (true);
}
catch (const std::exception &ex) {
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
std::string getRandomWord()
{
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<int> dist(0, words.size()-1);
return words[dist(gen)];
}
void handleClientFunction(socket_ptr clientSocketPtr) {
int clientSocket = clientSocketPtr.get();
try {
// receive player name from client
std::string playerName = readString(clientSocket);
// chose a word at random for the client to process
std::string word = getRandomWord();
// print the word to the screen
std::cout << "Word to guess is: " << word << std::endl;
// dashed representation of the word
std::string dashed(word.length(), '_');
char guess;
bool correct;
int32_t countCorrect = 0;
int numGuess = 0;
do {
// send dashed word for client to display
sendString(clientSocket, dashed);
// receive a guess from the client and check if it is valid
guess = readChar(clientSocket);
correct = false;
++numGuess;
// check if the word contains the guessed letter
auto idx = word.find(guess);
while (idx != std::string::npos) {
if (dashed[idx] != guess) {
correct = true;
dashed[idx] = guess;
++countCorrect;
}
idx = word.find(guess, idx+1);
}
// send the guess response from the server
sendBool(clientSocket, correct);
sendInt32(clientSocket, countCorrect);
}
while (countCorrect < word.length());
float score = std::round((static_cast<float>(numGuess) / static_cast<float>(word.length())) * 100) / 100;
std::string lBoardString = addToLeaderBoard(playerName, score);
sendString(clientSocket, lBoardString);
}
catch (const std::exception &ex) {
std::cerr << ex.what() << std::endl;
}
}
std::string addToLeaderBoard(const std::string &playerName, float score) {
std::lock_guard g(mutex1);
lb.emplace_back({score, playerName});
std::sort(lb.begin(), lb.end(),
[](const leaderBoard &lhs, const leaderBoard &rhs) {
return lhs.score < rhs.score;
}
);
std::ostringstream oss;
for (auto &p : lb) {
std::cout << p.playerName << std::endl;
std::cout << p.score << std::endl;
oss << "Name: " << p.playerName << ", " << "Score: " << p.score << "\n";
}
return oss.str();
}