Содержание

Что такое Socket (Сокет) в сетевом программировании

Сокеты (англ. socket — углубление, гнездо, разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Следует различать клиентские и серверные сокеты. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты. Программный интерфейс сокетов описан в стандарте POSIX.1 и в той или иной мере поддерживается всеми современными операционными системами.

Сокет на сленге системных администраторов означает комбинацию IP-адреса и номера порта, например 10.10.10.10:80.

Сокеты и бла-бла-бла...

Для того чтобы не было недоразумений, я сразу оговорюсь, что написанное ниже рассчитано на тех, кто кодит на с/с++ (MSVC++ в Windows-системах и gсс/g++ в никсах). Я также предполагаю, что у читателей есть хотя бы минимальный набор знаний об устройстве и функционировании компьютерных сетей. Необязателен, но желателен справочник по Windows API 32 под рукой или доступ к MSDN (юниксоидам в этом плане повезло - man pages не могут быть "не под рукой" ;)). Еще я хотел бы сделать предупреждение: представленный ниже материал не претендует на полноту освещения затронутых в нем тем, а также на абсолютную точность.

И наконец, перед тем, как мы окунемся в омут с головой, я дам еще один совет: дружище, выучи все-таки английский! Он тебе очень пригодится. Ведь когда ты захочешь стать гуру сетевого программирования, тебе придется прочесть очень много RFC-документов, а ошибки перевода и неправильного толкования технических спецификаций являются "бомбами замедленного действия"!

У каждой уважающей себя современной операционной системы есть средства для взаимодействия с другими компьютерами. Самым распространенным среди программистов средством для упомянутых целей являются сокеты. Сокеты - это API (Application Programming Interface - Интерфейс Программирования Приложений) для работы с уровнями OSI. Сокеты настолько гибки, что позволяют работать почти с любым из уровней модели OSI. Хочешь - формируй IP-пакеты руками и займись хакингом, отправляя "неправильные" пакеты, которые будут вводить сервера в ступор, хочешь - займись более благоразумным делом и создай новый удобный голосовой чат, хочешь - игрульку по сети гоняй, не хочешь - твое право, но этот случай мы в данном руководстве не рассматриваем… :)

Когда мы создаем сокет (socket - гнездо), мы получаем возможность доступа к нужному нам уровню OSI. Ну а дальше мы можем использовать соответствующие вызовы для взаимодействия с ним. Для того чтобы понять сокеты, можно провести аналогию с телефонным аппаратом и телефонной трубкой. Сокеты устроены таким образом, что они могут взаимодействовать с ОС на любом уровне OSI, скрывая ту часть реализации, которой мы не интересуемся (тебя же не волнует, как работает телефон, когда ты набираешь 03). Телефоны и сокеты бывают разные: бывают старые телефоны с дисковым набором и бывают низкоуровневые сокеты для работы с Ethernet-фреймами, бывают супер-модные цифровые телефоны и бывают сокеты для работы с верхними уровнями стека протоколов… и т.д. Причем вызовы для всех типов сокетов одни и те же, что, имхо, очень удобно. Когда мы создаем сокет, мы также заставляем систему организовать два канала: входящий (это как громкоговоритель у телефона) и исходящий (микрофон). Осуществляя чтение и запись в эти каналы, мы приказываем системе взять на себя дальнейшую судьбу данных, т.е. передать и проследить, чтоб данные дошли вовремя, в нужной последовательности, не искаженные и т.п. Система должна давать (и дает) максимум гарантий (для каждого уровня OSI - гарантии свои), что данные будут переданы правильно. Наша задача - поместить их в очередь, а на другом конце - прочитать из входящей очереди и обработать должным образом. Все остальное - нам ни к чему. Еще один плюс - сокеты переносимы. То есть изначально концепция сокетов была разработана в Berkeley, поэтому классическая реализация сокетов называется Berkeley sockets или BSD sockets (BSD == Berkeley Software Distribution). В дальнейшем, почти все ОС тем или иным образом унаследовали эту реализацию. В каждой ОС степень поддержки сокетов разная, но точно могу сказать: в современных операционных системах MS и *nix - сокеты поддерживаются настолько, насколько нам, геймдевелоперам, они могут понадобиться. Больше нам и не нужно, потому что мы не кодим под экзотические ОС, потому что, в свою очередь, геймеры (они наша целевая аудитория) на таковых не сидят. Однако по мере изучения мы будем придерживаться классической реализации BSD sockets, и стараться по минимуму использовать системно-зависимый код.

