Read and write line by line from buffer with TCP communication between C and Ruby

Note that I had a hard time communicating with the Ruby TCP server and C client.

As a result, the problem was that Ruby and C had different units of apparent data to send and receive to stream. Therefore, we implemented the sputs function and sgets function that wrap C write and read so that they can communicate in the same unit as Ruby.

The environment is

is.

Ruby server

It is a server that starts communication and returns the character string received from the client almost as it is. Repeat this 5 times to finish.

server.rb


require 'socket'

#Start the server on port 20000
server = TCPServer.open(20000)
#Accept communication
sock = server.accept

5.times do
  #Receives one line from the buffer.(Receive up to line breaks)
  line = sock.gets.chomp
  #Displayed on the console. If it is p, null is also\It is displayed as x00.
  p line
  #Return. At the end\n is added.
  sock.puts("you sent <<<#{line}>>>.")
end

#Disconnect communication
sock.close

C client

Implement the puts and sgets functions corresponding to ruby's gets and puts.

client.c


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define RECV_SIZE (10000)
#define SEND_SIZE (10000)

/*
 *Fetch a line from the buffer.(get string from stream)
 *If there is no data for one line in the buffer, it waits.
 */
char* sgets(int sd, char* line) {
	//The part taken out before the last line break
	static char* read_buf = NULL;
	if (read_buf == NULL) {
		read_buf = malloc(sizeof(char) * RECV_SIZE);
		read_buf[0] = '\0';
	}

	while (1) {
		int e;
		if (strlen(read_buf) != (e = strcspn(read_buf, "\n"))) {
			//Initialize line
			memset(line, '\0', sizeof(char) * strlen(line));
			//Copy one line to line
			strncpy(line, read_buf, (e + 1) - 0);
			//Read the next line_Copy from the beginning of buf
			strcpy(read_buf, strchr(read_buf, '\n') + 1);

			break;
		}
		//Initialize and prepare an array for receiving
		char r[RECV_SIZE] = { 0 };

		//Receives the amount of char just sent from the buffer.(Null not added at the end)
		if (read(sd, r, sizeof(r) * RECV_SIZE) < 0) {
			perror("recv");
			fflush(0);
			return NULL;
		}
		//Accumulate the read data.
		strcat(read_buf, r);
	}

	return line;
}

/*
 *Send one line.(put string to stream)
 *At the end"\n"Is added.
 */
void sputs(int sd, char* str) {
	char* send_str = malloc(sizeof(char) *(strlen(str) + 2));
	memset(send_str, '\0', sizeof(char) *(strlen(str) + 2));
	strcat(send_str, str);
	send_str[strlen(str)] = '\n';
	if (write(sd, send_str, sizeof(char) * strlen(send_str)) < 0) {
		perror("send");
		return;
	}
	free(send_str);
}

int main(int argc, char *argv[]) {
	int sd;  //Variables for creating sockets
	struct sockaddr_in addr;  //Variables for server connection
	char *recv[sizeof(char) * RECV_SIZE] = {0};

	//Create an IPv4 TCP socket
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return -1;
	}

	//Set the destination address and port number
	addr.sin_family = AF_INET;
	addr.sin_port = htons(20000);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	//Server connection (for TCP, you need to establish a connection)
	connect(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));

	char* strs[5] = {"abcde", "fg", "hijklmn", "opqrs", "tuvwxyz"};

	for(int i = 0; i < 5; i++) {
		//Send one line. At the end\n is added.
		sputs(sd, strs[i]);
		//Get one line.
		sgets(sd, recv);
		//display
		printf("I recived <<<%s>>>\n", recv);
	}

	//Close socket
	close(sd);

	return 0;
}

Be careful with the read function used in sgets. The contents of the buffer are written to r byread (sd, r, sizeof (r) * RECV_SIZE), but the read function does not pass the end of the string well, but 1 byte. Write up to the point you received each time. Also, null is not added at the end of writing. Therefore, it is recommended to fill r with null every time.

Also, be careful about the write function used in sputs. If you use write (sd, send_str, sizeof (char) * strlen (send_str) with a large number of 3rd arguments and set it to SEND_SIZE, SEND_SIZE bytes will be sent regardless of the length of the character string of the 2nd argument. It seems that the missing part is filled with NULL.

By the way, the implementation that does not work well is as follows.

client.c(Bad example)


int main(int argc, char *argv[]) {
	int sd;  //Variables for creating sockets
	struct sockaddr_in addr;  //Variables for server connection
	char *recv[sizeof(char) * RECV_SIZE] = {0};

	//Create an IPv4 TCP socket
	if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return -1;
	}

	//Set the destination address and port number
	addr.sin_family = AF_INET;
	addr.sin_port = htons(20000);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");

	//Server connection (for TCP, you need to establish a connection)
	connect(sd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));

	char* strs[5] = {"abcde", "fg", "hijklmn", "opqrs", "tuvwxyz"};

	for(int i = 0; i < 5; i++) {
		//Send one line.
		write(sd, strs[i], SEND_SIZE);
		//Get one line.
		read(sd, recv, RECV_SIZE);
		//display
		printf("I recived <<<%s>>>\n", recv);
	}

	//Close socket
	close(sd);

	return 0;
}

Writing read and write with the same feeling of gets and puts as Ruby will fail.

Execute

$ ruby server.rb
$ gcc -O2 -o client client.c
$ ./clinet

Server side


"abcde\n"
"fg\n"
"hijklmn\n"
"opqrs\n"
"tuvwxyz\n"

Client side


I recived <<<you sent <<<abcde>>>.
>>>
I recived <<<you sent <<<fg>>>.
>>>
I recived <<<you sent <<<hijklmn>>>.
>>>
I recived <<<you sent <<<opqrs>>>.
>>>
I recived <<<you sent <<<tuvwxyz>>>.
>>>

In C, it doesn't convert to a character string like Ruby, so I had to pay attention to byte-by-byte processing. Probably, if TCP communication is performed between other languages, various problems are likely to occur due to the difference in the handling of communication.

Recommended Posts

Read and write line by line from buffer with TCP communication between C and Ruby
I implemented Ruby with Ruby (and C) (I played with builtin)
How to write offline real time Implementation example by ruby and C99 of F04
Solving with Ruby, Perl and Java AtCoder ABC 128 C
In fact, Ruby distinguishes between line breaks and whitespace.
An application that acquires the value of the accelerometer by UDP communication between C # and Android
The difference between programming with Ruby classes and programming without it
Write DiscordBot to Spreadsheets Write in Ruby and run with Docker
Solving with Ruby, Perl and Java AtCoder ABC 129 C (Part 1)