New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update sockets module #301
Conversation
- WinSock version changed from 1.1 to 2.2. - Properly check for WinSock initialization on OnAmxxAttach/Detach. - Now natives will not be added if we can't start up WinSock. - socket_open() is now IP version agnostic (both IPv4 and IPv6 are supported). - Error reporting has been changed on socket_open(), a new parameter called _libc_errors has been added, and, if enabled, libc errors will be returned instead of the previous made-up errors. - socket_close() now returns a value on success/failure. - Added non-blocking sockets at socket_open_nb(). - Added socket_is_writable() to check if a socket is ready for write. - Added socket_is_readable() as an alias to socket_change(). - Code rewritten to be more readable, it should be self-explaining now.
Updated documentation following the guidelines
modules/sockets/sockets.cpp
Outdated
FD_ZERO(&readfds); | ||
FD_SET(sockfd, &readfds); | ||
|
||
return (select(sockfd + 1, &readfds, nullptr, nullptr, &tv) > 0) ? 1 : -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will break likely compatibility by returning -1 instead of 0 originally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was changed from 0 to -1 to maintain consistency with the other function's return value, because having some functions returning 0 and others returning -1 could be confusing.
Also I didn't thought anyone would check the socket against 0, I thought they would just do !socket.
Return values should be standardized across the module I think, so maybe changing all of them to 0 would be better, even if some of them return -1 by design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just doing if (socket_change())
will still break with -1 since expression will be considerated as boolean and because it's a non-zero value, condition will be true.
So yes it should be standardized as much as possible, but unfortunately you can't change suddenly 0 -> -1 or -1 -> 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Receives data.
*
* @note The ammount of bytes than you end up receiving can be less than the one you expected
*
* @param _socket Socket descriptor
* @param _data Array to save the data
* @param _length Length of the array
*
* @return Ammount of bytes received
* 0 if the peer closed the connection
* -1 on failure
*/
native socket_recv(_socket, _data[], _length);
Every return value but this one can be changed to 0 without any problem.
Changing socket_recv() to 0 would remove the possibility of knowing if the peer closed the connection.
Should we remove this feature in order to keep everything consistent ? I'm not sure it would be of any use, and the socket's status can be check using other functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't want to break backward compatibility, so no you can't change return value here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just took a look again at it[1] and yeah, it has to stay at -1. It could return 0 previously, I just documented it.
[1] https://github.com/alliedmodders/amxmodx/blob/master/modules/sockets/sockets.cpp#L143
- Fixed the backwards compatibility with the return codes - Merged socket_connect and socket_connect_nb - Added a 5th parameter to socket_open that takes bit flags to enable the new features (libc errors & nonblocking sockets) - Fixed an error on socket_send2 that caused the buffet not to start from the beginning if multiple calls were made - Updated docs - [docs] Prefixed error codes with SOCK_ - [docs] Added the new flags SOCK_NON_BLOCKING and SOCK_LIBC_ERRORS - [docs] Added a new stock called SOCK_ERROR_EINPROGRESS(error) to be used when checking if a newly created nonblocking socket is connecting
Simple test plugin #include < amxmodx >
#include <sockets>
#define NODE "httpbin.org"
#define SERVICE 80
new request[] = "GET /get HTTP/1.1^nHOST: httpbin.org^nConnection: close^n^n^n^n"
public plugin_init( )
{
register_plugin("Socket test", "1.0", "Javivi")
register_clcmd("say /s1", "test_blocking")
register_clcmd("say /s2", "test_nonblocking")
}
public test_blocking(id)
{
new sockfd, socket_error
new bytes_sent, bytes_recv, bytes_recv_buffer[500]
sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_LIBC_ERRORS)
if(sockfd != -1)
{
client_print(id, print_chat, "Got socketfd %d", sockfd)
bytes_sent = socket_send(sockfd, request, charsmax(request))
client_print(id, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
bytes_recv = socket_recv(sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
client_print(id, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
client_print(id, print_chat, "%s", bytes_recv_buffer)
socket_close(sockfd)
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
}
new nb_sockfd
public test_nonblocking(id)
{
new socket_error
nb_sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_NON_BLOCKING|SOCK_LIBC_ERRORS)
if(nb_sockfd != -1 && SOCK_ERROR_EINPROGRESS(socket_error))
{
client_print(id, print_chat, "Got nonblocking socket %d", nb_sockfd)
set_task(1.0, "check_nbsocket", 1234, .flags="b")
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
}
public check_nbsocket(task)
{
if(socket_is_writable(nb_sockfd))
{
remove_task(task)
client_print(0, print_chat, "Socket %d is writable", nb_sockfd)
new bytes_sent = socket_send(nb_sockfd, request, charsmax(request))
client_print(0, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
set_task(1.0, "recv_nbsocket", 1234, .flags = "b")
}
}
public recv_nbsocket(task)
{
new bytes_recv, bytes_recv_buffer[500]
bytes_recv = socket_recv(nb_sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
if(bytes_recv)
{
remove_task(task)
client_print(0, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
client_print(0, print_chat, "%s", bytes_recv_buffer)
socket_close(nb_sockfd)
}
} NOTE: The first call to /s2 will probably block, as the name resolution server will have to be called. To avoid that a numeric ip address has to be used, as noted in the docs. FOR THE FUTURE:
|
@Javivi ^n -> ^r^n? |
Both ways adhere to the HTTP requisite of a blank line at the end, but yes, if the test plugin doesn't work for someone try changing ^n^n for ^r^n. Do you mean creating a new function that spawns a thread and then callbacks when the socket is "ready" ? We discussed at the beginning of the pull against having 2 separate functions for connect when just 1 would do, and also keep in mind that once getaddrinfo resolves an address it stays on the dns cache until it gets flushed, and that may never happen. Using a numeric IP or having the dns address already cached makes the socket_open function with the NON_BLOCKING flag not to block, adding a new function with threads just for doing a resolve adds some unneeded complexity in my opinion. |
@Javivi hm, you can add a separate function for non-blocking domain resolving. |
You mean doing something like: That can be done, but I don't think it is needed. You only need to resolve the hostname one time and it stays cached until the dns cache gets flushed. Calling socket_open just one time at plugin_init or at any event that doesn't interfere with a current game would do the same, even though it is not as pretty Unless the dns of the domain is really misconfigured the query should not take more than a few ms and it can even return instantly, I would like some feedback on this if possible too https://github.com/javivi/amxmodx/tree/sockets-update Having an event based module is obviously the end game, and there won't be any problems with this when that happens, but meanwhile a work around is needed. |
The changes look overall okay.
Are there others things you wanted to add? |
Fix some typos, shorten lines, document SOCK_ERROR_EINPROGRESS
No more magic
This reverts commit 0f23329.
It should not be needed because nb sockets should always be checked or writability
strncopy will stop on a null byte, that makes the function unusable to receive binary data
Should be ready for merge if nothing wrong pops. Spaguetti test code. /s1 uses socket_open() + socket_send() /s1 and /s2 should print something like this on console
/s3 should print
/s4 should crash the server #include <amxmodx>
#include <sockets>
#define NODE "httpbin.org"
#define SERVICE 80
public plugin_init( )
{
register_clcmd("say /s1", "test_blocking")
register_clcmd("say /s2", "test_nonblocking")
register_clcmd("say /s3", "test_send2")
register_clcmd("say /s4", "test_send12")
}
public test_blocking(id)
{
new sockfd, socket_error
new bytes_sent, bytes_recv, bytes_recv_buffer[500], bytes_recv_buffer_printed
sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_LIBC_ERRORS)
if(sockfd != -1)
{
client_print(id, print_chat, "Got socketfd %d", sockfd)
new request[100]
formatex(request, charsmax(request), "GET /get HTTP/1.1^nHOST: httpbin.org^nTEST: %c%c%c^nConnection: close^n^n^n^n", 'a', -1, 'c')
bytes_sent = socket_send(sockfd, request, strlen(request))
client_print(id, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
bytes_recv = socket_recv(sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
client_print(id, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
bytes_recv_buffer_printed = server_print("%s", bytes_recv_buffer)
server_print("%s", bytes_recv_buffer[bytes_recv_buffer_printed-1])
socket_close(sockfd)
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
}
new nb_sockfd
public test_nonblocking(id)
{
new socket_error
nb_sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_NON_BLOCKING|SOCK_LIBC_ERRORS)
if(nb_sockfd != -1)
{
client_print(id, print_chat, "Got nonblocking socketfd %d", nb_sockfd)
set_task(1.0, "check_nbsocket", 1234, .flags="b")
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
}
public check_nbsocket(task)
{
if(socket_is_writable(nb_sockfd))
{
remove_task(task)
client_print(0, print_chat, "Socket %d is writable", nb_sockfd)
new request[100]
formatex(request, charsmax(request), "GET /get HTTP/1.1^nHOST: httpbin.org^nTEST: %c%c%c^nConnection: close^n^n^n^n", 'a', 'b', 'c')
new bytes_sent = socket_send(nb_sockfd, request, strlen(request))
client_print(0, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
set_task(1.0, "recv_nbsocket", 1234, .flags = "b")
}
}
public recv_nbsocket(task)
{
new bytes_recv, bytes_recv_buffer[500], bytes_recv_buffer_printed
bytes_recv = socket_recv(nb_sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
if(bytes_recv)
{
remove_task(task)
client_print(0, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
bytes_recv_buffer_printed = server_print("%s", bytes_recv_buffer)
server_print("%s", bytes_recv_buffer[bytes_recv_buffer_printed-1])
socket_close(nb_sockfd)
}
}
public test_send2(id)
{
new sockfd, socket_error
new bytes_sent, bytes_recv, bytes_recv_buffer[500], bytes_recv_buffer_printed
sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_LIBC_ERRORS)
if(sockfd != -1)
{
client_print(id, print_chat, "Got socketfd %d", sockfd)
new request[100]
formatex(request, charsmax(request), "GET /get HTTP/1.1^nHOST: httpbin.org^nTEST: %c%c%c^nConnection: close^n^n^n^n", 'a', 0, 'c')
bytes_sent = socket_send2(sockfd, request, charsmax(request))
client_print(id, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
bytes_recv = socket_recv(sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
client_print(id, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
bytes_recv_buffer_printed = server_print("%s", bytes_recv_buffer)
server_print("%s", bytes_recv_buffer[bytes_recv_buffer_printed-1])
socket_close(sockfd)
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
}
public test_send12(id)
{
new sockfd, socket_error
new bytes_sent, bytes_recv, bytes_recv_buffer[500], bytes_recv_buffer_printed
sockfd = socket_open(NODE, SERVICE, SOCKET_TCP, socket_error, SOCK_LIBC_ERRORS)
if(sockfd != -1)
{
client_print(id, print_chat, "Got socketfd %d", sockfd)
new request[100]
formatex(request, charsmax(request), "GET /get HTTP/1.1^nHOST: httpbin.org^nTEST: %c%c%c^nConnection: close^n^n^n^n", 'a', 0, 'c')
bytes_sent = socket_send(sockfd, request, charsmax(request))
client_print(id, print_chat, "[%d/%d] bytes sent", bytes_sent, charsmax(request))
bytes_recv = socket_recv(sockfd, bytes_recv_buffer, charsmax(bytes_recv_buffer))
client_print(id, print_chat, "[%d/%d] bytes received", bytes_recv, charsmax(bytes_recv_buffer))
bytes_recv_buffer_printed = server_print("%s", bytes_recv_buffer)
server_print("%s", bytes_recv_buffer[bytes_recv_buffer_printed-1])
socket_close(sockfd)
}
else
{
client_print(id, print_chat, "Error %d", socket_error)
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for testing again and fixing the left issues as we discussed.
It should be enough good now for a first update.
Others features can be done in another PR.
Let's do it then!
Been working on this some time. Changelog:
supported).
called _libc_errors has been added, and, if enabled, libc errors will be
returned instead of the previous made-up errors.
Sorry for the ugly diff @Arkshine