mirror of
https://github.com/DrBeef/Quake2Quest.git
synced 2025-01-10 03:00:45 +00:00
297 lines
8.7 KiB
C
297 lines
8.7 KiB
C
/*
|
|
SDL_net: An example cross-platform network library for use with SDL
|
|
Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
/* $Id$ */
|
|
|
|
#include "SDLnetsys.h"
|
|
#include "SDL_net.h"
|
|
|
|
/* The network API for TCP sockets */
|
|
|
|
/* Since the UNIX/Win32/BeOS code is so different from MacOS,
|
|
we'll just have two completely different sections here.
|
|
*/
|
|
|
|
struct _TCPsocket {
|
|
int ready;
|
|
SOCKET channel;
|
|
IPaddress remoteAddress;
|
|
IPaddress localAddress;
|
|
int sflag;
|
|
};
|
|
|
|
/* Open a TCP network socket
|
|
If 'remote' is NULL, this creates a local server socket on the given port,
|
|
otherwise a TCP connection to the remote host and port is attempted.
|
|
The newly created socket is returned, or NULL if there was an error.
|
|
*/
|
|
TCPsocket SDLNet_TCP_Open(IPaddress *ip)
|
|
{
|
|
TCPsocket sock;
|
|
struct sockaddr_in sock_addr;
|
|
|
|
/* Allocate a TCP socket structure */
|
|
sock = (TCPsocket)malloc(sizeof(*sock));
|
|
if ( sock == NULL ) {
|
|
SDLNet_SetError("Out of memory");
|
|
goto error_return;
|
|
}
|
|
|
|
/* Open the socket */
|
|
sock->channel = socket(AF_INET, SOCK_STREAM, 0);
|
|
if ( sock->channel == INVALID_SOCKET ) {
|
|
SDLNet_SetError("Couldn't create socket");
|
|
goto error_return;
|
|
}
|
|
|
|
/* Connect to remote, or bind locally, as appropriate */
|
|
if ( (ip->host != INADDR_NONE) && (ip->host != INADDR_ANY) ) {
|
|
|
|
// ######### Connecting to remote
|
|
|
|
memset(&sock_addr, 0, sizeof(sock_addr));
|
|
sock_addr.sin_family = AF_INET;
|
|
sock_addr.sin_addr.s_addr = ip->host;
|
|
sock_addr.sin_port = ip->port;
|
|
|
|
/* Connect to the remote host */
|
|
if ( connect(sock->channel, (struct sockaddr *)&sock_addr,
|
|
sizeof(sock_addr)) == SOCKET_ERROR ) {
|
|
SDLNet_SetError("Couldn't connect to remote host");
|
|
goto error_return;
|
|
}
|
|
sock->sflag = 0;
|
|
} else {
|
|
|
|
// ########## Binding locally
|
|
|
|
memset(&sock_addr, 0, sizeof(sock_addr));
|
|
sock_addr.sin_family = AF_INET;
|
|
sock_addr.sin_addr.s_addr = INADDR_ANY;
|
|
sock_addr.sin_port = ip->port;
|
|
|
|
/*
|
|
* Windows gets bad mojo with SO_REUSEADDR:
|
|
* http://www.devolution.com/pipermail/sdl/2005-September/070491.html
|
|
* --ryan.
|
|
*/
|
|
#ifndef WIN32
|
|
/* allow local address reuse */
|
|
{ int yes = 1;
|
|
setsockopt(sock->channel, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
|
|
}
|
|
#endif
|
|
|
|
/* Bind the socket for listening */
|
|
if ( bind(sock->channel, (struct sockaddr *)&sock_addr,
|
|
sizeof(sock_addr)) == SOCKET_ERROR ) {
|
|
SDLNet_SetError("Couldn't bind to local port");
|
|
goto error_return;
|
|
}
|
|
if ( listen(sock->channel, 5) == SOCKET_ERROR ) {
|
|
SDLNet_SetError("Couldn't listen to local port");
|
|
goto error_return;
|
|
}
|
|
|
|
/* Set the socket to non-blocking mode for accept() */
|
|
#if defined(__BEOS__) && defined(SO_NONBLOCK)
|
|
/* On BeOS r5 there is O_NONBLOCK but it's for files only */
|
|
{
|
|
long b = 1;
|
|
setsockopt(sock->channel, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
|
|
}
|
|
#elif defined(O_NONBLOCK)
|
|
{
|
|
fcntl(sock->channel, F_SETFL, O_NONBLOCK);
|
|
}
|
|
#elif defined(WIN32)
|
|
{
|
|
unsigned long mode = 1;
|
|
ioctlsocket (sock->channel, FIONBIO, &mode);
|
|
}
|
|
#elif defined(__OS2__)
|
|
{
|
|
int dontblock = 1;
|
|
ioctl(sock->channel, FIONBIO, &dontblock);
|
|
}
|
|
#else
|
|
#warning How do we set non-blocking mode on other operating systems?
|
|
#endif
|
|
sock->sflag = 1;
|
|
}
|
|
sock->ready = 0;
|
|
|
|
#ifdef TCP_NODELAY
|
|
/* Set the nodelay TCP option for real-time games */
|
|
{ int yes = 1;
|
|
setsockopt(sock->channel, IPPROTO_TCP, TCP_NODELAY, (char*)&yes, sizeof(yes));
|
|
}
|
|
#else
|
|
#warning Building without TCP_NODELAY
|
|
#endif /* TCP_NODELAY */
|
|
|
|
/* Fill in the channel host address */
|
|
sock->remoteAddress.host = sock_addr.sin_addr.s_addr;
|
|
sock->remoteAddress.port = sock_addr.sin_port;
|
|
|
|
/* The socket is ready */
|
|
return(sock);
|
|
|
|
error_return:
|
|
SDLNet_TCP_Close(sock);
|
|
return(NULL);
|
|
}
|
|
|
|
/* Accept an incoming connection on the given server socket.
|
|
The newly created socket is returned, or NULL if there was an error.
|
|
*/
|
|
TCPsocket SDLNet_TCP_Accept(TCPsocket server)
|
|
{
|
|
TCPsocket sock;
|
|
struct sockaddr_in sock_addr;
|
|
socklen_t sock_alen;
|
|
|
|
/* Only server sockets can accept */
|
|
if ( ! server->sflag ) {
|
|
SDLNet_SetError("Only server sockets can accept()");
|
|
return(NULL);
|
|
}
|
|
server->ready = 0;
|
|
|
|
/* Allocate a TCP socket structure */
|
|
sock = (TCPsocket)malloc(sizeof(*sock));
|
|
if ( sock == NULL ) {
|
|
SDLNet_SetError("Out of memory");
|
|
goto error_return;
|
|
}
|
|
|
|
/* Accept a new TCP connection on a server socket */
|
|
sock_alen = sizeof(sock_addr);
|
|
sock->channel = accept(server->channel, (struct sockaddr *)&sock_addr,
|
|
&sock_alen);
|
|
if ( sock->channel == INVALID_SOCKET ) {
|
|
SDLNet_SetError("accept() failed");
|
|
goto error_return;
|
|
}
|
|
#ifdef WIN32
|
|
{
|
|
/* passing a zero value, socket mode set to block on */
|
|
unsigned long mode = 0;
|
|
ioctlsocket (sock->channel, FIONBIO, &mode);
|
|
}
|
|
#elif defined(O_NONBLOCK)
|
|
{
|
|
int flags = fcntl(sock->channel, F_GETFL, 0);
|
|
fcntl(sock->channel, F_SETFL, flags & ~O_NONBLOCK);
|
|
}
|
|
#endif /* WIN32 */
|
|
sock->remoteAddress.host = sock_addr.sin_addr.s_addr;
|
|
sock->remoteAddress.port = sock_addr.sin_port;
|
|
|
|
sock->sflag = 0;
|
|
sock->ready = 0;
|
|
|
|
/* The socket is ready */
|
|
return(sock);
|
|
|
|
error_return:
|
|
SDLNet_TCP_Close(sock);
|
|
return(NULL);
|
|
}
|
|
|
|
/* Get the IP address of the remote system associated with the socket.
|
|
If the socket is a server socket, this function returns NULL.
|
|
*/
|
|
IPaddress *SDLNet_TCP_GetPeerAddress(TCPsocket sock)
|
|
{
|
|
if ( sock->sflag ) {
|
|
return(NULL);
|
|
}
|
|
return(&sock->remoteAddress);
|
|
}
|
|
|
|
/* Send 'len' bytes of 'data' over the non-server socket 'sock'
|
|
This function returns the actual amount of data sent. If the return value
|
|
is less than the amount of data sent, then either the remote connection was
|
|
closed, or an unknown socket error occurred.
|
|
*/
|
|
int SDLNet_TCP_Send(TCPsocket sock, const void *datap, int len)
|
|
{
|
|
const Uint8 *data = (const Uint8 *)datap; /* For pointer arithmetic */
|
|
int sent, left;
|
|
|
|
/* Server sockets are for accepting connections only */
|
|
if ( sock->sflag ) {
|
|
SDLNet_SetError("Server sockets cannot send");
|
|
return(-1);
|
|
}
|
|
|
|
/* Keep sending data until it's sent or an error occurs */
|
|
left = len;
|
|
sent = 0;
|
|
SDLNet_SetLastError(0);
|
|
do {
|
|
len = send(sock->channel, (const char *) data, left, 0);
|
|
if ( len > 0 ) {
|
|
sent += len;
|
|
left -= len;
|
|
data += len;
|
|
}
|
|
} while ( (left > 0) && ((len > 0) || (SDLNet_GetLastError() == EINTR)) );
|
|
|
|
return(sent);
|
|
}
|
|
|
|
/* Receive up to 'maxlen' bytes of data over the non-server socket 'sock',
|
|
and store them in the buffer pointed to by 'data'.
|
|
This function returns the actual amount of data received. If the return
|
|
value is less than or equal to zero, then either the remote connection was
|
|
closed, or an unknown socket error occurred.
|
|
*/
|
|
int SDLNet_TCP_Recv(TCPsocket sock, void *data, int maxlen)
|
|
{
|
|
int len;
|
|
|
|
/* Server sockets are for accepting connections only */
|
|
if ( sock->sflag ) {
|
|
SDLNet_SetError("Server sockets cannot receive");
|
|
return(-1);
|
|
}
|
|
|
|
SDLNet_SetLastError(0);
|
|
do {
|
|
len = recv(sock->channel, (char *) data, maxlen, 0);
|
|
} while ( SDLNet_GetLastError() == EINTR );
|
|
|
|
sock->ready = 0;
|
|
return(len);
|
|
}
|
|
|
|
/* Close a TCP network socket */
|
|
void SDLNet_TCP_Close(TCPsocket sock)
|
|
{
|
|
if ( sock != NULL ) {
|
|
if ( sock->channel != INVALID_SOCKET ) {
|
|
closesocket(sock->channel);
|
|
}
|
|
free(sock);
|
|
}
|
|
}
|