+7(960) 250-82-68 sergey@mirossa.ru


button
button
button
button
button


 
Статьи
 

Простейший сервер. Консольное приложение.

Создаём сокет. Привязываемся к порту 30000. Начинаем его прослушивать. При обращении клиента функция accept возвращает второй сокет, через который будем общаться с клиентом. А через первый сокет будем ждать других клиентов. Дочерний процесс, который будет дальше общаться с клиентом, наследует дескрипторы (нас интересует дескриптор "второго" сокета). после создания дочернего процесса получается, что на "второй" сокет есть два "указателя" - в родительском и дочернем процессах. т.е. можно отправлять данные клиенту из этих двух процессов. после создания дочернего процесса нужно закрыть в родительском процессе "указатель" на "второй" сокет, чтобы не повесить клиента( иначе, когда дочерний процесс завершит диалог и закроет сокет, в родительском процессе этот сокет будет открыт).

файл ikkp_server.c (серверная часть 1)

//компиляция C-Free
//cd "C:\Program Files (x86)\C-Free 5\mingw\bin"
//gcc.exe ikkp_server.c -lws2_32 -o ikkp_server.exe

//завершение по "CTRL_С"

//netstat -ano

//обращение клиента: telnet 127.0.0.1 30000

#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <windows.h>

//#define BUFSIZE 4096
#define BUFSIZE 100


void error(char * msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}

int read_in(int socket, char * buf, int len)
{
char * s = buf;
int slen = len;
int c = recv(socket, s, slen, 0);
while ((c > 0) && (s[c-1] != '\n')) {
s += c; slen -= c;
c = recv(socket, s, slen, 0);
}
if(c < 0)
return c;
else if (c == 0)
buf[0] = '\0';
else
s[c-1] = '\0';
return len - slen;
}

int open_listener_socket()
{
int s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1 )
error("Can't open socket");

return s;
}

void bind_to_port(int socket, int port)
{
struct sockaddr_in name ;
name.sin_family = PF_INET;
name.sin_port = htons(port); //30000
name.sin_addr.s_addr = htonl(INADDR_ANY); // inet_addr("127.0.0.1")
int reuse = 1;
if(setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(int)) == -1)
error("Ne mogu ustanovit dlya soketa parametr povtornogo ispolzovaniya");
int c = bind (socket, (struct sockaddr *) &name, sizeof(name));
if (c == -1)
error("Ne mogu privyazatsya k soketu");
}

int say(int socket, char * s)
{
int result = send(socket, s, strlen(s), 0);
//MessageBox(NULL, "debug1", TEXT("Error"), MB_OK);
if (result == -1)
fprintf(stderr, "%s: %s\n", "Error pri obschenii s klientom.", strerror(errno));
return result;
}


int main(int argc, char * argv[]){

SetConsoleCP(1251); // На ввод
SetConsoleOutputCP(1251); //На вывод.
///////////////////// Что-то типа инициализации. без этого не работает
WORD wVersionRequested;
WSADATA wsaData;
int err;

/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
//////////////////////

int listener_d = open_listener_socket();
//printf("Opened handle %i\r\n", listener_d);
bind_to_port(listener_d, 30000);

if(listen(listener_d, 10) == -1 ) // без этой строки - ошибка на accept(....
error("Can't listen");

char buf[255];
char * istr;

struct sockaddr_in client_addr;
unsigned int address_size = sizeof(client_addr);

puts("Waiting for connection");

while(1){


int connect_d = accept(listener_d, (struct sockaddr *)&client_addr, &address_size); //тут ждём
//printf("Opened handle %i\r\n", connect_d);

int radix = 10; //система счисления
char buffer[20]; //результат
char * pp; //указатель на результат
pp = itoa(connect_d, buffer, radix);

char par1[32]; //параметр командной строки для дочернего процесса
sprintf(par1, "ikkp_server_child.exe %s", pp); // ==>> "ikkp_server_child.exe 132\0"
// MessageBox(NULL, par1, TEXT("Error"), MB_OK);

if (connect_d == INVALID_SOCKET){
int ern = WSAGetLastError();
printf("Ошибка %i", ern);
return 1;
}

///////// создаём дочерний процесс. Дескрипторы наследуются. Для продолжения общения с клиентомб передаём сокет connect_d (число) в командной строке.

STARTUPINFO cif;
ZeroMemory(&cif,sizeof(STARTUPINFO));
cif.cb = sizeof(STARTUPINFO);
//cif.hStdError = hWritePipe; //не меняем
//cif.hStdOutput = hWritePipe; //
//cif.hStdInput = hReadPipe2; //
cif.dwFlags |= STARTF_USESTDHANDLES;

PROCESS_INFORMATION pi;

if (CreateProcess("ikkp_server_child.exe",
par1, // "ikkp_server_child.exe 132" //указал имя приложения в первом параметре, и продублировал во втором параметре.
NULL,NULL,
TRUE, // handles are inherited //Дескрипторы наследуются
0,
// переменные окружения
NULL,
NULL, &cif,&pi)==TRUE){


}
else
{
error("Err of create child process");

}


//Sleep(2000);
closesocket (connect_d); //закрываю, чтобы клиент не повис.
// Этот сокет был унаследован дочерним процессом. После завершения общения с клиентом в дочернем процессе его закрою.

}

//после завершения работы с сокетами нужно использовать WSACleanup()

return 0;
}

