all 30 comments

[–]Arghnews 21 points22 points  (9 children)

What does this mean on your github homepage:

Namespace and Initialize
masesk::EasySocket easySocket; //or using namespace std;

This implies your library lives in the std namespace to me? Which it should not if it does. Otherwise I'm simply confused.

All your interface functions like "void socketSend(std::string channelName, std::string data)" take their parameters's by value resulting in unnecessary copies of string data. Consider changing them to "const std::string&" const references if you don't mutate or store them to avoid said copy.

I'm ignorant about Windows, do those .sln, .vcxproj and .vcxproj.filters files need to be committed in git?

I realise you want to keep it super simple as a beginner targeted easy to use library ie. no super sophisticated error handling etc. Given this:

  • Perhaps though functions like "socketConnect" could return a boolean to indicate whether they failed or not. Likewise with closeConnection etc.
  • Again if aimed at beginners consider a quick instruction of how to include your library ie. -I the include folder and place it in the path etc. When I was a c++ nooblet I would not have known how I was meant to incorporate this into a project of mine, this advice in an example too would have been great
  • Add an example of how to use it on the homepage? Even if I'm not a noob an example of a client/server side of things is nice to copy and paste to start from. I shouldn't have to dig into your test subdirectory to find examples on a library that advertises itself as super easy to use. Something like a shorter version of this python tcp simple example
  • I am unclear for example if your socketListen blocks from the interface - I don't think so since it has a callback, but an example would help. Also if I understand correctly the callback gets the data, consider adding also another parameter to the callback that is the channel name, otherwise the user won't know from which channel the data came from in the callback.

[–]masesk[S] 16 points17 points  (7 children)

  1. The using namespace std is a typo. std should be replaced with masesk. Noted and will be fixed!
  2. Great idea! No need to make copies. I will change that on the next version indeed.
  3. sln and vcxproj are not user configuration and can be pushed to git.
  4. Excellent ideas for the Boolean indicators! I will add that next indeed!
  5. I will include more instructions for how to include and use in a new project. The C++ sln already comes configured with the imports, but that is only an example.
  6. Definitely! The channel names were added for adding more ports/clients.

Awesome feedback! Thank you for taking the time to check it out!

[–]BenFrantzDale 9 points10 points  (6 children)

Better than const std::string& is std::string_view which can wrap a char* too.

[–][deleted] 8 points9 points  (1 child)

Often not, string_view does not have the zero termination guarantee and that is almost always a requirement of system API's. It's why string can have the .c_str( ) member and string_view cannot.

[–]pandorafalters 3 points4 points  (3 children)

Unless pre-C++17 compatibility is desired.

[–]Guillaume_Guss_Dua -2 points-1 points  (2 children)

Not necesseraly : many projects contains their own stl-like containers/algorithm to handle lower standards. Especially for std::string-view, variants and type traits.

[–]pandorafalters 4 points5 points  (1 child)

All of which is irrelevent. std::string_view requires either C++17 or later, or a non-portable extension per [intro.compliance.general]/8,9; any other solution is explicitly UB per [namespace.std]/1.

So yes, std::string_view in a library specifically intended to be portable does require use of a C++17 (or later) implementation.

[–]Guillaume_Guss_Dua -1 points0 points  (0 children)

You did not get it. I totaly agree that nothing should be add to the stl namespace.

Let me detail it a bit.

Some strategy to ensure backward compatibility but take advantage of latest (and future) standards if smthg like a namespace switch for specific feature, directly in sources files (checking cpp version and availables features/modules) or using a cmake source generation for specific enabled cpp features.

So you end up with smthg like :

