An attempt to fix socket's behavior inconsistencies across platforms.

Added an option to override minimum socket's timeout to prevent getting timeouts on high latency networks.
This commit is contained in:
ANR2ME 2020-01-18 02:32:09 +07:00 committed by Henrik Rydgård
parent d9756e9231
commit d0045947f1
8 changed files with 115 additions and 19 deletions

View file

@ -955,7 +955,10 @@ static ConfigSetting controlSettings[] = {
static ConfigSetting networkSettings[] = { static ConfigSetting networkSettings[] = {
ConfigSetting("EnableWlan", &g_Config.bEnableWlan, false, true, true), ConfigSetting("EnableWlan", &g_Config.bEnableWlan, false, true, true),
ConfigSetting("EnableAdhocServer", &g_Config.bEnableAdhocServer, false, true, true), ConfigSetting("EnableAdhocServer", &g_Config.bEnableAdhocServer, false, true, true),
ConfigSetting("proAdhocServer", &g_Config.proAdhocServer, "myneighborsushicat.com", true, true),
ConfigSetting("PortOffset", &g_Config.iPortOffset, 0, true, true),
ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, true, true), ConfigSetting("EnableUPnP", &g_Config.bEnableUPnP, false, true, true),
ConfigSetting("MinTimeout", &g_Config.iMinTimeout, 1, true, true),
ConfigSetting("EnableNetworkChat", &g_Config.bEnableNetworkChat, false, true, true), ConfigSetting("EnableNetworkChat", &g_Config.bEnableNetworkChat, false, true, true),
ConfigSetting("ChatButtonPosition",&g_Config.iChatButtonPosition,BOTTOM_LEFT,true,true), ConfigSetting("ChatButtonPosition",&g_Config.iChatButtonPosition,BOTTOM_LEFT,true,true),
@ -995,9 +998,7 @@ static ConfigSetting systemParamSettings[] = {
ReportedConfigSetting("PSPModel", &g_Config.iPSPModel, &DefaultPSPModel, true, true), ReportedConfigSetting("PSPModel", &g_Config.iPSPModel, &DefaultPSPModel, true, true),
ReportedConfigSetting("PSPFirmwareVersion", &g_Config.iFirmwareVersion, PSP_DEFAULT_FIRMWARE, true, true), ReportedConfigSetting("PSPFirmwareVersion", &g_Config.iFirmwareVersion, PSP_DEFAULT_FIRMWARE, true, true),
ConfigSetting("NickName", &g_Config.sNickName, "PPSSPP", true, true), ConfigSetting("NickName", &g_Config.sNickName, "PPSSPP", true, true),
ConfigSetting("proAdhocServer", &g_Config.proAdhocServer, "myneighborsushicat.com", true, true),
ConfigSetting("MacAddress", &g_Config.sMACAddress, "", true, true), ConfigSetting("MacAddress", &g_Config.sMACAddress, "", true, true),
ConfigSetting("PortOffset", &g_Config.iPortOffset, 0, true, true),
ReportedConfigSetting("Language", &g_Config.iLanguage, &DefaultSystemParamLanguage, true, true), ReportedConfigSetting("Language", &g_Config.iLanguage, &DefaultSystemParamLanguage, true, true),
ConfigSetting("ParamTimeFormat", &g_Config.iTimeFormat, PSP_SYSTEMPARAM_TIME_FORMAT_24HR, true, true), ConfigSetting("ParamTimeFormat", &g_Config.iTimeFormat, PSP_SYSTEMPARAM_TIME_FORMAT_24HR, true, true),
ConfigSetting("ParamDateFormat", &g_Config.iDateFormat, PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD, true, true), ConfigSetting("ParamDateFormat", &g_Config.iDateFormat, PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD, true, true),

View file

@ -396,9 +396,7 @@ public:
// SystemParam // SystemParam
std::string sNickName; std::string sNickName;
std::string proAdhocServer;
std::string sMACAddress; std::string sMACAddress;
int iPortOffset;
int iLanguage; int iLanguage;
int iTimeFormat; int iTimeFormat;
int iDateFormat; int iDateFormat;
@ -410,9 +408,12 @@ public:
bool bSavedataUpgrade; bool bSavedataUpgrade;
// Networking // Networking
std::string proAdhocServer;
bool bEnableWlan; bool bEnableWlan;
bool bEnableAdhocServer; bool bEnableAdhocServer;
bool bEnableUPnP; bool bEnableUPnP;
int iPortOffset;
int iMinTimeout;
int iWlanAdhocChannel; int iWlanAdhocChannel;
bool bWlanPowerSave; bool bWlanPowerSave;
bool bEnableNetworkChat; bool bEnableNetworkChat;

View file

@ -42,7 +42,8 @@
#include "proAdhoc.h" #include "proAdhoc.h"
#include "i18n/i18n.h" #include "i18n/i18n.h"
uint16_t portOffset = g_Config.iPortOffset; uint16_t portOffset;
uint32_t minSocketTimeoutUS;
uint32_t fakePoolSize = 0; uint32_t fakePoolSize = 0;
SceNetAdhocMatchingContext * contexts = NULL; SceNetAdhocMatchingContext * contexts = NULL;
int one = 1; int one = 1;
@ -215,8 +216,8 @@ SceNetAdhocctlPeerInfo * findFriend(SceNetEtherAddr * MAC) {
void changeBlockingMode(int fd, int nonblocking) { void changeBlockingMode(int fd, int nonblocking) {
unsigned long on = 1; unsigned long on = 1;
unsigned long off = 0; unsigned long off = 0;
#ifdef _WIN32 #if defined(_WIN32)
if (nonblocking){ if (nonblocking) {
// Change to Non-Blocking Mode // Change to Non-Blocking Mode
ioctlsocket(fd, FIONBIO, &on); ioctlsocket(fd, FIONBIO, &on);
} }
@ -224,14 +225,31 @@ void changeBlockingMode(int fd, int nonblocking) {
// Change to Blocking Mode // Change to Blocking Mode
ioctlsocket(fd, FIONBIO, &off); ioctlsocket(fd, FIONBIO, &off);
} }
// If they have O_NONBLOCK, use the POSIX way to do it. On POSIX sockets Error code would be EINPROGRESS instead of EAGAIN
//#elif defined(O_NONBLOCK)
#else #else
if(nonblocking == 1) fcntl(fd, F_SETFL, O_NONBLOCK); int flags = fcntl(fd, F_GETFL, 0);
// Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5.
if (flags == -1)
flags = 0;
if (nonblocking) {
// Set Non-Blocking Flag
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
else { else {
// Get Flags
int flags = fcntl(fd, F_GETFL);
// Remove Non-Blocking Flag // Remove Non-Blocking Flag
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
} }
// Otherwise, use the old way of doing it (UNIX way). On UNIX sockets Error code would be EAGAIN instead of EINPROGRESS
/*#else
if (nonblocking) {
// Change to Non - Blocking Mode
ioctl(fd, FIONBIO, (char*)&on);
}
else {
// Change to Blocking Mode
ioctl(fd, FIONBIO, (char*)&off);
}*/
#endif #endif
} }
@ -1613,6 +1631,16 @@ uint16_t getLocalPort(int sock) {
return ntohs(localAddr.sin_port); return ntohs(localAddr.sin_port);
} }
int getSockMaxSize(int udpsock) {
#if !defined(SO_MAX_MSG_SIZE)
#define SO_MAX_MSG_SIZE 0x2003
#endif
int n = 1500; // Typical MTU size as default
socklen_t m = sizeof(n);
getsockopt(udpsock, SOL_SOCKET, SO_MAX_MSG_SIZE, (char*)&n, &m);
return n;
}
int getSockBufferSize(int sock, int opt) { // opt = SO_RCVBUF/SO_SNDBUF int getSockBufferSize(int sock, int opt) { // opt = SO_RCVBUF/SO_SNDBUF
int n = 16384; int n = 16384;
socklen_t m = sizeof(n); socklen_t m = sizeof(n);
@ -1625,22 +1653,45 @@ int setSockBufferSize(int sock, int opt, int size) { // opt = SO_RCVBUF/SO_SNDBU
return setsockopt(sock, SOL_SOCKET, opt, (char *)&n, sizeof(n)); return setsockopt(sock, SOL_SOCKET, opt, (char *)&n, sizeof(n));
} }
int setSockTimeout(int sock, int opt, unsigned long timeout_usec) { // opt = SO_SNDTIMEO/SO_RCVTIMEO
if (timeout_usec > 0 && timeout_usec < minSocketTimeoutUS) timeout_usec = minSocketTimeoutUS; // Override timeout for high latency multiplayer
#if defined(_WIN32)
unsigned long optval = timeout_usec / 1000UL;
if (timeout_usec > 0 && optval == 0) optval = 1; // Since there are games that use 100 usec timeout, we should set it to minimum value on Windows (1 msec) instead of using 0 (0 = indefinitely timeout)
#else
struct timeval optval = { static_cast<long>(timeout_usec) / 1000000L, static_cast<long>(timeout_usec) % 1000000L };
#endif
return setsockopt(sock, SOL_SOCKET, opt, (char*)&optval, sizeof(optval));
}
int getSockNoDelay(int tcpsock) {
int opt = 0;
socklen_t optlen = sizeof(opt);
getsockopt(tcpsock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, &optlen);
return opt;
}
int setSockNoDelay(int tcpsock, int flag) {
int opt = flag;
return setsockopt(tcpsock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt));
}
#if !defined(TCP_KEEPIDLE) #if !defined(TCP_KEEPIDLE)
#define TCP_KEEPIDLE TCP_KEEPALIVE //TCP_KEEPIDLE on Linux is equivalent to TCP_KEEPALIVE on macOS #define TCP_KEEPIDLE TCP_KEEPALIVE //TCP_KEEPIDLE on Linux is equivalent to TCP_KEEPALIVE on macOS
#endif #endif
int setSockKeepAlive(int sock, bool keepalive, const int keepcnt, const int keepidle, const int keepinvl) { int setSockKeepAlive(int sock, bool keepalive, const int keepinvl, const int keepcnt, const int keepidle) {
int optval = keepalive ? 1 : 0; int optval = keepalive ? 1 : 0;
int optlen = sizeof(optval); int optlen = sizeof(optval);
int result = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&optval, optlen); int result = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&optval, optlen);
if (result == 0 && keepalive) { if (result == 0 && keepalive) {
if (getsockopt(sock, SOL_SOCKET, SO_TYPE, (char*)&optval, (socklen_t*)&optlen) == 0 && optval == SOCK_STREAM) { if (getsockopt(sock, SOL_SOCKET, SO_TYPE, (char*)&optval, (socklen_t*)&optlen) == 0 && optval == SOCK_STREAM) {
optlen = sizeof(optval); optlen = sizeof(optval);
optval = keepidle; //180 sec
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&optval, optlen);
optval = keepinvl; //60 sec
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&optval, optlen);
optval = keepcnt; //20 optval = keepcnt; //20
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&optval, optlen); setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (char*)&optval, optlen);
optval = keepidle; //180
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, (char*)&optval, optlen);
optval = keepinvl; //60
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (char*)&optval, optlen);
} }
} }
return result; return result;

