Что такое p2p steam
Кооператив на Unity за «Бесплатно», или p2p соединение через ISteamNetworkingMessages
Разрабатывая вторую игру на Unity я решил замахнуться на кооперативный режим. Так как новая игра тоже выйдет на площадке Steam, сервисы стима уже интегрированны, а взнос за приложение уже уплачен, было решено попробовать сетевые сервисы стима. Steam заявляет что они очень круто работают, сервера расположены по всему миру (спойлер, это не так), работать с ними просто, а главное работает всё быстро.
Так как использовать сервер я не хочу, мне больше всего подходит вариант p2p соединения, и у Steam такое есть (даже два).
Как я уже сказал, у Steam есть два сетевых интерфейса ориентированных на p2p. Первый называется ISteamNetworking, в документации стим пишет что он устарел, и его уже даже удалять хотят. Я разумеется этой строки не заметил, и сначала написал все на этом интерфейсе. Кстати, про него я нашел пару англоязычных статей.
Актуальный интерфейс называется ISteamNetworkingMessages. Работает на UDP(точнее поверх ISteamNetworkingSockets). И пересылает все пакеты через ближайший стимовский сервер (из за этого, кстати, есть некоторые проблемы с пингом).
Собственно, главная проблема этого интерфейса, это практически полное отсутствие информации, за исключением документации Steam. Собственно, поэтому я и решил написать эту статью.
Наконец к практике
Первым делом вам надо сделать какое-то лобби, чтобы получить SteamID будущих игроков. О создании лобби уже до меня написано куча статей, поэтому на этом не буду заострять внимание.
У меня лобби выглядит примерно так
Для работы понадобится какая-то структура, которая будет содержать передаваемую информацию. У меня используется класс с названием Package, в котором просто написана куча конверторов, в том числе и в структуру. Эту структуру мы в дальнейшем будем маршалировать при помощи библиотеки Marshal. Так как новый интерфейс принимает указатель IntPrt, а не бинарный массив как старый.
В этой структуре у нас будет содержаться SteamID пользователя, от которого пришло сообщение, длинна сообщения и, собственно, бинарный массив с самим сообщением, в который мы будем загонять информацию путем сереализации. Для того что бы библиотека Marshal могла маршалировать нашу структуру, нам надо указать фиксированный размер нашего сообщения. А длину запоминаем что бы потом суметь его потом правильно прочитать.
В качесве message наверное лучше тоже использовать результат маршалинга, но у меня вся логика была написана под бинарные массивы, да и с ними, мне кажется, работать попроще, хотя может и медленнее.
Дальше, в принципе, уже можем отправлять сообщения. Но сначала напишем метод, который будет принимать и запоминать StemID наших игроков:
При первой отправке сообщения, и возможно ещё в каких то ситуациях, должно произойти рукопожатие. В документации этот момент описан так:
«Если у нас еще нет сеанса с этим пользователем, сеанс создается неявно. Возможно, должно произойти некоторое рукопожатие, прежде чем мы действительно сможем начать отправлять данные сообщений.»
Для этого создаем метод, который будет отвечать на это рукопожатие:
Где ExpectingClient это метод который вернет true если мы готовы этому пользователю «пожать руку». В моём случае выглядит так:
Для того чтобы наш метод SteamNetworkingMessagesSessionRequest обрабатывался, нам надо при старте создать поле обратного вызова:
Теперь можно попыться что-то отправить:
В этом методе cubData это размер пересылаемого сообщения. sendflag это флаг, используемый для отправки сообщений. Может быть следующий:
Чтобы не заморачиваться я просто отправляю сообщения всем клиентам (разумеется кроме себя):
Отправить это конечно хорошо, но надо бы и что то получить. Тут немного сложнее. Для чтения используется метод:
В котором первая цифра, это тот самый номер канала, outMessages это массив принятых сообщений (за раз их может несколько),а readPacketCount это, как раз, максимальное количество сообщений, которое мы хотим прочитать.
В итоге чтение будет выглядеть примерно так:
На этом в принципе и всё. Далее потребуется ещё какой-нибудь класс, который будет управлять всеми этими функциями, но это тема уже отдельной статьи.
Что такое p2p steam
Введение
История Steam как многопользовательской игровой платформы началась с выпуска Counter-Strike 1.6, и многопользовательский режим всегда был в центре внимания. В Steam представлено множество возможностей и API, которые позволяют добиться наилучших ощущений как во время многопользовательской игры, так и во время разработки многопользовательского режима. Разработчик может предоставить игрокам возможность находить друг друга и играть между собой с помощью API подбора игр, а с помощью API игровых серверов — предоставить игрокам постоянные выделенные сервера, вокруг которых они смогут построить игровые сообщества.
Для поддержки многопользовательского режима в Steam доступны множество возможностей. Прежде чем начать полезно ответить на следующие вопросы:
Поддерживается ли игра по сети?
Одноранговая сеть или клиент-серверная модель?
В зависимости от типа игры предпочтение должно быть отдано одному из типов организации сети.
Если игра является высококонкурентной (к примеру, как киберспорт), рекомендуется использовать Игровые серверы Steam.
Если в игре необходима поддержка групп игроков небольшого размера, предпочтительнее использовать Подбор игр и лобби и Одноранговая сеть Steam.
Поддержка голосового чата?
Возможности
Подбор игр и лобби
Эта функция является системообразующей при совместной игре. Лобби позволяют игрокам собраться вместе, прежде чем будет запущена игровая сессия. Подбор игр и лобби сами по себе не поддерживают сетевое соединение, поэтому потребуется внедрение одноранговой сети, игровых серверов или иного сетевого режима, встроенного в движок игры.
Подробнее подбор игр и лобби описаны в соответствующем разделе документации.
Одноранговая сеть Steam
Одноранговая сеть может быть использована для небольших групп игроков.
Игровые серверы Steam
API игровых серверов предоставляет все необходимые инструменты для создания выделенных игровых серверов. Вы можете разместить сервера самостоятельно или предоставить сообществу возможность заниматься ими. Подобные сервера наилучшим образом подходят в случае с высококонкурентными играми, такими как Dota 2, или в случае с постоянными серверами, которые продолжают работать, даже когда все игроки покинули игру, как в Team Fortress 2.
Подробно игровые сервера описаны в соответствующем разделе документации.
Что такое p2p steam
Сетевые функции для организации связи и отправки данных между клиентами. По возможности используется прохождение NAT.
NOTE: This API is deprecated and may be removed in a future Steamworks SDK release. Please use ISteamNetworkingSockets or ISteamNetworkingMessages instead. See the Сетевые функции Steam overview for more information.
Функции-члены
AcceptP2PSessionWithUser
Название | Тип | Описание |
---|---|---|
steamIDRemote | CSteamID | SteamID пользователя, отправившего изначальный пакет. |
This allows the game to specify accept an incoming packet. This needs to be called before a real connection is established to a remote host, the game will get a chance to say whether or not the remote user is allowed to talk to them.
When a remote user that you haven’t sent a packet to recently, tries to first send you a packet, your game will receive a callback P2PSessionRequest_t. This callback contains the Steam ID of the user who wants to send you a packet. In response to this callback, you’ll want to see if it’s someone you want to talk to (for example, if they’re in a lobby with you), and if so, accept the connection; otherwise if you don’t want to talk to the user, just ignore the request. If the user continues to send you packets, another P2PSessionRequest_t will be posted periodically. If you’ve called SendP2PPacket on the other user, this implicitly accepts the session request.
Note that this call should only be made in response to a P2PSessionRequest_t callback!
Returns: bool
true upon success; false only if steamIDRemote is invalid.
AllowP2PPacketRelay
Название | Тип | Описание |
---|---|---|
bAllow | bool |
Allow or disallow P2P connections to fall back to being relayed through the Steam servers if a direct connection or NAT-traversal cannot be established.
This only applies to connections created after setting this value, or to existing connections that need to automatically reconnect after this value is set.
P2P packet relay is allowed by default.
Возвращает: bool
This function always returns true.
CloseP2PChannelWithUser
Название | Тип | Описание |
---|---|---|
steamIDRemote | CSteamID | SteamID пользователя, соединение с которым требуется закрыть. |
nChannel | int | Канал, который требуется закрыть. |
Closes a P2P channel when you’re done talking to a user on the specific channel.
Once all channels to a user have been closed, the open session to the user will be closed and new data from this user will trigger a new P2PSessionRequest_t callback.
Returns: bool
true if the channel was successfully closed; otherwise, false if there was no active session or channel with the user.
CloseP2PSessionWithUser
Название | Тип | Описание |
---|---|---|
steamIDRemote | CSteamID | SteamID пользователя, соединение с которым требуется закрыть. |
This should be called when you’re done communicating with a user, as this will free up all of the resources allocated for the connection under-the-hood.
If the remote user tries to send data to you again, a new P2PSessionRequest_t callback will be posted.
CreateConnectionSocket
Название | Тип | Описание |
---|---|---|
nIP | uint32 | |
nPort | uint16 | |
nTimeoutSec | int |
Creates a socket and begin connection to a remote destination.
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: SNetSocket_t
CreateListenSocket
Название | Тип | Описание |
---|---|---|
nVirtualP2PPort | int | |
nIP | uint32 | |
nPort | uint16 | |
bAllowUseOfPacketRelay | bool |
Creates a socket and listens others to connect.
Will trigger a SocketStatusCallback_t callback on another client connecting.
nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports
this can usually just be 0 unless you want multiple sets of connections
unIP is the local IP address to bind to
pass in 0 if you just want the default local IP
unPort is the port to use
pass in 0 if you don’t want users to be able to connect via IP/Port, but expect to be always peer-to-peer connections only
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
CreateP2PConnectionSocket
Название | Тип | Описание |
---|---|---|
steamIDTarget | CSteamID | |
nVirtualPort | int | |
nTimeoutSec | int | |
bAllowUseOfPacketRelay | bool |
Creates a socket and begin connection to a remote destination.
can connect via a known Steam ID (client or game server), or directly to an IP
on success will trigger a SocketStatusCallback_t callback
on failure or timeout will trigger a SocketStatusCallback_t callback with a failure code in m_eSNetSocketState
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: SNetSocket_t
DestroyListenSocket
Название | Тип | Описание |
---|---|---|
hSocket | SNetListenSocket_t | |
bNotifyRemoteEnd | bool |
Destroying a listen socket will automatically kill all the regular sockets generated from it.
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращаемые значения: bool
DestroySocket
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t | |
bNotifyRemoteEnd | bool |
Disconnects the connection to the socket, if any, and invalidates the handle.
any unread data on the socket will be thrown away
if bNotifyRemoteEnd is set, socket will not be completely destroyed until the remote end acknowledges the disconnect
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
GetListenSocketInfo
Название | Тип | Описание |
---|---|---|
hListenSocket | SNetListenSocket_t | |
pnIP | uint32 * | |
pnPort | uint16 * |
Returns which local port the listen socket is bound to.
*pnIP and *pnPort will be 0 if the socket is set to listen for P2P connections only
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
GetMaxPacketSize
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t |
Gets the max packet size, in bytes.
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: int
GetP2PSessionState
Название | Тип | Описание |
---|---|---|
steamIDRemote | CSteamID | Пользователь, информацию о статусе активной сессии которого необходимо получить. |
pConnectionState | P2PSessionState_t * | Возвращает статус. |
Fills out a P2PSessionState_t structure with details about the connection like whether or not there is an active connection; number of bytes queued on the connection; the last error code, if any; whether or not a relay server is being used; and the IP and Port of the remote user, if known
This should only needed for debugging purposes.
Returns: bool
true if pConnectionState was filled out; otherwise, false if there was no open session with the specified user.
Example:
GetSocketConnectionType
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t |
Returns true to describe how the socket ended up connecting.
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
GetSocketInfo
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t | |
pSteamIDRemote | CSteamID * | |
peSocketStatus | int * | |
punIPRemote | uint32 * | |
punPortRemote | uint16 * |
Returns information about the specified socket, filling out the contents of the pointers.
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
IsDataAvailable
Название | Тип | Описание |
---|---|---|
hListenSocket | SNetListenSocket_t | |
pcubMsgSize | uint32 * | |
phSocket | SNetSocket_t * |
Checks for data from any socket that has been connected off this listen socket.
returns false if there is no data remaining
fills out *pcubMsgSize with the size of the next message, in bytes
fills out *phSocket with the socket that data is available on
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
IsDataAvailableOnSocket
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t | |
pcubMsgSize | uint32 * |
Returns false if there is no data remaining.
fills out *pcubMsgSize with the size of the next message, in bytes
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
IsP2PPacketAvailable
Название | Тип | Описание |
---|---|---|
pcubMsgSize | uint32 * | Возвращает размер пакета. |
nChannel | int | Канал, в котором проверяется, есть ли в нём пакет. |
Checks if a P2P packet is available to read, and gets the size of the message if there is one.
This should be called in a loop for each channel that you use. If there is a packet available you should call ReadP2PPacket to get the packet data.
Returns: bool
true if there is a packet available; otherwise, false.
ReadP2PPacket
If the cubDest buffer is too small for the packet, then the message will be truncated.
This call is not blocking, and will return false if no data is available.
Before calling this you should have called IsP2PPacketAvailable.
Returns: bool
true if a packet was successfully read; otherwise, false if no packet was available.
Example:
RetrieveData
Название | Тип | Описание |
---|---|---|
hListenSocket | SNetListenSocket_t | |
pubDest | void * | |
cubDest | uint32 | |
pcubMsgSize | uint32 * | |
phSocket | SNetSocket_t * |
Retrieves data from any socket that has been connected off this listen socket.
fills in pubDest with the contents of the message
messages are always complete, of the same size as was sent (i.e. packetized, not streaming)
if *pcubMsgSize
returns false if no data is available
fills out *phSocket with the socket that data is available on
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
RetrieveDataFromSocket
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t | |
pubDest | void * | |
cubDest | uint32 | |
pcubMsgSize | uint32 * |
Fills in pubDest with the contents of the message.
messages are always complete, of the same size as was sent (i.e. packetized, not streaming)
if *pcubMsgSize
returns false if no data is available
This is part of an older set of functions designed around the Berkeley TCP sockets model it’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Возвращает: bool
SendDataOnSocket
Название | Тип | Описание |
---|---|---|
hSocket | SNetSocket_t | |
pubData | void * | |
cubData | uint32 | |
bReliable | bool |
sending data
must be a handle to a connected socket
data is all sent via UDP, and thus send sizes are limited to 1200 bytes; after this, many routers will start dropping packets
use the reliable flag with caution; although the resend rate is pretty aggressive,
it can still cause stalls in receiving data (like TCP)
Returns: bool
SendP2PPacket
This is a session-less API which automatically establishes NAT-traversing or Steam relay server connections.
NOTE: The first packet send may be delayed as the NAT-traversal code runs.
See EP2PSend for descriptions of the different ways of sending packets.
The type of data you send is arbitrary, you can use an off the shelf system like Protocol Buffers or Cap’n Proto to encode your packets in an efficient way, or you can create your own messaging system.
Returns: bool
Triggers a P2PSessionRequest_t callback.
true if the packet was successfully sent.
Note that this does not mean successfully received, if we can’t get through to the user after a timeout of 20 seconds, then an error will be posted via the P2PSessionConnectFail_t callback.
Обратные вызовы
P2PSessionConnectFail_t
Called when packets can’t get through to the specified user.
All queued packets unsent at this point will be dropped, further attempts to send will retry making the connection (but will be dropped if we fail again).
Название | Тип | Описание |
---|---|---|
m_steamIDRemote | CSteamID | Пользователь, которому мы пытаемся отправить пакет. |
m_eP2PSessionError | uint8 | Показывает причину, почему у нас возникли трудности. В действительности — EP2PSessionError. |
P2PSessionRequest_t
A user wants to communicate with us over the P2P channel via the SendP2PPacket. In response, a call to AcceptP2PSessionWithUser needs to be made, if you want to open the network channel with them.
Название | Тип | Описание |
---|---|---|
m_steamIDRemote | CSteamID | Пользователь, который хочет начать P2P-сессию. |
Associated Functions: SendP2PPacket
SocketStatusCallback_t
Called when the status of a socket has changed, used as part of the CreateListenSocket and CreateP2PConnectionSocket calls.
This is part of an older set of functions designed around the Berkeley TCP sockets model. It’s preferential that you use the P2P functions, they’re more robust and these older functions will be removed eventually.
Название | Тип | Описание |
---|---|---|
m_hSocket | SNetSocket_t | сокет, используемый для отправки и получения данных от удалённого узла |
m_hListenSocket | SNetListenSocket_t | это серверный сокет, который мы слушаем. NULL, если это было исходящее соединение |
m_steamIDRemote | CSteamID | удалённый SteamID, к которому мы соединились, если он есть |
m_eSNetSocketState | int | состояние сокета, ESNetSocketState |
Структуры
P2PSessionState_t
Connection state to a specified user, returned by GetP2PSessionState. This is the under-the-hood info about what’s going on with a previous call to SendP2PPacket. This typically shouldn’t be needed except for debugging purposes.
Название | Тип | Описание |
---|---|---|
m_bConnectionActive | uint8 | Есть ли у нас активное открытое соединение с пользователем (если да — true, если нет — false)? |
m_bConnecting | uint8 | Пытаемся ли мы установить соединение с пользователем в настоящий момент (если да — true, если нет — false)? |
m_eP2PSessionError | uint8 | Последняя ошибка, записанная для сокета. Возвращает EP2PSessionError. |
m_bUsingRelay | uint8 | Установлено ли соединение через серверы ретрансляции Steam (если да — true, если нет — false)? |
m_nBytesQueuedForSend | int32 | Число байтов в очереди для отправки пользователю. |
m_nPacketsQueuedForSend | int32 | Число пакетов в очереди для отправки пользователю. |
m_nRemoteIP | uint32 | IP-адрес удалённого узла, если он установлен. Может быть сервер ретрансляции Steam. Существует только для совместимости со старым API аутентификации. |
m_nRemotePort | uint16 | Порт удалённого узла, если он установлен. Может быть сервер ретрансляции Steam. Существует только для совместимости со старым API аутентификации. |
Перечисления
EP2PSend
Specifies the send type of SendP2PPacket.
Typically k_EP2PSendUnreliable is what you want for UDP-like packets, k_EP2PSendReliable for TCP-like packets
EP2PSessionError
List of possible errors returned by SendP2PPacket, these will be sent in the P2PSessionConnectFail_t callback.
Название | Значение | Описание |
---|---|---|
k_EP2PSessionErrorNone | 0 | Ошибки не было. |
k_EP2PSessionErrorNotRunningApp | 1 | У целевого пользователя не запущена та же игра. |
k_EP2PSessionErrorNoRightsToApp | 2 | Локальный пользователь не владеет запущенным приложением. |
k_EP2PSessionErrorDestinationNotLoggedIn | 3 | Целевой пользователь не соединён со Steam. |
k_EP2PSessionErrorTimeout | 4 | Время ожидания соединения истекло, потому что целевой пользователь не ответил, вероятно, он не вызывает AcceptP2PSessionWithUser. Корпоративные брандмауэры также могут блокировать это (прохождение NAT не является прохождением брандмауэра), поэтому убедитесь, что UDP-порты 3478, 4379 и 4380 в исходящем направлении открыты. |
k_EP2PSessionErrorMax | 5 | Не используется. |
ESNetSocketConnectionType
Describes how the socket is currently connected. Only used by the old networking API.
Название | Значение | Описание |
---|---|---|
k_ESNetSocketConnectionTypeNotConnected | 0 | |
k_ESNetSocketConnectionTypeUDP | 1 | |
k_ESNetSocketConnectionTypeUDPRelay | 2 |
ESNetSocketState
Connection progress indicators, used by CreateP2PConnectionSocket.
Название | Значение | Описание |
---|---|---|
k_ESNetSocketStateInvalid | 0 | |
k_ESNetSocketStateConnected | 1 | коммуникация действительна |
k_ESNetSocketStateInitiated | 10 | состояние при установлении соединения компьютер состояния соединения запущен |
k_ESNetSocketStateLocalCandidatesFound | 11 | P2P-соединение мы нашли локальную информацию об IP-адресе |
k_ESNetSocketStateReceivedRemoteCandidates | 12 | мы получили информацию об IP-адресе от удалённого компьютера через сервер Steam |
k_ESNetSocketStateChallengeHandshake | 15 | прямые соединения мы получили пакет запроса с сервера |
k_ESNetSocketStateDisconnecting | 21 | состояния сбоя API закрыт, и мы сейчас сообщаем другому концу |
k_ESNetSocketStateLocalDisconnect | 22 | API закрыт, и мы завершили закрытие |
k_ESNetSocketStateTimeoutDuringConnect | 23 | при попытке создания соединения время ожидания истекло |
k_ESNetSocketStateRemoteEndDisconnected | 24 | удалённый конец отсоединился от нас |
k_ESNetSocketStateConnectionBroken | 25 | соединение было нарушено: либо исчез собеседник, либо наше локальное сетевое соединение сломалось |
Typedefs
These are typedefs which are defined for use with ISteamNetworking.
Название | Базовый тип | Описание |
---|---|---|
SNetListenSocket_t | uint32 | CreateListenSocket() |
SNetSocket_t | uint32 | дескриптор сокета CreateP2PConnectionSocket() |
Константы
These are constants which are defined for use with ISteamNetworking.