Введение в сетевое программирование — различия между версиями

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Новая: ==Введение в сетевое программирование== ===Интерфейс транспортного уровня=== === Сокеты=== '''Socket''' (гнездо) ...)
 
Строка 83: Строка 83:
 
===TCP-сервер (C++)===
 
===TCP-сервер (C++)===
 
Будем использовать winsock2.h
 
Будем использовать winsock2.h
<source lang="Cpp">SOCKET ListenSocket, ClientSocket;
+
<source lang="Cpp">#include <winsock2.h>
sockaddr_in ServerAddr;
+
#include <ws2tcpip.h>
int err, maxlen = 512;
+
#include <stdlib.h>
char* recvbuf = new char[maxlen];
+
#include <stdio.h>
char* result_string = new char[maxlen];
 
  
ListenSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib
ServerAddr.sin_family = AF_INET;
 
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
ServerAddr.sin_port = htons(12345);
 
// сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт 12345
 
bind ( ListenSocket, ServerAddr, sizeof(ServerAddr));
 
// выделяет память для очереди подключений
 
listen (ListenSocket, 50);
 
  
while (true)  
+
int __cdecl main(void)  
 
{
 
{
  // устанавливает соединение с клиентом из очереди
+
WSADATA wsaData;
  ClientSocket = аccept(ListenSocket, NULL, NULL);
+
SOCKET ListenSocket,ClientSocket;  // впускающий сокет и сокет для клиентов
  // принимает данные
+
sockaddr_in ServerAddr;  // это будет адрес сервера
  err = recv (ClientSocket, recvbuf, maxlen, 0);
+
int err, maxlen = 512;  // код ошибки и размер буферов
  // если очередь пуста, то ждет подключения клиента
+
char* recvbuf=new char[maxlen];  // буфер приема
  if (err > 0)  
+
char* result_string=new char[maxlen];  // буфер отправки
  {
+
 
    recvbuf[err]=0;
+
 
    printf("Received query: %s\n", (char* )recvbuf);
+
// Initialize Winsock
    // вычисляем результат  
+
WSAStartup(MAKEWORD(2,2), &wsaData);
    int result = ... ;
+
 
    _snprintf_s (result_string, maxlen, maxlen, "OK %d\n", result);
+
// Create a SOCKET for connecting to server
    // передает данные
+
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    send( ClientSocket, result_string, strlen(result_string), 0 );
+
 
    printf("Sent answer: %s\n", result_string);
+
// Setup the TCP listening socket
  }
+
ServerAddr.sin_family=AF_INET;
  // закрывает соединение с клиентом
+
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
  closesocket(ClientSocket);
+
ServerAddr.sin_port=htons(12345);
}</source>
+
err = bind( ListenSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));
 +
