In my ongoing attempts to become a better blog writer I have some written some more code that needs reviewing.
Full Source: https://github.com/Loki-Astari/Examples/tree/master/Version1
First Article: https://lokiastari.com/posts/SocketProgramminginC
This is a Simple Client Server implementation using RAW Sockets.
MakeFile
all: client server
clean:
rm -f *.o client server
CFLAGS = -Wall -Wextra -pedantic -Werror
client: client.o
server: server.o
client.cpp
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CLIENT_BUFFER_SIZE 1024
int main(int argc, char* argv[])
{
if (argc != 3)
{
fprintf(stderr, "Usage: client <host> <Message>\n");
exit(1);
}
int socketId = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddr;
socklen_t addrSize = sizeof(serverAddr);
bzero((char*)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
connect(socketId, (struct sockaddr*)&serverAddr, addrSize);
write(socketId, argv[2], strlen(argv[2]));
shutdown(socketId, SHUT_WR);
char buffer[CLIENT_BUFFER_SIZE];
size_t get = read(socketId, buffer, CLIENT_BUFFER_SIZE - 1);
buffer[get] = '0円';
fprintf(stdout, "%s %s\n", "Response from server", buffer);
close(socketId);
}
server.cpp
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SERVER_BUFFER_SIZE 1024
int main()
{
int socketId = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in serverAddr;
bzero((char*)&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = INADDR_ANY;
bind(socketId, (struct sockaddr *) &serverAddr, sizeof(serverAddr));
listen(socketId, 5);
int finished = 0;
while(!finished)
{
struct sockaddr_storage serverStorage;
socklen_t addr_size = sizeof serverStorage;
int newSocket = accept(socketId, (struct sockaddr*)&serverStorage, &addr_size);
char buffer[SERVER_BUFFER_SIZE];
int get = read(newSocket, buffer, SERVER_BUFFER_SIZE - 1);
buffer[get] = '0円';
fprintf(stdout, "%s\n", buffer);
write(newSocket, "OK", 2);
fprintf(stdout, "Message Complete\n");
close(newSocket);
}
close(socketId);
}
4 Answers 4
I can't resist saying this... the way you use a lot of whitespace to achieve your horizontal alignment, especially in server.cpp
, is distracting and hinders readability.
I don't mind it so much when several lines have some relationship to each other. For example:
serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8080); serverAddr.sin_addr.s_addr = INADDR_ANY;
But for this? No, don't bother trying to horizontally align anything at all.
struct sockaddr_storage serverStorage; socklen_t addr_size = sizeof serverStorage; int newSocket = accept(socketId, (struct sockaddr*)&serverStorage, &addr_size); char buffer[SERVER_BUFFER_SIZE]; int get = read(newSocket, buffer, SERVER_BUFFER_SIZE - 1);
-
\$\begingroup\$ I agree it does not work well there. Removing it. \$\endgroup\$Loki Astari– Loki Astari2016年06月05日 22:19:23 +00:00Commented Jun 5, 2016 at 22:19
-
\$\begingroup\$ @MartinYork Personalized spacing is a distraction. Rather than comment on it much. I tend to copy the source code, press about 5 keys to auto-format it using a very standard style and then review. If the reformatted code looks OK, all it well. It not, the code is not well adapted to auto-formatting. Save time. Do not manually format code. Auto-format it. \$\endgroup\$chux– chux2023年04月14日 02:27:47 +00:00Commented Apr 14, 2023 at 2:27
One of the issues with TCP sockets is that there's no guarantee that sending N bytes in one go will result in receiving exactly N bytes after one call to read()
. I would thus emphasize this by using a separator in the messages (newline perhaps?) and only printing out the result of what I've read after the whole line has been received.
I'd also try to get rid of magic numbers. listen(socketId, 5);
makes me look at the manual to figure out what 5
is supposed to mean. A carefully named constant would make reading the code easier.
-
\$\begingroup\$ Yes I know about the read. That is a deliberate mistake so I can explicitly talk about it in my blog and show how to do it correctly. lokiastari.com/blog/2016/04/09/socket-read \$\endgroup\$Loki Astari– Loki Astari2016年06月05日 16:26:38 +00:00Commented Jun 5, 2016 at 16:26
-
\$\begingroup\$ @MartinYork Good review etiquette: "deliberate mistake", for whatever reason, should be pointed out in the question or code. \$\endgroup\$chux– chux2023年04月14日 04:27:33 +00:00Commented Apr 14, 2023 at 4:27
-
\$\begingroup\$ Above Page movedd: localhost:3000/posts/SocketRead_Write \$\endgroup\$Loki Astari– Loki Astari2023年04月15日 05:56:53 +00:00Commented Apr 15, 2023 at 5:56
Some issues that occur to me:
The obvious problem is that there is no error checking.
memset
is often preferred tobzero
, although I don't see why. Also the cast ofserverAddr
tochar*
in thebzero
call is unnecessary.port number should be shared in a header file
is
inet_addr
the correct way to get the address? Also note that it, too, can fail. And there is usually anmemcpy
involved in creating the address structure.does calling
shutdown
have any purpose?putting a 1k buffer on the stack would be a bad idea in some small systems
the issue of
read
not returning all of what was written (or being interrupted) was already mentioned. Alsoread
returnsssize_t
notint
orsize_t
.finished variable is redundant.
Trivia
Usage message might use argv[0] instead of assumed executable name. Also the host must be a dotted address not a name.
exit status might prefer EXIT_FAILURE
I'd put "Response from server" in the format string.
-
\$\begingroup\$ The client calling
shutdown()
should tell the server that there is nothing more to read. \$\endgroup\$200_success– 200_success2016年06月06日 18:45:46 +00:00Commented Jun 6, 2016 at 18:45 -
\$\begingroup\$ The
EXIT_FAILURE
macro is so ugly. But yes. @glampert also mentioned thebzero()
issue in a review of the C++ version. Apparently itsarchaic and non-standard
codereview.stackexchange.com/a/131172/507 Apart fromshutdown
I will address all these issues. Thanks. \$\endgroup\$Loki Astari– Loki Astari2016年06月06日 18:46:43 +00:00Commented Jun 6, 2016 at 18:46 -
\$\begingroup\$ @LokiAstari, those are my words, hehe. But here is a more in-depth discussion: stackoverflow.com/questions/17096990/why-use-bzero-over-memset (the archaic part is my own opinion, but the function is not part of the C or C++ standard). \$\endgroup\$glampert– glampert2016年06月06日 19:21:23 +00:00Commented Jun 6, 2016 at 19:21
-
\$\begingroup\$ Yes, I agree about
EXIT_FAILURE
, just being pedantic. Also I forgot to note that thefinished
variable is redundant. \$\endgroup\$William Morris– William Morris2016年06月06日 19:44:41 +00:00Commented Jun 6, 2016 at 19:44
There is a problem with the naming of your files. The .c extension is for the C language and the .cpp extension is for the C++ language. The tag on this question is c, so the expectation is the file name extension would be .c, not .cpp.
Most compilers that can handle both languages look at the file name extension to determine which language to compile.
The posted code needs to check the returned values from: socket()
, bind()
, read()
, accept()
.
The variable finished
is never updated, so it's not needed. Just use while (1)
.
libevent
. But before I get there I want to implement a threaded version (then show the event driven version to show that it is better). \$\endgroup\$