Пример кода клиент- серверного сокета на языке С++

Жизненный цикл сервера для языка С++ можно представить так:

1.Initialize Winsock.
2.Create a socket. Создание сокета ориентированного на соединение (функция socket()).
3.Bind the socket. Назначение сокету адреса привязки (функция bind()).
4.Listen on the socket for a client. Перевод сокета в режим ожидания запроса (функция listen()).
5.Accept a connection from a client. Прием поступающих запросов(функция accept()).
6.Receive and send data.
7.Disconnect. Закрытие сокета (функция close()).

Жизненный цикл клиента можно представить так:

1.Initialize Winsock.
2.Create a socket.
3.Connect to the server.
4.Send and receive data.
5.Disconnect.

Листинг клиента

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
//#include <iostream>
 
#pragma comment(lib, "Ws2_32.lib")
 
int main(int argc, char **argv) {
	WSADATA wsaData;
	int iResult;
 
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData); //для инициализации вызываем WSAStartup. MAKEWORD(2,2) указываем версию библиотеки для использования
if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
}
//Creating a Socket for the Client
 
struct addrinfo *result = NULL,
                *ptr = NULL,
                hints;
 
ZeroMemory( &hints, sizeof(hints) );/*заполняем нулями стркутуру hints, если этого не сделать то струкутура будет заполнена случайными цифрами*/
hints.ai_family = AF_UNSPEC;//
hints.ai_socktype = SOCK_STREAM;//
hints.ai_protocol = IPPROTO_TCP;//
 
#define DEFAULT_PORT "27015"
 
// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}
 
SOCKET ConnectSocket = INVALID_SOCKET;
 
// Attempt to connect to the first address returned by
// the call to getaddrinfo
ptr=result;
 
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 
    ptr->ai_protocol);
 
if (ConnectSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}
 
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    ConnectSocket = INVALID_SOCKET;
}
 
// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message
 
freeaddrinfo(result);
 
if (ConnectSocket == INVALID_SOCKET) {
    printf("Unable to connect to server!\n");
    WSACleanup();
    return 1;
}
 
#define DEFAULT_BUFLEN 512
 
int recvbuflen = DEFAULT_BUFLEN;
 
char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
 
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}
 
printf("Bytes Sent: %ld\n", iResult);
 
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}
 
// Receive data until the server closes the connection
do {
    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Bytes received: %d\n", iResult);
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);
 
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}
 
// cleanup
closesocket(ConnectSocket);
WSACleanup();
 
  return 0;
}

Листинг сервера

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
 
#pragma comment(lib, "Ws2_32.lib")
 
int main() {
	WSADATA wsaData;
	int iResult;
 
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
}
 
#define DEFAULT_PORT "27015"
 
struct addrinfo *result = NULL, *ptr = NULL, hints;
 
ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
 
// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
    printf("getaddrinfo failed: %d\n", iResult);
    WSACleanup();
    return 1;
}
 
SOCKET ListenSocket = INVALID_SOCKET;
 
// Create a SOCKET for the server to listen for client connections
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
 
if (ListenSocket == INVALID_SOCKET) {
    printf("Error at socket(): %ld\n", WSAGetLastError());
    freeaddrinfo(result);
    WSACleanup();
    return 1;
}
 
// Setup the TCP listening socket
iResult = bind( ListenSocket, 
    result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    printf("bind failed: %d\n", WSAGetLastError());
    freeaddrinfo(result);
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
 
freeaddrinfo(result);
 
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
    printf( "Listen failed with error: %ld\n", WSAGetLastError() );
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
 
SOCKET ClientSocket;
 
ClientSocket = INVALID_SOCKET;
 
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
    printf("accept failed: %d\n", WSAGetLastError());
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
#define DEFAULT_BUFLEN 512
 
char recvbuf[DEFAULT_BUFLEN];
int iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
 
// Receive until the peer shuts down the connection
do {
 
    iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0) {
        printf("Bytes received: %d\n", iResult);
 
        // Echo the buffer back to the sender
        iSendResult = send(ClientSocket, recvbuf, iResult, 0);
        if (iSendResult == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
        printf("Bytes sent: %d\n", iSendResult);
    } else if (iResult == 0)
        printf("Connection closing...\n");
    else {
        printf("recv failed: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }
 
} while (iResult > 0);
 
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;
}
 
 
// cleanup
closesocket(ClientSocket);
WSACleanup();
 
  return 0;
}