if (err == SOCKET_ERROR) {
 +
printf("bind failed: %d\n", WSAGetLastError());
 +
closesocket(ListenSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
 
 +
err = listen(ListenSocket, 50);
 +
if (err == SOCKET_ERROR) {
 +
printf("listen failed: %d\n", WSAGetLastError());
 +
closesocket(ListenSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
while (true) {
 +
// Accept a client socket
 +
ClientSocket = accept(ListenSocket, NULL, NULL);
 +
err = recv(ClientSocket, recvbuf, maxlen, 0);
 +
if (err > 0) {
 +
recvbuf[err]=0;
 +
printf("Received query: %s\n", (char* )recvbuf);
 +
// вычисляем результат
 +
int result=72;
 +
_snprintf_s(result_string,maxlen,maxlen,"OK %d\n",result);
 +
// отправляем результат на сервер
 +
send( ClientSocket, result_string, strlen(result_string), 0 );
 +
printf("Sent answer: %s\n", result_string);
 +
}
 +
else if (err == 0)
 +
printf("Connection closing...\n");
 +
else  {
 +
printf("recv failed: %d\n", WSAGetLastError());
 +
closesocket(ClientSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
 
 +
// shutdown the connection since we're done
 +
closesocket(ClientSocket);
 +
}
 +
}
 +
</source>
  
 
===TCP-клиент (C++)===
 
===TCP-клиент (C++)===
<source lang="Cpp">SOCKET ClientSocket;
+
<source lang="Cpp">#include <winsock2.h>
sockaddr_in ServerAddr;
+
#include <ws2tcpip.h>
int err, maxlen = 512;
+
#include <stdlib.h>
char* recvbuf = new char[maxlen];
+
#include <stdio.h>
char* query = new char[maxlen];
 
  
ConnectSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib
ServerAddr.sin_family = AF_INET;
 
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
ServerAddr.sin_port=htons(12345);
 
// получает от ОС случайный номер порта для общения с сервером и устанавливает соединение с сервером
 
connect (ConnectSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr)); // TCP-клиенту порт присваивается в функции connect
 
_snprintf_s (query,maxlen,maxlen,"CALC * 12 6\n");
 
// передает данные
 
send (ConnectSocket, query,strlen(query),0);
 
printf("Sent: %s\n", query);
 
  
// принимает данные
+
int __cdecl main(void)  
err = recv(ConnectSocket,recvbuf,maxlen,0);
 
if (err > 0)  
 
 
{
 
{
  recvbuf[err]=0;
+
WSADATA wsaData;
  printf(“Result: %s\n", (char* )recvbuf);
+
SOCKET ConnectSocket;  // впускающий сокет и сокет для клиентов
 +
sockaddr_in ServerAddr;  // это будет адрес сервера
 +
int err, maxlen = 512;  // код ошибки и размер буферов
 +
char* recvbuf=new char[maxlen];  // буфер приема
 +
char* query=new char[maxlen];  // буфер отправки
 +
 
 +
 
 +
// Initialize Winsock
 +
WSAStartup(MAKEWORD(2,2), &wsaData);
 +
 
 +
// Connect to server
 +
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 +
 
 +
ServerAddr.sin_family=AF_INET;
 +
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
 +
ServerAddr.sin_port=htons(12345);
 +
 
 +
err = connect( ConnectSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));
 +
 
 +
if (err == SOCKET_ERROR) {
 +
printf("connect failed: %d\n", WSAGetLastError());
 +
closesocket(ConnectSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
 
 +
_snprintf_s(query,maxlen,maxlen,"CALC * 12 6\n");
 +
// отправляем запрос на сервер
 +
send( ConnectSocket,  query, strlen(query), 0 );
 +
printf("Sent: %s\n", query);
 +
 
 +
// получаем результат
 +
err = recv(ConnectSocket, recvbuf, maxlen, 0);
 +
if (err > 0) {
 +
recvbuf[err]=0;
 +
printf("Result: %s\n", (char* )recvbuf);
 +
}
 +
else if (err == 0)
 +
printf("Connection closing...\n");
 +
else  {
 +
printf("recv failed: %d\n", WSAGetLastError());
 +
closesocket(ConnectSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
 
 +
// shutdown the connection since we're done
 +
closesocket(ConnectSocket);
 +
 
}
 
}
 +
</source>
 +
 +
===UDP-сервер (C++)===
 +
<source lang="Cpp">
 +
#include <winsock2.h>
 +
#include <ws2tcpip.h>
 +
#include <stdlib.h>
 +
#include <stdio.h>
 +
 +
// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib
 +
 +
int __cdecl main(void)
 +
{
 +
WSADATA wsaData;
 +
SOCKET SendRecvSocket;  // сокет для приема и передачи
 +
sockaddr_in ServerAddr, ClientAddr;  // это будет адрес сервера и клиентов
 +
int err, maxlen = 512, ClientAddrSize=sizeof(ClientAddr);  // код ошибки, размер буферов и размер структуры адреса
 +
char* recvbuf=new char[maxlen];  // буфер приема
 +
char* result_string=new char[maxlen];  // буфер отправки
 +
 +
 +
// Initialize Winsock
 +
WSAStartup(MAKEWORD(2,2), &wsaData);
  
// закрывает соединение с сервером
+
// Create a SOCKET for connecting to server
closesocket(ConnectSocket);</source>
+
SendRecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  
===Ненадежный UDP-сервер (C++)===
+
// Setup the TCP listening socket
<source lang="Cpp">SOCKET SendRecvSocket;
+
ServerAddr.sin_family=AF_INET;
sockaddr_in ServerAddr, ClientAddr;  
+
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int err, maxlen = 512;
+
ServerAddr.sin_port=htons(12345);
ClientAddrSize = sizeof(ClientAddr);  
+
err = bind( SendRecvSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));
char* recvbuf = new char[maxlen];  
+
if (err == SOCKET_ERROR) {
char* result_string = new char[maxlen];  
+
printf("bind failed: %d\n", WSAGetLastError());
 +
closesocket(SendRecvSocket);
 +
WSACleanup();
 +
return 1;
 +
}
  
SendRecvSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
+
while (true) {
ServerAddr.sin_family = AF_INET;
+
// Accept a client socket
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
+
err = recvfrom(SendRecvSocket,recvbuf,maxlen,0,(sockaddr *)&ClientAddr,&ClientAddrSize);
ServerAddr.sin_port=htons(12345);
+
if (err > 0) {
// сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт 12345
+
recvbuf[err]=0;
bind ( ListenSocket, ServerAddr, sizeof(ServerAddr));
+
printf("Received query: %s\n", (char* )recvbuf);
// listen – нельзя !!!
+
// вычисляем результат
while (true)  
+
int result=72;
{  
+
_snprintf_s(result_string,maxlen,maxlen,"OK %d\n",result);
  // accept – нельзя !!!
+
// отправляем результат на сервер
  // принимает данные
+
sendto(SendRecvSocket,result_string,strlen(result_string),0,(sockaddr *)&ClientAddr,sizeof(ClientAddr));
  err = recvfrom (SendRecvSocket, recvbuf, maxlen, 0, (sockaddr *)&ClientAddr, &ClientAddrSize);
+
printf("Sent answer: %s\n", result_string);
  if (err > 0)  
+
}
  {
+
else  {
    recvbuf[err]=0;
+
printf("recv failed: %d\n", WSAGetLastError());
    printf("Received query: %s\n", (char* )recvbuf);
+
closesocket(SendRecvSocket);
    // вычисляем результат  
+
WSACleanup();
    int result = ... ;
+
return 1;
    _snprintf_s (result_string,maxlen,maxlen,
+
}
    "OK %d\n",result);
+
}
    // передает данные
+
}
    sendto (SendRecvSocket, result_string, strlen(result_string), 0, (sockaddr *)&ClientAddr, sizeof(ClientAddr));
+
</source>
    printf("Sent answer: %s\n", result_string);
 
  }
 
}</source>
 
  
===Ненадежный UDP-клиент (C++)===
+
===UDP-клиент (C++)===
<source lang="Cpp">SOCKET SendRecvSocket;
+
<source lang="Cpp">#include <winsock2.h>
sockaddr_in ServerAddr;
+
#include <ws2tcpip.h>
int err, maxlen = 512;
+
#include <stdlib.h>
char* recvbuf = new char[maxlen];
+
#include <stdio.h>
char* query = new char[maxlen];
+
 
SendRecvSocket = socket (AF_INET, SOCK_DGRAM,  IPPROTO_UDP);
+
// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib
ServerAddr.sin_family = AF_INET;
+
 
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
+
int __cdecl main(void)  
ServerAddr.sin_port=htons(12345);
 
_snprintf_s (query,maxlen,maxlen,"CALC * 12 6\n");
 
// автоматически получает от ОС случайный номер порта для общения с сервером при отправке первой датаграммы
 
// передает данные
 
sendto (SendRecvSocket, query, strlen(query), 0, (sockaddr *)&ServerAddr, sizeof(ServerAddr));
 
printf("Sent: %s\n", query);
 
// принимает данные
 
err = recvfrom (SendRecvSocket,recvbuf,maxlen,0,0,0);
 
if (err > 0)  
 
 
{
 
{
  recvbuf[err]=0;
+
WSADATA wsaData;
  printf(“Result: %s\n", (char* )recvbuf);
+
SOCKET SendRecvSocket;  // сокет для приема и передачи
 +
sockaddr_in ServerAddr;  // это будет адрес сервера и клиентов
 +
int err, maxlen = 512;  // код ошибки, размер буферов и размер структуры адреса
 +
char* recvbuf=new char[maxlen];  // буфер приема
 +
char* query=new char[maxlen];  // буфер отправки
 +
 
 +
 
 +
// Initialize Winsock
 +
WSAStartup(MAKEWORD(2,2), &wsaData);
 +
 
 +
// Create a SOCKET for connecting to server
 +
SendRecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 +
 
 +
ServerAddr.sin_family=AF_INET;
 +
ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
 +
ServerAddr.sin_port=htons(12345);
 +
 
 +
_snprintf_s(query,maxlen,maxlen,"CALC * 12 6\n");
 +
// отправляем запрос на сервер
 +
sendto(SendRecvSocket,query, strlen(query), 0, (sockaddr *)&ServerAddr,sizeof(ServerAddr)); 
 +
printf("Sent: %s\n", query);
 +
 
 +
// получаем результат
 +
err = recvfrom(SendRecvSocket,recvbuf,maxlen,0,0,0);
 +
if (err > 0) {
 +
recvbuf[err]=0;
 +
printf("Result: %s\n", (char* )recvbuf);
 +
}
 +
else {
 +
printf("recv failed: %d\n", WSAGetLastError());
 +
closesocket(SendRecvSocket);
 +
WSACleanup();
 +
return 1;
 +
}
 +
 
 +
closesocket(SendRecvSocket);
 +
 
 
}
 
}
closesocket(SendRecvSocket);</source>
+
</source>
  
 
===TCP-сервер (C#, .NET)===
 
===TCP-сервер (C#, .NET)===
<source lang="Csharp">Int32 port = 12345;
+
<source lang="Csharp">using System;
IPAddress localAddr=IPAddress.Parse("127.0.0.1");
+
 
server = new TcpListener(localAddr, port);
+
using System.IO;
server.Start();
+
 
 +
using System.Net;
 +
 
 +
using System.Net.Sockets;
 +
 
 +
using System.Text;
 +
 
 +
 
 +
 
 +
class TCP_Server
  
while (true)
 
 
{
 
{
  TcpClient client = server.AcceptTcpClient();
+
 
  NetworkStream stream = client.GetStream();
+
    public static void Main()
  stream.Read(query, 0, query.Length)
+
 
  stream.Write(result, 0, result.Length);
+
    {
  client.Close();
+
 
}</source>
+
        TcpListener server = null;
 +
 
 +
        try
 +
 
 +
        {
 +
 
 +
            Int32 port = 12345; //порт сервера
 +
 
 +
            IPAddress localAddr = IPAddress.Parse("127.0.0.1");//ip-адрес сервера (интерфейс)
 +
 
 +
           
 +
 
 +
            //TcpListener - класс TCP-сервера из .Net Framework Class Library
 +
 
 +
            server = new TcpListener(localAddr, port);
 +
 
 +
 
 +
 
 +
            // начинаем ожидание подсоединений клиентов на интерфейсе localAddr и порту port
 +
 
 +
            server.Start();
 +
 
 +
 
 +
 
 +
            // буффер для приема сообщений и соответствующая ему строка для вывода на экран
 +
 
 +
            Byte[] bytes = new Byte[1000];
 +
 
 +
            String data;
 +
 
 +
           
 +
 
 +
            //ответ клиенту
 +
 
 +
            String answer_message;
 +
 
 +
 
 +
 
 +
            //цикл обработки подсоединений клиентов
 +
 
 +
            while (true)
 +
 
 +
            {
 +
 
 +
                Console.Write("Waiting for a connection... ");
 +
 
 +
                // Ждем соединения клиента
 +
 
 +
                TcpClient client = server.AcceptTcpClient();
 +
 
 +
                //Ура! Кто-то подсоединился!
 +
 
 +
                Console.WriteLine("Connected!");
 +
 
 +
                // вводим поток stream для чтения и записи через установленное соединение
 +
 
 +
                NetworkStream stream = client.GetStream();
 +
 
 +
                int i = stream.Read(bytes, 0, bytes.Length);
 +
 
 +
                if (i > 0)
 +
 
 +
                {
 +
 
 +
                    // преобразуем принятые данные в строку ASCII string.
 +
 
 +
                    data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
 +
 
 +
                    //печатаем то, что получили
 +
 
 +
                    Console.WriteLine("Received: {0}", data);
 +
 
 +
                    //анализируем запрос клиента и вычисляем результат
 +
 
 +
                    int res = 72;
 +
 
 +
                    answer_message = "OK " + res.ToString() + (char)13 + (char)10;
 +
 
 +
                    //печатаем то, что будем отправлять
 +
 
 +
                    Console.WriteLine("Sent: {0}", answer_message);
 +
 
 +
                    //преобразуем строчку-ответ сервера в массив байт
 +
 
 +
                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(answer_message);
 +
 
 +
                    // отправляем ответ
 +
 
 +
                    stream.Write(msg, 0, msg.Length);
 +
 
 +
                }
 +
 
 +
 
 +
 
 +
                // закрываем соединение
 +
 
 +
                client.Close();
 +
 
 +
            }
 +
 
 +
        }
 +
 
 +
        catch (SocketException expt)
 +
 
 +
        {
 +
 
 +
            Console.WriteLine("SocketException: {0}", expt);
 +
 
 +
        }
 +
 
 +
        finally
 +
 
 +
        {
 +
 
 +
            // Stop listening for new clients.
 +
 
 +
            server.Stop();
 +
 
 +
        }
 +
 
 +
 
 +
 
 +
 
 +
 
 +
        Console.WriteLine("\nHit enter to continue...");
 +
 
 +
        Console.Read();
 +
 
 +
    }
 +
 
 +
}
 +
 
 +
 
 +
</source>
  
 
===TCP-клиент (C#, .NET)===
 
===TCP-клиент (C#, .NET)===
<source lang="Csharp">Int32 ServerPort = 12345;
+
<source lang="Csharp">using System;
string ServerName=“localhost”
+
 
TcpClient client = new TcpClient(ServerName, ServerPort );
+
using System.Collections.Generic;
NetworkStream stream = client.GetStream();
+
 
stream.Write(query, 0, query.Length)
+
using System.Text;
stream.Read(result, 0, result.Length);
+
 
stream.Close();
+
using System.Net.Sockets;
client.Close();</source>
+
 
 +
 
 +
 
 +
namespace ClientCSharp
 +
 
 +
{
 +
 
 +
    class TCP_Client
 +
 
 +
    {
 +
 
 +
        static void Main(string[] args)
 +
 
 +
        {
 +
 
 +
            try
 +
 
 +
            {
 +
 
 +
                Int32 port = 12345;//порт сервера
 +
 
 +
                string message = "CALC * 12 6\n";//строка, которую пошлем серверу
 +
 
 +
                TcpClient client = new TcpClient("localhost", port);
 +
 
 +
 
 +
 
 +
                //преобразуем строчку в массив байт
 +
 
 +
                Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
 +
 
 +
 
 +
 
 +
                // вводим поток stream для чтения и записи через установленное соединение               
 +
 
 +
                NetworkStream stream = client.GetStream();
 +
 
 +
 
 +
 
 +
                // посылаем сообщение серверу
 +
 
 +
                stream.Write(data, 0, data.Length);
 +
 
 +
 
 +
 
 +
                Console.WriteLine("Sent: {0}", message);//печатаем то, что отправили
 +
 
 +
 
 +
 
 +
                // буффер для приема сообщений
 +
 
 +
                data = new Byte[1000];
 +
 
 +
 
 +
 
 +
                // строка для приема сообщений сервера
 +
 
 +
                String responseData;
 +
 
 +
 
 +
 
 +
                // получаем сообщение от сервера
 +
 
 +
                Int32 bytes = stream.Read(data, 0, data.Length);
 +
 
 +
                responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
 +
 
 +
                //печатаем то, что получили
 +
 
 +
                Console.WriteLine("Received: {0}", responseData);
 +
 
 +
 
 +
 
 +
                // закрываем соединение
 +
 
 +
                stream.Close();
 +
 
 +
                client.Close();
 +
 
 +
            }
 +
 
 +
            catch (ArgumentNullException expt)
 +
 
 +
            {
 +
 
 +
                Console.WriteLine("ArgumentNullException: {0}", expt);
 +
 
 +
            }
 +
 
 +
            catch (SocketException expt)
 +
 
 +
            {
 +
 
 +
                Console.WriteLine("SocketException: {0}", expt);
 +
 
 +
            }
 +
 
 +
 
 +
 
 +
            Console.WriteLine("\n Press Enter to continue...");
 +
 
 +
            Console.Read();
 +
 
 +
        }
 +
 
 +
    }
 +
 
 +
}
 +
</source>

Версия 00:55, 27 мая 2009

Введение в сетевое программирование

Интерфейс транспортного уровня

Сокеты

Socket (гнездо) – это структура данных, идентифицирующая сетевое соединение.

Зачем нужны эти сокеты? Сервер (программа) может одновременно поддерживать несколько TCP-соединений с другими компьютерами, используя один и тот же стандартный номер порта. Как это реализовать? Можно возложить эту задачу на программиста. Пусть он выбирает из буфера приема сетевого уровня пакеты, смотрит от кого они отправлены и отвечает соответствующим образом. Но можно сделать все это удобнее.

С каждым соединением должен быть связан свой поток, в который можно писать информацию и из которого можно ее считывать. Каждому потоку соответствует свой IP-адрес удаленного компьютера и свой порт удаленного компьютера. Будем назвать структуру данных, соответствующую каждому такому потоку, сокетом (розеткой). Таким образом, сервер можно сравнить с разветвителем с кучей розеток, к которым подключены клиенты. Если сделать так, то вместо того, чтобы разбираться в куче разносортных пакетов из буфера приема сетевого уровня, сервер будет читать из потоков, каждый из которых соответствует своему клиенту. Данные от клиентов не будут сваливаться в кучу, а будут распределяться по потокам-сокетам. Ответственность за такое распределение ложится не на программиста, а на драйвер транспортного уровня операционной системы. Сокеты были разработаны в университете в Berkley. Стали стандартом, вместо OSI-шного TLI (Transport Layer Interface).

Команды

SOCKET – создать новый (пустой) сокет.

BIND – сервер связывает свой локальный адрес (порт) с сокетом.

LISTEN – сервер выделяет память под очередь подсоединений клиентов (TCP).

ACCEPT – сервер ожидает подсоединения клиента или принимает первое подключение из очереди (TCP). Чтобы заблокировать ожидание входящих соединений, сервер выполняет примитив ACCEPT. Получив запрос соединения, транспортный модуль ОС создает новый сокет с теми же свойствами, что и у исходного сокета, и возвращает описатель файла для него. После этого сервер может разветвить процесс или поток, чтобы обработать соединение для нового сокета и параллельно ожидать следующего соединения для оригинального сокета.

CONNECT–клиент запрашивает соединение (TCP).

SEND/SEND_TO – послать данные (TCP/UDP).

RECEIVE/RECEIVE_FROM – получить данные (TCP/UDP).

DISCONNECT – запросить разъединение (TCP).

Постановка задачи

Для ознакомления с сетевым программированием разберем пример. Пусть требуется запрограммировать службу удаленных вычислений. Клиенты просят сервер вычислить выражение (для начала содержащее только одну арифметическую операцию +, -, *, / ), сервер возвращает результат.

Разработка протокола Calculation 0.1

Назовем наш протокол Calculation 0.1. Для начала определим формат запроса и ответа. Пусть, например, запрос клиента должен начинаться со слова CALC, далее через пробел оперция и потом два числа – аргументы. Тогда запрос клиента на вычисление произведения 12*6 будет выглядеть так:

CALC * 12 6 ENTER

Ответы сервера будет начинаться со слова OK, если запрос был корректен и далее через пробел число – результат вычислений. Если же запрос был неправелен, ответом будет одно слово ERR.

ОК 72 ENTER (если все нормально)
ERR ENTER (если запрос неправильный)

ENTER нужен, чтобы узнать где конец строки.

После определения правил работы протокола, можно перейти к реализации. И тут встает вопрос: используем TCP или UDP? Мы разберем оба случая. И, для начала, разберем алгоритм взаимодействия сервера с клиентом, а потом попытаемся его реализовать программно на языке C++.

Алгоритм работы сервера (TCP)

  1. Запускается заранее, до подключения клиентов.
  2. Сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт № 12345.
  3. Выделяет память для очереди подключений.
  4. В цикле:
  • устанавливает соединение с клиентом из очереди; если очередь пуста, то ждет подключения клиента;
  • принимает/передает данные;
  • закрывает соединение с клиентом.

Алгоритм работы клиента (TCP)

  1. Получает от ОС случайный номер порта для общения с сервером.
  2. Устанавливает соединение с сервером.
  3. Передает/принимает данные.
  4. Закрывает соединение с сервером.

Алгоритм работы сервера (UDP)

  1. Запускается заранее, до подключения клиентов.
  2. сообщает ОС, что будет ожидать сообщений, посланных на заранее утвержденный порт № 12345.
  3. В цикле:
  • ждет прихода сообщения;
  • обрабатывает данные;
  • передает результат.

Основное отличие UDP от TCP — не нужно возиться с соединениями.

Алгоритм работы клиента (UDP)

  1. Получает от ОС случайный номер порта для общения с сервером.
  2. Передает/принимает данные.

TCP-сервер (C++)

Будем использовать winsock2.h

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib

int __cdecl main(void) 
{
	WSADATA wsaData;
	SOCKET ListenSocket,ClientSocket;  // впускающий сокет и сокет для клиентов
	sockaddr_in ServerAddr;  // это будет адрес сервера
	int err, maxlen = 512;  // код ошибки и размер буферов
	char* recvbuf=new char[maxlen];  // буфер приема
	char* result_string=new char[maxlen];  // буфер отправки


	// Initialize Winsock
	WSAStartup(MAKEWORD(2,2), &wsaData);

	// Create a SOCKET for connecting to server
	ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	// Setup the TCP listening socket
	ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	ServerAddr.sin_port=htons(12345);
	err = bind( ListenSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));
	if (err == SOCKET_ERROR) {
		printf("bind failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}

	err = listen(ListenSocket, 50);
	if (err == SOCKET_ERROR) {
		printf("listen failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	while (true) {
		// Accept a client socket
		ClientSocket = accept(ListenSocket, NULL, NULL);
		err = recv(ClientSocket, recvbuf, maxlen, 0);
		if (err > 0) {
			recvbuf[err]=0;
			printf("Received query: %s\n", (char* )recvbuf);
			// вычисляем результат
			int result=72;
			_snprintf_s(result_string,maxlen,maxlen,"OK %d\n",result);
			// отправляем результат на сервер
			send( ClientSocket,  result_string, strlen(result_string), 0 );
			printf("Sent answer: %s\n", result_string);
		}
		else if (err == 0)
			printf("Connection closing...\n");
		else  {
			printf("recv failed: %d\n", WSAGetLastError());
			closesocket(ClientSocket);
			WSACleanup();
			return 1;
		}

		// shutdown the connection since we're done
		closesocket(ClientSocket);
	}
}

TCP-клиент (C++)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib

int __cdecl main(void) 
{
	WSADATA wsaData;
	SOCKET ConnectSocket;  // впускающий сокет и сокет для клиентов
	sockaddr_in ServerAddr;  // это будет адрес сервера
	int err, maxlen = 512;  // код ошибки и размер буферов
	char* recvbuf=new char[maxlen];  // буфер приема
	char* query=new char[maxlen];  // буфер отправки


	// Initialize Winsock
	WSAStartup(MAKEWORD(2,2), &wsaData);

	// Connect to server
	ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	ServerAddr.sin_port=htons(12345);

	err = connect( ConnectSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));

	if (err == SOCKET_ERROR) {
		printf("connect failed: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);
		WSACleanup();
		return 1;
	}

	_snprintf_s(query,maxlen,maxlen,"CALC * 12 6\n");
	// отправляем запрос на сервер
	send( ConnectSocket,  query, strlen(query), 0 );
	printf("Sent: %s\n", query);

	// получаем результат
	err = recv(ConnectSocket, recvbuf, maxlen, 0);
	if (err > 0) {
		recvbuf[err]=0;
		printf("Result: %s\n", (char* )recvbuf);
	}
	else if (err == 0)
		printf("Connection closing...\n");
	else  {
		printf("recv failed: %d\n", WSAGetLastError());
		closesocket(ConnectSocket);
		WSACleanup();
		return 1;
	}

	// shutdown the connection since we're done
	closesocket(ConnectSocket);
	
}

UDP-сервер (C++)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib

int __cdecl main(void) 
{
	WSADATA wsaData;
	SOCKET SendRecvSocket;  // сокет для приема и передачи
	sockaddr_in ServerAddr, ClientAddr;  // это будет адрес сервера и клиентов
	int err, maxlen = 512, ClientAddrSize=sizeof(ClientAddr);  // код ошибки, размер буферов и размер структуры адреса
	char* recvbuf=new char[maxlen];  // буфер приема
	char* result_string=new char[maxlen];  // буфер отправки


	// Initialize Winsock
	WSAStartup(MAKEWORD(2,2), &wsaData);

	// Create a SOCKET for connecting to server
	SendRecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	// Setup the TCP listening socket
	ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	ServerAddr.sin_port=htons(12345);
	err = bind( SendRecvSocket, (sockaddr *) &ServerAddr, sizeof(ServerAddr));
	if (err == SOCKET_ERROR) {
		printf("bind failed: %d\n", WSAGetLastError());
		closesocket(SendRecvSocket);
		WSACleanup();
		return 1;
	}

	while (true) {
		// Accept a client socket
		err = recvfrom(SendRecvSocket,recvbuf,maxlen,0,(sockaddr *)&ClientAddr,&ClientAddrSize);
		if (err > 0) {
			recvbuf[err]=0;
			printf("Received query: %s\n", (char* )recvbuf);
			// вычисляем результат
			int result=72;
			_snprintf_s(result_string,maxlen,maxlen,"OK %d\n",result);
			// отправляем результат на сервер
			sendto(SendRecvSocket,result_string,strlen(result_string),0,(sockaddr *)&ClientAddr,sizeof(ClientAddr));
			printf("Sent answer: %s\n", result_string);
		}
		else  {
			printf("recv failed: %d\n", WSAGetLastError());
			closesocket(SendRecvSocket);
			WSACleanup();
			return 1;
		}
	}
}

UDP-клиент (C++)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// В опциях проекта, в разделе Linker, в пункте Additional Dependancies укажите Ws2_32.lib

int __cdecl main(void) 
{
	WSADATA wsaData;
	SOCKET SendRecvSocket;  // сокет для приема и передачи
	sockaddr_in ServerAddr;  // это будет адрес сервера и клиентов
	int err, maxlen = 512;  // код ошибки, размер буферов и размер структуры адреса
	char* recvbuf=new char[maxlen];  // буфер приема
	char* query=new char[maxlen];  // буфер отправки


	// Initialize Winsock
	WSAStartup(MAKEWORD(2,2), &wsaData);

	// Create a SOCKET for connecting to server
	SendRecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	ServerAddr.sin_family=AF_INET;
	ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	ServerAddr.sin_port=htons(12345);

	_snprintf_s(query,maxlen,maxlen,"CALC * 12 6\n");
	// отправляем запрос на сервер
	sendto(SendRecvSocket,query, strlen(query), 0, (sockaddr *)&ServerAddr,sizeof(ServerAddr));  
	printf("Sent: %s\n", query);

	// получаем результат
	err = recvfrom(SendRecvSocket,recvbuf,maxlen,0,0,0);
	if (err > 0) {
		recvbuf[err]=0;
		printf("Result: %s\n", (char* )recvbuf);
	}
	else {
		printf("recv failed: %d\n", WSAGetLastError());
		closesocket(SendRecvSocket);
		WSACleanup();
		return 1;
	}

	closesocket(SendRecvSocket);

}

TCP-сервер (C#, .NET)

using System;

using System.IO;

using System.Net;

using System.Net.Sockets;

using System.Text;



class TCP_Server

{

    public static void Main()

    {

        TcpListener server = null;

        try

        {

            Int32 port = 12345; //порт сервера

            IPAddress localAddr = IPAddress.Parse("127.0.0.1");//ip-адрес сервера (интерфейс)

            

            //TcpListener - класс TCP-сервера из .Net Framework Class Library

            server = new TcpListener(localAddr, port);



            // начинаем ожидание подсоединений клиентов на интерфейсе localAddr и порту port

            server.Start();



            // буффер для приема сообщений и соответствующая ему строка для вывода на экран

            Byte[] bytes = new Byte[1000];

            String data;

            

            //ответ клиенту

            String answer_message;



            //цикл обработки подсоединений клиентов

            while (true)

            {

                Console.Write("Waiting for a connection... ");

                // Ждем соединения клиента

                TcpClient client = server.AcceptTcpClient();

                //Ура! Кто-то подсоединился!

                Console.WriteLine("Connected!");

                // вводим поток stream для чтения и записи через установленное соединение

                NetworkStream stream = client.GetStream();

                int i = stream.Read(bytes, 0, bytes.Length);

                if (i > 0)

                {

                    // преобразуем принятые данные в строку ASCII string.

                    data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);

                    //печатаем то, что получили

                    Console.WriteLine("Received: {0}", data);

                    //анализируем запрос клиента и вычисляем результат

                    int res = 72;

                    answer_message = "OK " + res.ToString() + (char)13 + (char)10;

                    //печатаем то, что будем отправлять

                    Console.WriteLine("Sent: {0}", answer_message);

                    //преобразуем строчку-ответ сервера в массив байт

                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(answer_message);

                    // отправляем ответ

                    stream.Write(msg, 0, msg.Length);

                }



                // закрываем соединение

                client.Close();

            }

        }

        catch (SocketException expt)

        {

            Console.WriteLine("SocketException: {0}", expt);

        }

        finally

        {

            // Stop listening for new clients.

            server.Stop();

        }





        Console.WriteLine("\nHit enter to continue...");

        Console.Read();

    }

}

TCP-клиент (C#, .NET)

using System;

using System.Collections.Generic;

using System.Text;

using System.Net.Sockets;



namespace ClientCSharp

{

    class TCP_Client

    {

        static void Main(string[] args)

        {

            try

            {

                Int32 port = 12345;//порт сервера

                string message = "CALC * 12 6\n";//строка, которую пошлем серверу

                TcpClient client = new TcpClient("localhost", port);



                //преобразуем строчку в массив байт

                Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);



                // вводим поток stream для чтения и записи через установленное соединение                

                NetworkStream stream = client.GetStream();



                // посылаем сообщение серверу 

                stream.Write(data, 0, data.Length);



                Console.WriteLine("Sent: {0}", message);//печатаем то, что отправили



                // буффер для приема сообщений

                data = new Byte[1000];



                // строка для приема сообщений сервера

                String responseData;



                // получаем сообщение от сервера

                Int32 bytes = stream.Read(data, 0, data.Length);

                responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);

                //печатаем то, что получили

                Console.WriteLine("Received: {0}", responseData);



                // закрываем соединение

                stream.Close();

                client.Close();

            }

            catch (ArgumentNullException expt)

            {

                Console.WriteLine("ArgumentNullException: {0}", expt);

            }

            catch (SocketException expt)

            {

                Console.WriteLine("SocketException: {0}", expt);

            }



            Console.WriteLine("\n Press Enter to continue...");

            Console.Read();

        }

    }

}