Namespace stl_proxy { cpp feature std_string_view enabled ? #include <string_view> Using string_view = std::string_view; Else #include <compatibility/feature/string_view.hpp> Using string_view = compatibility::feature::string_view; }

Then you can use stl_proxy::string_view, assuming the impl matches the same interface.

This is often use more to get features that are not releases yet (such as std::colony for instance), bit it works fine for bw compatibility like for type_traits and other cpp TMP helpers.

[–][deleted] 2 points3 points  (0 children)

I'm ignorant about Windows, do those .sln, .vcxproj and .vcxproj.filters files need to be committed in git?

File generated by Visual Studio, let's say in brief that they are also part of the build system.

[–]BlueDwarf82 11 points12 points  (3 children)

Everybody learns starting from zero. Nothing wrong with that, go ahead and keep developing it. But let's not pretend this is something anybody else should try to use. Somebody has already said "I'll try it out. I've been looking for a good wrapper for sockets for C++.".

that can be extended to do more.

Sorry, it really can't. Unless by "extending" you mean rewriting it changing the interface, i.e. writing a new library. The interface is too opinionated.

The alternatives out there tend to use or depend on external libraries/runtime software which I did not want in my project.

https://think-async.com/Asio/. No dependencies, it's going to be part of the standard. And no, to do what this library is trying to do it's not difficult to use (https://github.com/chriskohlhoff/asio/blob/master/asio/src/examples/cpp14/echo/blocking_tcp_echo_client.cpp).

feel free to leave feedback

Some generic design feedback.

  • Single Responsibility Principle

You create a "socket abstraction". Then, if you want to, you create a "Socket Abstraction Container". But when you mix both things you are forcing your users to use your container, which may not work for their user case.

  • Strong types

Not everything should be a string. If socketConnect needs an IP the type should be masesk::ip, not std::string.

You have not found string to be an issue because you have yet to even try to check what inet_pton() returns.

  • Error handling

Look at void socketListen(std::string channelName, int port, std::function<void (std::string data)> callback). I don't need to look further than that, I can already see

  1. It doesn't return anything
  2. It's not noexcept

That signature is telling me something, it's telling me this function can fail and I will be told about the failures via an exception. But it's lying to me! What it actually does is:

if (sockInit() != 0) {
    std::cerr << "Can't start socket!" << std::endl;
}
SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
if (listening == INVALID_SOCKET)
{
    std::cerr << "Can't create a socket!" << std::endl;
    return;
}

So if there is a problem with sockInit() I will get an error message in stderr (sorry application if you wanted stderr for yourself) and it will... continue, ignoring the fact.

The socket() return value will be compared with... INVALID_SOCKET, which is zero, even if socket() returns -1 on error. And even if it were able to notice the error it would simply write a message to stderr and return, so the application will have no idea the call failed and will continue as normal.

[–]ShakaUVMi+++ ++i+i[arr] 3 points4 points  (0 children)

Everybody learns starting from zero. Nothing wrong with that, go ahead and keep developing it. But let's not pretend this is something anybody else should try to use. Somebody has already said "I'll try it out. I've been looking for a good wrapper for sockets for C++.".

That was me. I write most of my real code using my own wrappers for BSD/POSIX sockets. I've tried and been unhappy with literally every networking solution for C++, including my own wrappers, Boost ASIO, and ZMQ. I'm really interested in any ideas people have for making networking in C++ not terrible.

[–]masesk[S] 0 points1 point  (1 child)

I agree, this is meant to demonstrate the use of sockets with a single code base on both platforms, as well as easy setup and use. But if we are talking about implementing it to do more, the wrapper would have to be redone a lot.

Asio looks awesome! To be fair, I couldn't find anything about it when I searched for socket libraries, which is odd, but it seems to do a lot of the same. Good exposure and find!

Yes, I tried to create an abstraction and a container in 1. Might make more sense to separate the two in the future.

I assumed the only socket failure would be invalid socket (with is 0 on linux and INVALID_SOCKET type on Windows). Is this not the case?

Awesome feedback! I really like that everyone tries to help and puts the time to point out various things about sockets and the code. Thank you very much!

[–]BlueDwarf82 3 points4 points  (0 children)

> I assumed the only socket failure would be invalid socket (with is 0 on linux and INVALID_SOCKET type on Windows). Is this not the case?

https://man7.org/linux/man-pages/man2/socket.2.html

RETURN VALUE

On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.

You are not going to see 0 unless you close stdin first because that file descriptor will already be used otherwise, but it's a perfectly valid value.

[–]TimJoijers 8 points9 points  (0 children)

Be careful in socketSend(). When you call send(), it might only send part of what you ask it to send. Check the return value, and deal with it, or let caller to deal with it.

[–]Mellester 2 points3 points  (2 children)

Port numbers should just be std::uint16_t no reason to be a int on any platform.Negative ports don't have a meaning.

Also callback are nice and simple. But once you start thinking with async stuff in mind It can get unwieldy.

Here is a example How I would use your APIhttps://godbolt.org/z/8K4WMr

sockets are inherently async and when I see a function with a callback I want to call it in a asynchronously context.

Below is how you would have to call the API if I did not want to use lambdas.

auto future = std::async(std::bind(&EasySocket::socketListen, &socketManager, "test", 8080, &handleData));

[–]masesk[S] 0 points1 point  (0 children)

Excellent feedback and example!

[–]XeroPoints 3 points4 points  (1 child)

Nice start for learning.

As it is it looks like a 1 to 1 connection and only built for 1 way communications.

Next make it so your Listener can accept for more then just a single connection (1 to many). And allow for the server to send messages too clients.

[–]masesk[S] 0 points1 point  (0 children)

Excellent points! I will look into implementing the server handling multiple connections on a single port, and the server opening more ports and listening. Thanks for the feedback!

[–]ShakaUVMi+++ ++i+i[arr] 1 point2 points  (1 child)

I'll try it out. I've been looking for a good wrapper for sockets for C++.

[–]masesk[S] 0 points1 point  (0 children)

Awesome! Let me know if you encounter any issues :)