файл ikkp_server_child.c (серверная часть 2)

//компиляция C-Free
//cd "C:\Program Files (x86)\C-Free 5\mingw\bin"
//gcc.exe ikkp_server_child.c -lws2_32 -o ikkp_server_child.exe


#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include <windows.h>

#define BUFSIZE 100


void error(char * msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}

int read_in(int socket, char * buf, int len)
{
char * s = buf;
int slen = len;
int c = recv(socket, s, slen, 0);
while ((c > 0) && (s[c-1] != '\n')) {
s += c; slen -= c;
c = recv(socket, s, slen, 0);
}
if(c < 0)
return c;
else if (c == 0)
buf[0] = '\0';
else
s[c-1] = '\0';
return len - slen;
}

int say(int socket, char * s)
{
int result = send(socket, s, strlen(s), 0);
//MessageBox(NULL, "debug1", TEXT("Error"), MB_OK);
if (result == -1)
fprintf(stderr, "%s: %s\n", "Error pri obschenii s klientom.", strerror(errno));
return result;
}


int main(int argc, char * argv[]){

SetConsoleCP(1251); // На ввод
SetConsoleOutputCP(1251); //На вывод.
/////////////////////

char * str_socket = argv[1];

int connect_d;
//char str[256] = "12345"; //исходное число в виде строки
connect_d = atoi(str_socket); //результат


char buf[255];

// FILE * out1 = fopen("q1.txt", "a");
// fprintf(out1, "\r\n");
// fprintf(out1, str_socket);
// fclose(out1);


///////////////////Что-то типа инициализации. без этого не работает//
WORD wVersionRequested;
WSADATA wsaData;
int err;

/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
/////Выводим приветствие сервера/////////////////
if (say(connect_d, "Internet Protocol Server\r\nVersion 1.0\r\nPSP\r\n") == -1){
closesocket (connect_d);
// FILE * out1 = fopen("q1.txt", "a");
// fprintf(out1, "\r\nErr of posting message\r\n");
// fclose(out1);

error("Err of posting message");
}

/////Ждём запрос
//запрос может приходить с задержками и по частям

fd_set read_s; // Множество

int res;
int buffsize = 2023; //предполагаем , что запрос точно войдёт в этот размер, не проверяем.
char buf[buffsize + 1];
char * bufC = buf;

struct timeval time_out; // Таймаут

FD_ZERO (&read_s); // Обнуляем множество

FD_SET (connect_d, &read_s); // Заносим в него наш сокет

time_out.tv_sec = 10;
time_out.tv_usec = 500000; //Таймаут 10.5 секунды.
int bytesRcvd = 0;
u_long arg;

loop:
if (SOCKET_ERROR == (res = select (0, &read_s, NULL, NULL, &time_out) ) ){

return -1;
}

if (res!=0){

if (FD_ISSET (connect_d, &read_s)){ //т.к. сокет один, то можно не проверять
//MessageBox(NULL, "Готов к чтению.", TEXT("notice"), MB_OK);

// https://club.shelek.ru/viewart.php?id=37

do
{ //MessageBox(NULL, "01", TEXT("notice"), MB_OK);
ioctlsocket(connect_d, FIONREAD, &arg); //сколько данных доступно для чтения
//printf("\r\nDostupno %i\r\n", arg);
if (arg == 0){

//MessageBox(NULL, "02", TEXT("notice"), MB_OK);
break;
}
if (arg > buffsize)
arg = buffsize;
bytesRcvd = recv(connect_d, bufC, arg, 0);
if (bytesRcvd > 0){
bufC[bytesRcvd] = '\0';
bufC = bufC + bytesRcvd;
//printf("%s", buf);
}
}
while (bytesRcvd > 0);
//Проверяю, пришёл ли запрос полностью и оставшееся время .

// будет занесен адрес первой найденной подстроки
char *istr;

//Поиск строки
istr = strstr (buf, "\r\n\r\n");

if ( istr == NULL){
//время ещё есть. ждём
// FILE * out1 = fopen("q1.txt", "a");
// fprintf(out1, "\r\n2\r\n");
// fprintf(out1, buf);
// fclose(out1);
goto loop; // :)

}else{
// парсим запрос ...
//отправляем ответ
say(connect_d, "_Запрошенная информация_\r\n");
}

}

}else{

// if ( (time_out.tv_sec == 0) && (time_out.tv_usec == 0) ){
// say(connect_d, "Time_out\r\n"); // клиент не готов, не зачем и отправлять
// }

// error("Клиент не готов к чтению");
}


closesocket (connect_d);

WSACleanup();

return 0;

}

