TCP client server dengan winsock2
Untuk keperluan selanjutnya di pengembangan game yang sedang saya buat ( :-o ), saya memerlukan program yang bisa di tweak lewat program lain. Dengan kata lain 2 program yang saling bisa berkomunikasi. Cara yang bisa dicapai , pakai file sharing atau dengan cara yang pas : pakai networking tcp / udp . Kalau mau buat program dengan kemampuan networking, cara yang bijaksana mungkin pakai 3rd party library yang sudah cukup populer dan reliable. Di antaranya, yang saya ketahui dan sepertinya sudah terbukti bagus : boost-asio ( free ), raknet ( free untuk hobyist ), libevent ( free ), etc. ada yang lain ?. Tapi saya pinginnya ndak usah pakai 3rd party lib, lagipula program yang akan saya buat cukup sederhana. winsock2 & c/c++ . itu pilihannya. dan rasanya akan fun juga buat program nya hehe. Sebagai pemanasan awal dengan winsock2 ( saya pemula ) , saya perlu membuat program seperti ini : ada client dan server. client bisa kirim data ke server. server bisa kirim data ke client. jika client nya lebih dari satu, client bisa kirim data ke client yang lain. representasi dari program ini adalah program “chatting” :d . kemampuan server dalam keadaan running :
- stand by & listening kalau ada client mau membuat koneksi.
- setiap ada client tersambung, server akan membuat handler yang menangani koneksi ke client tersebut berdasarkan socket id. ( dalam test program dipost ini, setiap ada client, server membuat thread baru, untuk menanganinya ).
- server bisa kirim data ke client lewat console
- ada > 3 thread . 1 main thread: untuk menangkap command line dari user, 1 thread untuk “stand by” / connection acceptor, dan n thread , untuk n client yang tersambung. ya sebenarnya ndak benar ( bisa di optimize pakai thread pool mungkin nantinya ).
kemampuan client dalam keadaan running :
- connect ke server
- kirim data ke server
- ada 2 thread, 1 main thread, 1 client handler thread ( data receiver & sender )
- port hardcoded : 22536
catatan : [1] saya membuat socket mode ke non-blocking , artinya pemanggilan fungsi2 winsock seperti connect, recv, send tidak akan mem-blok jalannya program loop. jadi non-blocking & di thread lain selain main thread. u_long iMode = 1; // iMode=0, blocking mode, default value ioctlsocket(ConnectSocket, FIONBIO, &iMode); [2] untuk sharing data antar thread, saya menggunakan concurrent queue dari blog Anthony William http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html yang saya modifikasi menggunakan std library ( awalnya pakai boost ) [3] data yang dikirim berupa string.
Beberapa pertanyaan :
- apa cara di catatan no [1] ( non-blocking & di thread lain ) efektif ?
- saat memakai non-blocking mode, kalau mengirim data byte yang sangat besaarr , apakah semuanya terkirim langsung ATAU setelah beberapa call sent?
Reference : -MSDN winsock2 ( belum semuanya saya baca :| )
berikut code program chat client & server yang saya buat :
(server code) : ~288 baris
[sourcecode language=“cpp”] // xedi xermawan < edi.ermawan@gmail.com > // May 27, 2014 // tcp client server networking test using winsock2 // server code
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include
#include “concurrent_queue.hpp”
#pragma comment (lib, “ws2_32.lib”)
#define DEFAULT_BUFLEN 512 #define DEFAULT_PORT “22536”
struct XDclient_info { int socket_id; struct sockaddr_in address; };
class XDclient { public: XDclient(XDclient_info& clientinfo) { m_clientinfo = clientinfo; m_recvbuflen = DEFAULT_BUFLEN; m_islive = true; m_thread = new std::thread(&XDclient::run, this); } bool isLive() { return m_islive; } XDclient_info getinfo() const { return m_clientinfo; } ~XDclient() { m_thread->join(); delete m_thread; } std::string get_data() { std::string ret_str = “”; if (!m_queue.empty()) { m_queue.try_pop(ret_str); } return ret_str; } private: XDclient_info m_clientinfo; std::thread* m_thread; char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_islive; concurrent_queue std::string m_queue;
void run()
{
while (m_islive)
{
unsigned int result_recv = recv( m_clientinfo.socket_id, m_recvbuf, m_recvbuflen, 0);
int nError = WSAGetLastError();
if (nError != WSAEWOULDBLOCK&&nError != 0)
{
printf("Client Error: %d\n", nError);
printf("Client disconnected: %d\n", nError);
m_islive = false;
}
else if (result_recv != INVALID_SOCKET && result_recv!= 0)
{
m_recvbuf[result_recv] = '\0';
std::string str_send(m_recvbuf);
m_queue.push(str_send);
}
else if (result_recv == 0)
{
printf("Client disconnected: %d\n", nError);
m_islive = false;
}
// give a sleep, avoid high percentage cpu usage
Sleep(50);
}
}
};
class XDserver { public: XDserver() { m_recvbuflen = DEFAULT_BUFLEN; m_isrun = true; m_thread = new std::thread(&XDserver::run, this); } ~XDserver() { m_isrun = false; m_thread->join(); delete m_thread; } void run() { WSADATA wsaData; int result_ ;
SOCKET listen_socket = INVALID_SOCKET;
SOCKET client_socket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// init
result_ = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result_ != 0) {
printf("WSAStartup failed with error: %d\n", result_);
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// get info address for host
result_ = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (result_ != 0) {
printf("getaddrinfo failed with error: %d\n", result_);
WSACleanup();
}
// create socket
listen_socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (listen_socket == INVALID_SOCKET) {
printf("socket failed. error code : %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
}
// set-up socket listening
result_ = bind(listen_socket, result->ai_addr, (int)result->ai_addrlen);
if (result_ == SOCKET_ERROR) {
printf("bind failed. error code: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(listen_socket);
WSACleanup();
m_isrun = false;
}
if (!m_isrun)
return;
freeaddrinfo(result);
result_ = listen(listen_socket, SOMAXCONN);
if (result_ == SOCKET_ERROR) {
printf("listen failed. error code: %d\n", WSAGetLastError());
closesocket(listen_socket);
WSACleanup();
}
// set to non-blocking mode
u_long iMode = 1;
ioctlsocket(listen_socket, FIONBIO, &iMode);
int t_clientAddressLen = sizeof(struct sockaddr_in);
struct sockaddr_in t_clientAddress;
// server loop
while (m_isrun)
{
// check new client connected
client_socket = accept(listen_socket, (struct sockaddr *)&t_clientAddress, &t_clientAddressLen);
int nError = WSAGetLastError();
if (nError != WSAEWOULDBLOCK&&nError != 0)
{
// todo :
}
else if (client_socket != INVALID_SOCKET)
{
XDclient_info clientinfo;
clientinfo.address = t_clientAddress;
clientinfo.socket_id = client_socket;
XDclient* clientnew = new XDclient(clientinfo);
m_clients.push_back(clientnew);
if (client_socket == INVALID_SOCKET) {
printf("accept failed. error code: %d\n", WSAGetLastError());
}
}
// check client live
for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
{
if (!(*it)->isLive())
{
delete *it;
m_clients.erase(it);
break;
}
}
// check if there are data need to be sent
if (!m_queue.empty())
{
std::string str = "";
m_queue.try_pop(str );
std::cout << " send data: " << str.c_str() <<std::endl;
for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
{
int m_recvbuflen;
int sendresult = send((*it)->getinfo().socket_id, str.c_str(), str.length(), 0);
if (sendresult == SOCKET_ERROR)
{
std::cout << " send failed. error code " << sendresult << std::endl;
}
}
}
for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
{
std::string str_data = (*it)->get_data();
if (str_data != "")
{
for (std::vector< XDclient* >::iterator it2 = m_clients.begin(); it2 != m_clients.end(); it2++)
{
if (*it != *it2)
{
int sendresult = send((*it2)->getinfo().socket_id, str_data.c_str(), str_data.length(), 0);
if (sendresult == SOCKET_ERROR)
{
std::cout << " send failed. error code " << sendresult << std::endl;
}
}
}
}
}
// give a sleep, avoid high percentage cpu usage
Sleep(50);
}
}
void senddata(std::string& str)
{
m_queue.push(str);
}
bool isLive()
{
return m_isrun;
}
private: char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_isrun; std::thread* m_thread; std::vector< XDclient* > m_clients; concurrent_queue std::string m_queue; };
class Games { };
int __cdecl main(int argc, char** argv) { XDserver* server = new XDserver(); std::cout « “welcome to the server”«std::endl; std::cout « “type an input to broadcast to all client. ““exit”” to close program " « std::endl; while (server->isLive()) { char str_in[512]=”"; std::cin » str_in; std::string str_input(str_in); server->senddata(“server|"+str_input); if (!str_input.compare(“exit”)) { break; } } delete server; std::cout « “server shutdown” « std::endl; system(“pause”); return 0; } [/sourcecode]
(client code) : ~181 baris
[sourcecode language=“cpp”] // xedi xermawan < edi.ermawan@gmail.com > // May 27, 2014 // tcp client server networking test using winsock2 // client code
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include
#include “concurrent_queue.hpp”
// link to winsock2 static lib #pragma comment (lib, “ws2_32.lib”)
#define DEFAULT_BUFLEN 512 #define DEFAULT_PORT “22536”
class XDclientapp { public: XDclientapp(std::string& clientname, std::string& serveraddresss) { m_server_address = serveraddresss; m_client_name = clientname; m_recvbuflen = DEFAULT_BUFLEN; m_isrun = true; m_thread = new std::thread(&XDclientapp::run, this); } ~XDclientapp() { m_isrun = false; m_thread->join(); delete m_thread; } void run() { WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo *result = NULL, *ptr = NULL, hints; char *sendbuf = “this is a test”; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo( m_server_address.c_str(), DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
}
// Attempt to connect to an address until one succeeds
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
}
// Connect to server.
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
// set to non-blocking mode
u_long iMode = 1;
ioctlsocket(ConnectSocket, FIONBIO, &iMode);
freeaddrinfo(result);
int errorid;
while (1)
{
unsigned int result_recv = recv(ConnectSocket, recvbuf, recvbuflen, 0);
errorid = WSAGetLastError();
if (errorid != WSAEWOULDBLOCK && errorid != 0)
{
printf("Client disconnected: %d\n", errorid);
break;
}
else if (result_recv != INVALID_SOCKET && result_recv != 0)
{
// printf("Bytes received: %d\n", result_recv);
recvbuf[result_recv] = '\0';
printf("\n%s\n", recvbuf);
std::cout << m_client_name.c_str() << "(You) : ";
}
else if (result_recv == 0)
{
printf("Client disconnected: %d\n", errorid);
break;
}
// check if there are data need to be sent
if (!m_queue.empty())
{
std::string str = "";
m_queue.try_pop(str);
int m_recvbuflen;
int sendresult = send( ConnectSocket , str.c_str(), str.length(), 0);
if (sendresult == SOCKET_ERROR)
{
std::cout << " send failed. error code " << sendresult << std::endl;
}
}
// give a sleep, avoid high percentage cpu usage
Sleep(50);
}
}
void senddata(std::string& str)
{
std::string pstr = m_client_name + " : " + str;
m_queue.push(pstr);
}
private: char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_isrun; std::thread* m_thread; concurrent_queue std::string m_queue; std::string m_client_name; std::string m_server_address; }; //– int __cdecl main(int argc,char** argv) { std::string clientname = “”; std::string serveraddress = “localhost”; std::cout « “Please enter your name: “; std::getline(std::cin, clientname);
if (argc > 1)
{
serveraddress = argv[1];
}
XDclientapp* client = new XDclientapp(clientname, serveraddress);
std::cout << "welcome to the chat client" << std::endl;
std::cout << "type an input to broadcast to all client. ""exit"" to close program " << std::endl;
std::cout << "---" << std::endl;
while (1)
{
std::string str_input = "";
std::cout << clientname.c_str() << "(You) : ";
std::getline(std::cin, str_input);
client->senddata(str_input);
if (!str_input.compare("exit"))
{
break;
}
}
delete client;
return 0;
} [/sourcecode]
Screenshoot : Download : source + vs project + binary ( hanya 39 kb ! ) .