내가 뭘했는지 알 수 있습니다. 먼저 소켓 통신을위한 클래스를 만들고 작동하는지 확인하십시오.
잠시 전에 작성한 작업 코드가 있습니다. http://www.mediafire.com/download/6j84bedkp3s3sq5/Socket+Chat.zip
Socket.hpp :
#ifndef SOCKETS_HPP_INCLUDED
#define SOCKETS_HPP_INCLUDED
#include <Winsock2.h>
#include <Windows.h>
#include <Ws2tcpip.h>
#include <iostream>
#include <stdexcept>
#define WM_SOCKET 0x10000
class Socket
{
private:
SOCKET socket;
std::uint32_t Port;
std::string Address;
HWND WindowHandle;
bool Listen, Initialized, Asynchronous;
public:
Socket(){};
Socket(std::uint32_t Port, std::string Address, bool Listen = false, HWND WindowHandle = nullptr, bool Asynchronous = false);
~Socket();
int Recv(void* Buffer, std::uint32_t BufferLength);
int Recv(SOCKET S, void* Buffer, std::uint32_t BufferLength);
int Send(void* Buffer, std::size_t BufferSize);
int Send(SOCKET S, void* Buffer, std::size_t BufferSize);
void Connect(std::uint32_t Port, std::string Address, bool Listen, HWND WindowHandle, bool Asynchronous);
SOCKET Accept(sockaddr* ClientInfo, int* ClientInfoSize);
SOCKET GetSocket() const;
void Close();
};
#endif // SOCKETS_HPP_INCLUDED
당신은 그것을 아래 수행하는 방법을 볼 수 있습니다 .. 또한 서버와 클라이언트 모두에 대한 소스를 (Codeblocks 및 GCC/G ++ 4.8.1를 사용하여 컴파일) 업로드
Socket.cpp :
#include "Sockets.hpp"
std::string ErrorMessage(std::uint32_t Error, bool Throw = true)
{
LPTSTR lpMsgBuf = nullptr;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, Error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, nullptr);
if (Throw)
{
throw std::runtime_error(lpMsgBuf);
}
return lpMsgBuf;
}
Socket::~Socket()
{
Close();
}
void Socket::Close()
{
if (socket)
{
shutdown(socket, SD_BOTH);
closesocket(socket);
socket = 0;
}
if (Initialized)
{
WSACleanup();
}
}
SOCKET Socket::GetSocket() const {return this->socket;}
Socket::Socket(std::uint32_t Port, std::string Address, bool Listen, HWND WindowHandle, bool Asynchronous) : socket(0)
{
Connect(Port, Address, Listen, WindowHandle, Asynchronous);
}
void Socket::Connect(std::uint32_t Port, std::string Address, bool Listen, HWND WindowHandle, bool Asynchronous)
{
if (!socket)
{
this->Port = Port;
this->Address = Address;
this->Listen = Listen;
this->WindowHandle = WindowHandle;
this->Asynchronous = Asynchronous;
this->Initialized = true;
WSADATA wsaData;
struct sockaddr_in* sockaddr_ipv4;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
if ((this->socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
this->Close();
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
if (Address != "INADDR_ANY")
{
struct addrinfo *result = nullptr;
getaddrinfo(Address.c_str(), nullptr, nullptr, &result);
struct addrinfo* it;
for (it = result; it != nullptr; it = it->ai_next)
{
sockaddr_ipv4 = reinterpret_cast<sockaddr_in*>(it->ai_addr);
Address = inet_ntoa(sockaddr_ipv4->sin_addr);
if (Address != "0.0.0.0") break;
}
freeaddrinfo(result);
}
SOCKADDR_IN SockAddr;
memset(&SockAddr, 0, sizeof(SockAddr));
SockAddr.sin_port = htons(Port);
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.s_addr = (Address == "INADDR_ANY" ? htonl(INADDR_ANY) : inet_addr(Address.c_str()));
if (Listen && (bind(this->socket, reinterpret_cast<SOCKADDR*>(&SockAddr), sizeof(SockAddr)) == SOCKET_ERROR))
{
this->Close();
std::string Error = ErrorMessage(WSAGetLastError());
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
if (Asynchronous && WindowHandle)
{
if(WSAAsyncSelect(socket, WindowHandle, WM_SOCKET, FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE | FD_ACCEPT) != 0)
{
this->Close();
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
}
if (Listen && (listen(this->socket, SOMAXCONN) == SOCKET_ERROR))
{
this->Close();
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
if(!Listen && (connect(this->socket, reinterpret_cast<SOCKADDR*>(&SockAddr), sizeof(SockAddr)) == SOCKET_ERROR))
{
if(Asynchronous && WindowHandle && (WSAGetLastError() != WSAEWOULDBLOCK))
{
this->Close();
throw std::runtime_error("Error: " + ErrorMessage(WSAGetLastError()));
}
}
}
}
SOCKET Socket::Accept(sockaddr* ClientInfo, int* ClientInfoSize)
{
static int Size = sizeof(sockaddr);
return accept(this->socket, ClientInfo, (ClientInfo && ClientInfoSize ? ClientInfoSize : &Size));
}
int Socket::Recv(void* Buffer, std::uint32_t BufferLength)
{
return recv(this->socket, reinterpret_cast<char*>(Buffer), BufferLength, 0);
}
int Socket::Recv(SOCKET S, void* Buffer, std::uint32_t BufferLength)
{
return recv(S, reinterpret_cast<char*>(Buffer), BufferLength, 0);
}
int Socket::Send(void* Buffer, std::size_t BufferSize)
{
return send(this->socket, reinterpret_cast<char*>(Buffer), BufferSize, 0);
}
int Socket::Send(SOCKET S, void* Buffer, std::size_t BufferSize)
{
return send(S, reinterpret_cast<char*>(Buffer), BufferSize, 0);
}
위는 사용하는 것은 매우 쉽게 소켓 주위의 래퍼입니다.
이제 클라이언트 및 서버 창에 대해 WinAPI 기능을 둘러싼 또 다른 래퍼를 만들어 쉽게 Windows를 만들 수 있습니다!
Window.hpp :
#ifndef WINDOW_HPP_INCLUDED
#define WINDOW_HPP_INCLUDED
#include <windows.h>
#include <string>
class Window
{
private:
HWND WindowHandle;
static LRESULT __stdcall WindowProcedure(HWND Hwnd, UINT Msg, WPARAM wParam, LPARAM lParam);
public:
void Create(std::string ClassName, std::string Title, int Width = CW_USEDEFAULT, int Height = CW_USEDEFAULT, WNDPROC WindowProcedure = nullptr, WNDCLASSEX WndClass = {0});
HWND GetWindowHandle();
};
#endif // WINDOW_HPP_INCLUDED
Window.cpp :
#include "Window.hpp"
LRESULT __stdcall Window::WindowProcedure(HWND Hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(Hwnd, Msg, wParam, lParam);
}
return 0;
};
void Window::Create(std::string ClassName, std::string Title, int Width, int Height, WNDPROC WindowProcedure, WNDCLASSEX WndClass)
{
if (WindowProcedure == nullptr)
{
WindowProcedure = Window::WindowProcedure;
}
if (WndClass.cbSize == 0)
{
WndClass =
{
sizeof(WNDCLASSEX), CS_DBLCLKS, WindowProcedure,
0, 0, GetModuleHandle(nullptr), LoadIcon(nullptr, IDI_APPLICATION),
LoadCursor(nullptr, IDC_ARROW), HBRUSH(COLOR_WINDOW),
nullptr, ClassName.c_str(), LoadIcon (nullptr, IDI_APPLICATION)
};
}
if(RegisterClassEx(&WndClass))
{
this->WindowHandle = CreateWindowEx(0, ClassName.c_str(), Title.c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, Width, Height, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
if(WindowHandle)
{
MSG msg = {nullptr};
ShowWindow(WindowHandle, SW_SHOWDEFAULT);
while(GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
Protocol.hpp :
위의 헤더와 소스 파일의 모든 사용됩니다
#include "Sockets.hpp"
#include <iostream>
/**
Packet Protocol Definition. Can probably make this into an Enum later when it gets more complex.
This protocol determines how a packet is read. Is it a packet for the server? The client? What type? etc..
**/
const int PACKET_PROTOCOL_SERVER_ID = -3; //A Server packet telling all clients a global message or Sends clients a unique ID upon connect.
const int PACKET_PROTOCOL_UPDATE_ID = -2; //Sends a packet to the server telling it we want to update other clients with our new info.
const int PACKET_PROTOCOL_CLIENT_DISC = -1; //A client has disconnected, update our contacts list.
const int PACKET_PROTOCOL_CLIENT_CONN = 0; //A client has connected, update our contacts list.
//const int PACKET_PROTOCOL_ //Add other protocols such as admin-login, authenticate, etc..
//If you add more, don't forget to update the Packet struct.
/**
A structure that represents a packet to be sent over a network/socket.
**/
struct Packet
{
std::int32_t ID;
std::int32_t To;
std::int32_t From;
std::string Name;
std::string Message;
};
template <typename T>
T ReadPointer(char*& Pointer)
{
T Result = *(reinterpret_cast<T*>(Pointer));
Pointer += sizeof(T);
return Result;
}
template <typename T>
void WritePointer(char*& Pointer, const T& Value)
{
*(reinterpret_cast<T*>(Pointer)) = Value;
Pointer += sizeof(T);
}
/**
Serializes a packet into a buffer of unsigned-chars.. aka bytes. Then sends it through the socket.
**/
bool WritePacket(SOCKET s, Packet &packet)
{
if (s)
{
std::vector<char> Buffer((sizeof(int32_t) * 3) + sizeof(packet.Name.size()) + packet.Name.size() + sizeof(packet.Message.size()) + packet.Message.size(), 0);
char* Ptr = Buffer.data();
WritePointer(Ptr, packet.ID);
WritePointer(Ptr, packet.To);
WritePointer(Ptr, packet.From);
WritePointer(Ptr, packet.Name.size());
for (auto it = packet.Name.begin(); it != packet.Name.end(); ++it)
WritePointer(Ptr, *it);
WritePointer(Ptr, packet.Message.size());
for (auto it = packet.Message.begin(); it != packet.Message.end(); ++it)
WritePointer(Ptr, *it);
send(s, Buffer.data(), Buffer.size(), 0);
return true;
}
return false;
}
/**
Deserializes a buffer of unsigned-chars.. aka bytes back into a packet.
**/
bool ReadPacket(SOCKET s, Packet &packet)
{
if (s)
{
packet.Name.clear();
recv(s, reinterpret_cast<char*>(&packet.ID), sizeof(packet.ID), 0);
recv(s, reinterpret_cast<char*>(&packet.To), sizeof(packet.To), 0);
recv(s, reinterpret_cast<char*>(&packet.From), sizeof(packet.From), 0);
decltype(packet.Name.size()) Size = 0;
recv(s, reinterpret_cast<char*>(&Size), sizeof(Size), 0);
std::vector<char> Buffer(Size, 0);
recv(s, Buffer.data(), Buffer.size(), 0);
packet.Name.append(Buffer.begin(), Buffer.end());
Buffer.clear();
Size = 0;
recv(s, reinterpret_cast<char*>(&Size), sizeof(Size), 0);
recv(s, Buffer.data(), Buffer.size(), 0);
packet.Message.append(Buffer.begin(), Buffer.end());
return true;
}
return false;
}
BOTH 클라이언트와 서버. 프로토콜은 클라이언트와 서버 간의 링크입니다. 그것은 패킷과 그것을 읽고 쓰는 방법을 설명했다. 그것은 서버와 클라이언트가 앞뒤로 통신하는 방법입니다! 시작에
: 위에서 볼 수 있듯이, 서버가 먼저 실행해야합니다 내가했던 서버를 들어
. 그것은 localhost를 수신하고 그것을 클라이언트 연결에 포트 27015.
에서 수신됩니다 연결하는 클라이언트시 , 클라이언트는 고유 한 ID를 발급하고 서버의 목록에 추가됩니다. 이 문제는 FD_ACCEPT에서 확인할 수 있습니다.메시지에
을받은 : 클라이언트가 메시지를 보내는 경우 다음, FD_READ가 트리거되고 서버는 클라이언트에서 보낸 패킷을 읽기 시작한다. 업데이트 패킷 인 경우 패킷은 서버의 다른 모든 클라이언트로 보내 지므로 다른 클라이언트가 패킷을 보낸 클라이언트에 대한 정보를 업데이트 할 수 있습니다. 우리는 또한 서버에 저장된 클라이언트 정보를 업데이트합니다. 서버 패킷 인 경우 서버 패킷을 소비하고 클라이언트에게 다시 보냅니다. 클라이언트 연결 끊기에
: 분리시 , 일명 FD_CLOSE는, 서버가 "일부 클라이언트가"연결이 끊어진 것을을 알리는 다른 모든 클라이언트에 차단 패킷을 보내는 것을 볼 수 있습니다. 그런 다음 목록에서 클라이언트를 제거하고 클라이언트의 소켓을 닫습니다.
서버의 MAIN.CPP 시작에
: 내가했던 클라이언트를 들어
#include "Sockets.hpp"
#include "Window.hpp"
#include "Protocol.hpp"
#include <vector>
#include <map>
/**
Packet Protocol Definition. Can probably make this into an Enum later when it gets more complex.
This protocol determines how a packet is read. Is it a packet for the server? The client? What type? etc..
**/
Socket* sock = nullptr;
bool SocketConnected = false;
/**
Stores information about each client that connects.
**/
std::vector<std::tuple<int, SOCKET, Packet>> Clients;
auto FindClient(int ID) -> decltype(Clients.begin())
{
for (auto it = Clients.begin(); it != Clients.end(); ++it)
{
if (std::get<0>(*it) == ID)
return it;
}
return Clients.end();
}
auto FindClient(SOCKET socket) -> decltype(Clients.begin())
{
for (auto it = Clients.begin(); it != Clients.end(); ++it)
{
if (std::get<1>(*it) == socket)
return it;
}
return Clients.end();
}
void SendAll(Packet &packet)
{
for (auto it = Clients.begin(); it != Clients.end(); ++it)
{
if (std::get<0>(*it) != packet.From)
{
packet.To = std::get<0>(*it);
WritePacket(std::get<1>(*it), packet);
}
}
}
LRESULT __stdcall WindowProcedure(HWND Hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
{
sock = new Socket(27015, "INADDR_ANY", true, Hwnd, true);
}
break;
case WM_SOCKET: /** We received a socket event **/
{
switch(WSAGETSELECTEVENT(lParam))
{
case FD_WRITE:
{
SocketConnected = true;
}
break;
case FD_READ: /** We have received a packet from the client. Read the ID and interpret the packet information. **/
{
Packet P;
ReadPacket(reinterpret_cast<SOCKET>(wParam), P);
if (P.ID == PACKET_PROTOCOL_UPDATE_ID)
{
auto it = FindClient(P.From);
if (it != Clients.end())
{
Packet* Client = &std::get<2>(*it);
Client->Name = P.Name;
for (auto it = Clients.begin(); it != Clients.end(); ++it)
{
P.ID = PACKET_PROTOCOL_UPDATE_ID;
P.From = std::get<0>(*it);
SendAll(P);
}
}
}
else if (P.ID == PACKET_PROTOCOL_SERVER_ID)
{
auto it = FindClient(P.To);
if (it != Clients.end())
{
WritePacket(std::get<1>(*it), P);
}
}
SocketConnected = true;
}
break;
case FD_ACCEPT: //A client wants to connect. We accept them and store them in our list.
{
int ClientID = 1;
while(FindClient(ClientID) != Clients.end())
{
++ClientID;
}
Packet Client;
sockaddr_in ClientAddressInfo = {0};
Clients.push_back(std::make_tuple(ClientID, sock->Accept(reinterpret_cast<sockaddr*>(&ClientAddressInfo), nullptr), Client));
Packet PacketInfo;
PacketInfo.ID = PACKET_PROTOCOL_SERVER_ID;
PacketInfo.To = ClientID;
SocketConnected = true;
WritePacket(std::get<1>(Clients.back()), PacketInfo);
}
break;
case FD_CLOSE: //A client has disconnected. Notify all other clients and remove the client from our list.
{
auto it = FindClient(reinterpret_cast<SOCKET>(wParam));
if (it != Clients.end())
{
Packet PacketInfo;
PacketInfo.ID = PACKET_PROTOCOL_CLIENT_DISC;
PacketInfo.From = std::get<0>(*it);
SendAll(PacketInfo);
Clients.erase(it);
}
}
break;
default:
break;
}
break;
}
case WM_DESTROY:
{
sock->Close();
delete sock;
WSACleanup();
PostQuitMessage(0);
}
return 0;
default:
return DefWindowProc(Hwnd, Msg, wParam, lParam);
}
return 0;
}
int main()
{
Window().Create("Server", "Server", 200, 100, WindowProcedure, {0});
}
창을 만들 때, 그것은 시도 서버에 연결 .
연결시 : 서버에 연결하면 FD_WRITE 메시지가 수신됩니다. FD_WRITE 스위치의 경우 기본 패킷을 구성하고 서버에 보내서 이름, 정보 등을 알립니다. 서버는 UPDATE 패킷을 받고 정보를 업데이트 할뿐만 아니라 다른 모든 클라이언트에게 있다.
수신 수신 : 메시지를 받으면 FD_READ가 트리거됩니다. 서버에서받은 패킷을 읽고 원하는대로 해석해야합니다. 현재 서버에서받은 첫 번째 패킷은 우리에게 발행 된 고유 ID이며 연결된 모든 연락처 목록입니다.
또한 패킷이 서버 패킷이 아닌 경우 패킷을 읽고 수신 상자에 텍스트를 추가해야합니다. 그러면 서버의 다른 연락처 또는 클라이언트가 수신 한 모든 메시지가 표시됩니다. 분리에
: 해제 메시지시는 (FD_CLOSE) 수신 제가 연결을 다시 시도하는 대신 소켓을 종료하기로 결정했다. 그것만큼이나 간단합니다. 원한다면 delete sock; sock = new....
으로 다시 연결할 수 있습니다.
고객의 MAIN.CPP :
#include "Sockets.hpp"
#include "Window.hpp"
#include "Protocol.hpp"
#include <vector>
/**
Global variables:
Socket
ClientID
Handles for controls
IDs for controls
**/
int ClientID = -1;
int ReceiverID = -1;
std::string ClientName = "Client";
Socket* sock = nullptr;
bool SocketConnected = false;
HWND SendBox, ReceiveBox, SendButton;
enum {SENDBOX_ID, RECEIVEBOX_ID, SENDBUTTON_ID};
LRESULT __stdcall WindowProcedure(HWND Hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
{
ReceiveBox = CreateWindowEx(WS_EX_STATICEDGE, "Edit", nullptr, WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, 10, 10, 465, 275, Hwnd, (HMENU)RECEIVEBOX_ID, nullptr, nullptr);
SendBox = CreateWindowEx(WS_EX_STATICEDGE, "Edit", nullptr, WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL, 10, 315, 465, 110, Hwnd, (HMENU)SENDBOX_ID, nullptr, nullptr);
SendButton = CreateWindowEx(0, "Button", "Send", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 385, 430, 90, 25, Hwnd, (HMENU)SENDBUTTON_ID, nullptr, nullptr);
sock = new Socket(27015, "localhost", false, Hwnd, true);
}
break;
case WM_COMMAND: /** We received an event from a button/control **/
{
switch(LOWORD(wParam))
{
case SENDBUTTON_ID: //The send button was pressed so we want to construct a packet from the sendbox's contents and send it to the server.
{
Packet P;
std::vector<std::uint8_t> Buffer(GetWindowTextLength(SendBox) + 1);
GetWindowText(SendBox, reinterpret_cast<char*>(Buffer.data()), Buffer.size());
P.Message.append(Buffer.begin(), Buffer.end());
if (!P.Message.empty())
{
P.ID = ReceiverID; //The packet is NOT meant for the server. It is meant for the client.
P.To = ReceiverID; //We will be sending the packet to some other client.
P.From = ClientID; //The packet is from this client.
P.Name = ClientName; //Our name..
WritePacket(sock->GetSocket(), P);
}
}
break;
case RECEIVEBOX_ID:
{
if (HIWORD(wParam) == EN_SETFOCUS)
{
HideCaret(ReceiveBox);
}
else if (HIWORD(wParam) == EN_KILLFOCUS)
{
ShowCaret(ReceiveBox);
}
}
break;
}
}
break;
case WM_SOCKET: /** We received a socket event **/
{
switch(WSAGETSELECTEVENT(lParam))
{
case FD_WRITE: /** We connected to the server successfully so we need to send an initialization packet. **/
{
SocketConnected = true;
Packet P;
P.ID = PACKET_PROTOCOL_UPDATE_ID;
P.To = PACKET_PROTOCOL_SERVER_ID;
P.From = -1; /** The server will send us a unique Identifier. **/
P.Name = "ICantChooseUsernames";
P.Message = "Hello";
WritePacket(sock->GetSocket(), P);
}
break;
case FD_READ: /** We have received a packet from the server. Read the ID and interpret the packet information. **/
{
Packet P;
ReadPacket(sock->GetSocket(), P);
if (P.ID == PACKET_PROTOCOL_SERVER_ID) //If the packet is a server packet, then it is sending us our Unique client ID.
{
ClientID = P.To;
SetWindowText(Hwnd, (ClientName + ": " + std::to_string(P.ID)).c_str()); //Set the window title to "OurName: " + OurID.
}
else if (P.ID == PACKET_PROTOCOL_CLIENT_DISC)
{
//Delete the specified contact from our contacts list..
}
else if (P.ID == PACKET_PROTOCOL_CLIENT_CONN)
{
//Add the client to the contacts list..
}
else //Else print the packet's message in the received box..
{
std::string Sender = P.Name;
std::string Message = P.Message;
int ReceiveBoxLength = GetWindowTextLength(ReceiveBox);
Message = ReceiveBoxLength == 0 ? Sender + ": " + Message : "\r\n\r\n" + Sender + ": " + Message;
SendMessage(ReceiveBox, EM_SETSEL, -1, -1);
SendMessage(ReceiveBox, EM_REPLACESEL, 0, reinterpret_cast<LPARAM>(Message.c_str()));
}
}
break;
case FD_CLOSE:
{
sock->Close();
}
break;
default:
break;
}
break;
}
case WM_DESTROY:
{
sock->Close();
delete sock;
WSACleanup();
PostQuitMessage(0);
}
return 0;
default:
return DefWindowProc(Hwnd, Msg, wParam, lParam);
}
return 0;
}
int main()
{
Window().Create("Client", "Client", 500, 500, WindowProcedure, {0});
}
당신은 당신의 특정 문제, 예상 입력과 출력을 지정해야합니다. _ '일하지 않아 ... 괜찮지 않아 .'_ 약간의 예비 정보가 있니?' –
텍스트 버퍼의 데이터 유형을 표시하십시오. –
IP, 로그인, 암호의 세 가지 필드가 있습니다. 입력란을 채운 후에는 단 한 번의 클릭으로 채팅에 클라이언트를 추가하려면 어떻게합니까? ScottMcP-MVP 구조 사용자에게 : {char ip [30]; char login [16]; char password [16]} – user2849744