файл client_.c (клиентская часть)

//компиляция C-Free
//cd "C:\Program Files (x86)\C-Free 5\mingw\bin"
//gcc.exe client_.c -lws2_32 -o client_.exe

#define _WIN32_WINNT 0x0501 //иначе - ошибка "Undefined reference to getaddrinfo"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define BUFSIZE 100

////#pragma comment(lib, "Ws2_32.lib")

void error(char * msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}

int open_socket(char * host, char * port)
{
struct addrinfo * res;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if(getaddrinfo(host, port, &hints, &res) == -1)
error("Не могу определить адрес");
int d_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(d_sock == -1)
error("Не могк открыть сокет");
int c = connect(d_sock, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if(c == -1)
error("Не могу подключиться к сокету");
return d_sock;
}

int say(int socket, char * s)
{
int result = send(socket, s, strlen(s), 0);
//MessageBox(NULL, "debug1", TEXT("Error"), MB_OK);
if (result == -1)
fprintf(stderr, "%s: %s\n", "Ошибка при общении с сервером", strerror(errno));
return result;
}

int main(int argc, char * argv[]){

SetConsoleCP(1251); // На ввод
SetConsoleOutputCP(1251); //На вывод.
///////////////////// Что-то типа инициализации. без этого не работает
WORD wVersionRequested;
WSADATA wsaData;
int err;

/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);

err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
//////////////////////
int d_sock;

d_sock = open_socket("127.0.0.1", "30000");

//Sleep(2000); //костыль, чтобы сокет успел подготовиться к чтению/записи не нужен, работает time_out в select()

// sprintf(buf, "GET /%s http/1.1\r\n", argv[1]);
//MessageBox(NULL, "11", TEXT("notice"), MB_OK);
// say(d_sock, buf);

// say(d_sock, "Host: localhost\r\n\r\n");

// при подключении клиента к серверу, последний может отправить шапку-приветствие, а может не отправить.
//а дальше сервер ждёт запрос
// https://club.shelek.ru/viewart.php?id=37

fd_set read_s; // Множество
//fd_set write_s; // Множество

int res;
int buffsize = 255;
char buf[buffsize + 1];

struct timeval time_out; // Таймаут

FD_ZERO (&read_s); // Обнуляем множество
//FD_ZERO (&write_s); // Обнуляем множество

FD_SET (d_sock, &read_s); // Заносим в него наш сокет
//FD_SET (d_sock, &write_s); // Заносим в него наш сокет

time_out.tv_sec = 2; time_out.tv_usec = 500000; //Таймаут 2.5 секунды.
int bytesRcvd = 0;
u_long arg;

if (SOCKET_ERROR == (res = select (0, &read_s, NULL, NULL, &time_out) ) ) // select (0, &read_s, &write_s, NULL, &time_out)
error("SOCKET_ERROR");

if (res!=0){

// готов ли сокет к чтению?
if (FD_ISSET (d_sock, &read_s)){ //проверяем, были ли изменения по данному сокету
//MessageBox(NULL, "Готов к чтению", TEXT("notice"), MB_OK);

// https://club.shelek.ru/viewart.php?id=37


do
{ //MessageBox(NULL, "01", TEXT("notice"), MB_OK);
ioctlsocket(d_sock, FIONREAD, &arg); //сколько данных доступно для чтения
//printf("\r\nDostupno %i\r\n", arg);
if (arg == 0){

//MessageBox(NULL, "02", TEXT("notice"), MB_OK);
break;
}
if (arg > buffsize)
arg = buffsize;
bytesRcvd = recv(d_sock, buf, arg, 0);
if (bytesRcvd > 0){
buf[bytesRcvd] = '\0';
printf("%s", buf);
}
}
while (bytesRcvd > 0);

}else
printf("\r\n Сервер не готов к чтению.\r\n");

// // // готов ли сокет к записи? готовность к записи может быть раньше готовности к чтению.
// // if ( !(FD_ISSET (d_sock, &write_s))) { //проверяем, были ли изменения по данному сокету
// // error("Сервер не готов к записи.");
// // } else
// // printf("\r\n Сервер готов к записи.\r\n");

}else error("Сервер не готов ни к чтению ни к записи");

//ДЕЛАЕМ ЗАПРОС

if (say(d_sock, "GET /index.php http/1.1\r\n") == -1){
closesocket (d_sock);
WSACleanup();
error("Ошибка отправки запроса");
}
if (say(d_sock, "Host: localhost\r\n\r\n") == -1){
closesocket (d_sock);
WSACleanup();
error("Ошибка отправки запроса");
}

// ЖДЁМ ГОВОВНОСТИ СОКЕТА ДЛЯ ЧТЕНИЯ

FD_ZERO (&read_s); // Обнуляем множество
FD_SET (d_sock, &read_s); // Заносим в него наш сокет
time_out.tv_sec = 2; time_out.tv_usec = 500000; //Таймаут 2.5 секунды.

if (SOCKET_ERROR == (res = select (0, &read_s, NULL, NULL, &time_out) ) )
error("SOCKET_ERROR");

if (res!=0){

// готов ли сокет к чтению?
if (FD_ISSET (d_sock, &read_s)){ //проверяем, были ли изменения по данному сокету

do
{ //MessageBox(NULL, "01", TEXT("notice"), MB_OK);
ioctlsocket(d_sock, FIONREAD, &arg); //сколько данных доступно для чтения
//printf("\r\nDostupno %i\r\n", arg);
if (arg == 0){

break;
}
if (arg > buffsize)
arg = buffsize;
bytesRcvd = recv(d_sock, buf, arg, 0);
if (bytesRcvd > 0){
buf[bytesRcvd] = '\0';
printf("%s", buf);
}
}
while (bytesRcvd > 0);

}else
printf("\r\n Сервер не готов к чтению.\r\n");

}else error("Сервер не готов ни к чтению ни к записи");

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


// // int bytesRcvd = recv(d_sock, buf, buffsize, 0);

// // BOOL l = TRUE;
// // if (SOCKET_ERROR == ioctlsocket (d_sock, FIONBIO, (unsigned long* ) &l) ) //перевод в неблокирующий режим
// // {
// // // Error
// // res = WSAGetLastError ();
// // return -1;
// // }


closesocket(d_sock);

//////////////////////
WSACleanup();

return 0;
}

https://stackoverflow.com/questions/2033608/mingw-linker-error-winsock
https://docs.microsoft.com/ru-ru/windows/desktop/WinSock/shared-sockets-2
https://docs.microsoft.com/ru-ru/windows/desktop/api/winsock2/nf-winsock2-wsaduplicatesocketa
https://metanit.com/cpp/c/7.3.php
http://vsokovikov.narod.ru/New_MSDN_API/Handles_objects/fn_duplicatehandle.htm
https://docs.microsoft.com/en-us/windows/desktop/procthread/creating-a-child-process-with-redirected-input-and-output
http://beej.us/guide/bgnet/translations/bgnet_A4_rus.pdf

to be continued...