View file

@ -65,6 +65,7 @@
#undef EINPROGRESS #undef EINPROGRESS
#undef EISCONN #undef EISCONN
#undef EALREADY #undef EALREADY
#undef ETIMEDOUT
#define errno WSAGetLastError() #define errno WSAGetLastError()
#define ECONNABORTED WSAECONNABORTED #define ECONNABORTED WSAECONNABORTED
#define ECONNRESET WSAECONNRESET #define ECONNRESET WSAECONNRESET
@ -73,12 +74,13 @@
#define EINPROGRESS WSAEWOULDBLOCK #define EINPROGRESS WSAEWOULDBLOCK
#define EISCONN WSAEISCONN #define EISCONN WSAEISCONN
#define EALREADY WSAEALREADY #define EALREADY WSAEALREADY
#define ETIMEDOUT WSAETIMEDOUT
inline bool connectInProgress(int errcode){ return (errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEALREADY); } inline bool connectInProgress(int errcode){ return (errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEALREADY); }
#else #else
#define INVALID_SOCKET -1 #define INVALID_SOCKET -1
#define SOCKET_ERROR -1 #define SOCKET_ERROR -1
#define closesocket close #define closesocket close
inline bool connectInProgress(int errcode){ return (errcode == EINPROGRESS || errcode == EALREADY); } inline bool connectInProgress(int errcode){ return (errcode == EAGAIN || errcode == EWOULDBLOCK || errcode == EINPROGRESS || errcode == EALREADY); }
#endif #endif
#ifndef POLL_ERR #ifndef POLL_ERR
@ -841,6 +843,7 @@ extern SceNetAdhocPdpStat * pdp[255];
extern SceNetAdhocPtpStat * ptp[255]; extern SceNetAdhocPtpStat * ptp[255];
extern uint16_t portOffset; extern uint16_t portOffset;
extern uint32_t minSocketTimeoutUS;
extern bool isLocalServer; extern bool isLocalServer;
extern sockaddr LocalhostIP; // Used to differentiate localhost IP on multiple-instance extern sockaddr LocalhostIP; // Used to differentiate localhost IP on multiple-instance
extern sockaddr LocalIP; // IP of Network Adapter used to connect to Adhoc Server (LAN/WAN) extern sockaddr LocalIP; // IP of Network Adapter used to connect to Adhoc Server (LAN/WAN)
@ -1155,6 +1158,11 @@ int getActivePeerCount(const bool excludeTimedout = true);
int getLocalIp(sockaddr_in * SocketAddress); int getLocalIp(sockaddr_in * SocketAddress);
uint32_t getLocalIp(int sock); uint32_t getLocalIp(int sock);
/*
* Get UDP Socket Max Message Size
*/
int getSockMaxSize(int udpsock);
/* /*
* Get Socket Buffer Size (opt = SO_RCVBUF/SO_SNDBUF) * Get Socket Buffer Size (opt = SO_RCVBUF/SO_SNDBUF)
*/ */
@ -1165,10 +1173,25 @@ int getSockBufferSize(int sock, int opt);
*/ */
int setSockBufferSize(int sock, int opt, int size); int setSockBufferSize(int sock, int opt, int size);
/*
* Set Socket TimeOut (opt = SO_SNDTIMEO/SO_RCVTIMEO)
*/
int setSockTimeout(int sock, int opt, unsigned long timeout_usec);
/*
* Get TCP Socket TCP_NODELAY (Nagle Algo)
*/
int getSockNoDelay(int tcpsock);
/*
* Set TCP Socket TCP_NODELAY (Nagle Algo)
*/
int setSockNoDelay(int tcpsock, int flag);
/* /*
* Set Socket KeepAlive (opt = SO_KEEPALIVE) * Set Socket KeepAlive (opt = SO_KEEPALIVE)
*/ */
int setSockKeepAlive(int sock, bool keepalive, const int keepcnt = 20, const int keepidle = 180, const int keepinvl = 60); int setSockKeepAlive(int sock, bool keepalive, const int keepinvl = 60, const int keepcnt = 20, const int keepidle = 180);
/** /**
* Return the Number of Players with the chosen Nickname in the Local Users current Network * Return the Number of Players with the chosen Nickname in the Local Users current Network

View file

@ -37,6 +37,7 @@
#else #else
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/tcp.h>
#endif #endif
#include <fcntl.h> #include <fcntl.h>
@ -476,6 +477,8 @@ const char * strcpyxml(char * out, const char * in, uint32_t size);
// Function Prototypes // Function Prototypes
void interrupt(int sig); void interrupt(int sig);
void enable_address_reuse(int fd); void enable_address_reuse(int fd);
void enable_keepalive(int fd);
void change_nodelay_mode(int fd, int flag);
void change_blocking_mode(int fd, int nonblocking); void change_blocking_mode(int fd, int nonblocking);
int create_listen_socket(uint16_t port); int create_listen_socket(uint16_t port);
int server_loop(int server); int server_loop(int server);
@ -1743,6 +1746,17 @@ void enable_keepalive(int fd)
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(on)); setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(on));
} }
/**
* Change TCP Socket TCP_NODELAY (Nagle Algo) mode
* @param fd Socket
* @param nonblocking 1 for Nonblocking, 0 for Blocking
*/
void change_nodelay_mode(int fd, int flag)
{
int opt = flag;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt));
}
/** /**
* Change Socket Blocking Mode * Change Socket Blocking Mode
* @param fd Socket * @param fd Socket
@ -1799,6 +1813,9 @@ int create_listen_socket(uint16_t port)
// Make Socket Nonblocking // Make Socket Nonblocking
change_blocking_mode(fd, 1); change_blocking_mode(fd, 1);
// Make TCP Socket send immediately
change_nodelay_mode(fd, 1);
// Prepare Local Address Information // Prepare Local Address Information
struct sockaddr_in local; struct sockaddr_in local;
memset(&local, 0, sizeof(local)); memset(&local, 0, sizeof(local));

View file

@ -115,7 +115,9 @@ static void __ResetInitNetLib() {
} }
void __NetInit() { void __NetInit() {
// Windows: Assuming WSAStartup already called beforehand
portOffset = g_Config.iPortOffset; portOffset = g_Config.iPortOffset;
minSocketTimeoutUS = g_Config.iMinTimeout * 1000UL;
InitLocalhostIP(); InitLocalhostIP();
@ -149,8 +151,6 @@ void __NetShutdown() {
g_PortManager.Restore(); g_PortManager.Restore();
g_PortManager.Deinit(); g_PortManager.Deinit();
} }
//PPSSPPIDCleanup(); // To make the ID/IP persistent on every reset, we should just let the OS closes all open handles instead of calling PPSSPPIDCleanup() on every reset
} }
static void __UpdateApctlHandlers(int oldState, int newState, int flag, int error) { static void __UpdateApctlHandlers(int oldState, int newState, int flag, int error) {

View file

@ -104,6 +104,7 @@ bool PortManager::Init(const unsigned int timeout) {
break; break;
} }
} }
m_leaseDuration = "43200"; // 12 hours m_leaseDuration = "43200"; // 12 hours
m_InitState = UPNP_INITSTATE_BUSY; m_InitState = UPNP_INITSTATE_BUSY;
urls = (UPNPUrls*)malloc(sizeof(struct UPNPUrls)); urls = (UPNPUrls*)malloc(sizeof(struct UPNPUrls));
@ -165,6 +166,7 @@ bool PortManager::Init(const unsigned int timeout) {
RefreshPortList(); RefreshPortList();
return true; return true;
} }
ERROR_LOG(SCENET, "PortManager - upnpDiscover failed (error: %i) or No UPnP device detected", error); ERROR_LOG(SCENET, "PortManager - upnpDiscover failed (error: %i) or No UPnP device detected", error);
auto n = GetI18NCategory("Networking"); auto n = GetI18NCategory("Networking");
host->NotifyUserMessage(n->T("Unable to find UPnP device"), 6.0f, 0x0000ff); host->NotifyUserMessage(n->T("Unable to find UPnP device"), 6.0f, 0x0000ff);

View file

@ -693,6 +693,7 @@ void GameSettingsScreen::CreateViews() {
networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableUPnP, n->T("Enable UPnP", "Enable UPnP (need a few seconds to detect)")));
networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress); networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), (const char *)nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress);
networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset(0 = PSP compatibility)"), 100, screenManager())); networkingSettings->Add(new PopupSliderChoice(&g_Config.iPortOffset, 0, 60000, n->T("Port offset", "Port offset(0 = PSP compatibility)"), 100, screenManager()));
networkingSettings->Add(new PopupSliderChoice(&g_Config.iMinTimeout, 1, 15000, n->T("Minimum Timeout", "Minimum Timeout (override low latency communication in milliseconds)"), 100, screenManager()));
networkingSettings->Add(new ItemHeader(n->T("Chat"))); networkingSettings->Add(new ItemHeader(n->T("Chat")));
networkingSettings->Add(new CheckBox(&g_Config.bEnableNetworkChat, n->T("Enable network chat", "Enable network chat"))); networkingSettings->Add(new CheckBox(&g_Config.bEnableNetworkChat, n->T("Enable network chat", "Enable network chat")));