[LINUX] Introduction to Socket API Learned in C Part 3 TCP Server / Client # 1

[Introduction to Socket API Learned in C Part 2 Client Edition] This is the server / client version # 1 of the sequel to (http://qiita.com/tajima_taso/items/fb5669ddca6e4d022c15). This theme is unlikely to be completed with just one post, so I decided to make it # 1.

I will continue to base socket programming on the C language source code, but since I covered the main things such as socket creation and TCP connection establishment last time and last time, unless there is some new information. I will omit the explanation of that part.

This time, we will look at the actual exchange of messages (data exchanged by network applications) between the client and server, showing some signs of application protocols.

Execution environment

Client software Mac OS X 10.10.5 gcc Server software x86_64 CentOs 6.8 gcc

First, let's look at client software.

Source code of client software

tcpc.c


#include <stdio.h> //printf(), fprintf(), perror()
#include <sys/socket.h> //socket(), bind(), accept(), listen()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa(), inet_aton()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset(), strcmp()
#include <unistd.h> //close()

#define MSGSIZE 1024
#define BUFSIZE (MSGSIZE + 1)

int main(int argc, char* argv[]) {

	int sock; //local socket descriptor
	struct sockaddr_in servSockAddr; //server internet socket address
	unsigned short servPort; //server port number
	char recvBuffer[BUFSIZE];//receive temporary buffer
	char sendBuffer[BUFSIZE]; // send temporary buffer

	if (argc != 3) {
		fprintf(stderr, "argument count mismatch error.\n");
		exit(EXIT_FAILURE);
	}

	memset(&servSockAddr, 0, sizeof(servSockAddr));

	servSockAddr.sin_family = AF_INET;

	if (inet_aton(argv[1], &servSockAddr.sin_addr) == 0) {
		fprintf(stderr, "Invalid IP Address.\n");
		exit(EXIT_FAILURE);
	}

	if ((servPort = (unsigned short) atoi(argv[2])) == 0) {
		fprintf(stderr, "invalid port number.\n");
		exit(EXIT_FAILURE);
	}
	servSockAddr.sin_port = htons(servPort);

	if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ){
		perror("socket() failed.");
		exit(EXIT_FAILURE);
	}

	if (connect(sock, (struct sockaddr*) &servSockAddr, sizeof(servSockAddr)) < 0) {
		perror("connect() failed.");
		exit(EXIT_FAILURE);
	}

	printf("connect to %s\n", inet_ntoa(servSockAddr.sin_addr));

	while(1){
		printf("please enter the characters:");
		if (fgets(sendBuffer, BUFSIZE, stdin) == NULL){
			fprintf(stderr, "invalid input string.\n");
			exit(EXIT_FAILURE);
		}

		if (send(sock, sendBuffer, strlen(sendBuffer), 0) <= 0) {
			perror("send() failed.");
			exit(EXIT_FAILURE);
		}

		int byteRcvd  = 0;
		int byteIndex = 0;
		while (byteIndex < MSGSIZE) {
			byteRcvd = recv(sock, &recvBuffer[byteIndex], 1, 0);
			if (byteRcvd > 0) {
				if (recvBuffer[byteIndex] == '\n'){
					recvBuffer[byteIndex] = '\0';
					if (strcmp(recvBuffer, "quit") == 0) {
						close(sock);
						return EXIT_SUCCESS;
					} else {
						break;
					}
				}
				byteIndex += byteRcvd;
			} else if(byteRcvd == 0){
				perror("ERR_EMPTY_RESPONSE");
				exit(EXIT_FAILURE);
			} else {
				perror("recv() failed.");
				exit(EXIT_FAILURE);
			}
		}
		printf("server return: %s\n", recvBuffer);
	}

	return EXIT_SUCCESS;
}

Lines 1 to 50

Header, data type, socket creation, connection establishment, etc. are the same as the client version of Last time, so I will omit it.

Lines 51-56

The while loop starts, and printf () outputs an instruction to prompt character input to the standard output.

Then use fgets to receive the string from the standard input. Depending on the environment, the input method will be the keyboard of the user's terminal. Keep in mind that fgets takes input until it receives an EOF or newline, but adds a trailing null character.

Note that MSGSIZE is intended for the maximum size of the displayed string, and BUFSIZE is intended for the maximum size of the stored byte string, including null characters.

Lines 58-61

Use the send () system call to send data to the remote host to connect to.

The first argument specifies the socket descriptor for which the connection has been established.

The second argument is the pointer that stores the message to be sent, and the third argument is the length of the message. Since the strlen function is used to specify the message length, the null character bytes added by fgets are excluded.

As with the previous recv, 0 is specified for the 4th argument. This is the default behavior of blocking the program until the message is stored in the socket module's send buffer for the specified message length.

A possible blocking situation is when the socket module's send buffer is full and messages cannot be sent. This occurs because there is no free space in the receive buffer of the remote host to connect to, so data cannot be sent from the send buffer to the lower module due to flow control based on the TCP window size, and data transfer is waited. To do.

In addition, the send buffer of the socket is realized by the data structure of the queue, and it can also be confirmed as SendQ in netstat.

What is important here is that the success of send () is that it requested the OS to process the transmission and stored the message in the transmission buffer of the socket module, and it was possible to actually send the data to the remote host. That doesn't mean that.

After the process of send (), various processes are performed by the TCP module, IP module, and device driver in the OS, and packets (byte strings) are subjected to some physical conversion from hardware such as network interface cards, and some physical medium is used. After that, it will reach the host to connect to.

Lines 64 to 64

Defines an integer that specifies the number of bytes in the received buffer (byteRcvd) and the index of the receive buffer array (byteIndex). Note that the byteIndex matches the total number of bytes received in this implementation.

Lines 65-86

As long as the total number of bytes received is within MSG SIZE, recv will repeat the reception.

I touched on the recv () system call last time, but the descriptor of the socket where the connection was established is the first argument, the pointer of the message to be received in the second argument, the length of the message to be received in the third argument, and the fourth argument. Specify 0 to indicate the default behavior for block behavior.

The default behavior is to block program execution until a string of bytes up to the specified message length can be retrieved from the socket's receive buffer. The receive buffer of the socket is realized by the data structure of the queue and can be confirmed as RecvQ in netstat,

Like send (), recv () is a system call that requests the OS to transfer a byte string from the receive buffer stored in the socket to the user process, and is a lower layer transport module (in this case). Is just receiving the bytes passed by the TCP module) via the socket API.

Since the physical signal that reaches the hardware such as the network interface card only receives the data processed by the protocol stack due to hardware interrupts or software interrupts, recv () performs the data reception processing. It does not mean that you are there.

In other words, the packet reception process itself may or may not be executing recv (), but it is done constantly, and if the buffer of each module is full or the data is corrupted, it will be discarded. ..

Correct messages that were not discarded until the post-processing stage of the TCP module are stored in the socket receive buffer, so recv () forwards the message to the user process if there is data there.

Go back to the source code.

This time, each byte is transferred to the array recvBuffer.

Also, if the stream-type receive byte string ends with \ n, the application protocol is designed as a chunk of meaningful data up to that point, so when \ n is received, the line feed is overwritten with a null character. Whether it is quit or not is determined using the strcmp function.

If it matches quit, we will terminate the operation, so close the client socket and terminate the program.

If this is not the case, exit the while loop and display the received string with line breaks. Since a null character is added to the end of the byte string, it can be output by the C language string output function.

This time, recv () is executed byte by byte to simplify the program, but since recv () is a system call, the overhead of switching between the kernel mode and user mode of the CPU is not a little incurred.

Therefore, it should improve the execution performance by storing a somewhat large byte string in the buffer of the user process with one recv () and processing it one byte at a time, but with this amount of data I don't think it will change in human experience.

As we will check in the subsequent server program, the message sent from the server is the character string input by the client itself, so the same character string that was input will be output to the standard output as it is.

Next, let's look at server software. The server software this time is simpler than the client software.

Server software source code

tcps.c



#include <stdio.h> //printf(), fprintf(), perror()
#include <sys/socket.h> //socket(), bind(), accept(), listen()
#include <arpa/inet.h> // struct sockaddr_in, struct sockaddr, inet_ntoa()
#include <stdlib.h> //atoi(), exit(), EXIT_FAILURE, EXIT_SUCCESS
#include <string.h> //memset()
#include <unistd.h> //close()

#define QUEUELIMIT 5
#define MSGSIZE 1024
#define BUFSIZE (MSGSIZE + 1)

int main(int argc, char* argv[]) {

	int servSock; //server socket descriptor
	int clitSock; //client socket descriptor
	struct sockaddr_in servSockAddr; //server internet socket address
	struct sockaddr_in clitSockAddr; //client internet socket address
	unsigned short servPort; //server port number
	unsigned int clitLen; // client internet socket address length
	char recvBuffer[BUFSIZE];//receive temporary buffer
	int recvMsgSize, sendMsgSize; // recieve and send buffer size

	if ( argc != 2) {
		fprintf(stderr, "argument count mismatch error.\n");
		exit(EXIT_FAILURE);
	}

	if ((servPort = (unsigned short) atoi(argv[1])) == 0) {
		fprintf(stderr, "invalid port number.\n");
		exit(EXIT_FAILURE);
	}

	if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ){
		perror("socket() failed.");
		exit(EXIT_FAILURE);
	}

	memset(&servSockAddr, 0, sizeof(servSockAddr));
	servSockAddr.sin_family      = AF_INET;
	servSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servSockAddr.sin_port        = htons(servPort);

	if (bind(servSock, (struct sockaddr *) &servSockAddr, sizeof(servSockAddr) ) < 0 ) {
		perror("bind() failed.");
		exit(EXIT_FAILURE);
	}

	if (listen(servSock, QUEUELIMIT) < 0) {
		perror("listen() failed.");
		exit(EXIT_FAILURE);
	}

	while(1) {
		clitLen = sizeof(clitSockAddr);
		if ((clitSock = accept(servSock, (struct sockaddr *) &clitSockAddr, &clitLen)) < 0) {
			perror("accept() failed.");
			exit(EXIT_FAILURE);
		}
		printf("connected from %s.\n", inet_ntoa(clitSockAddr.sin_addr));

		while(1) {
			if ((recvMsgSize = recv(clitSock, recvBuffer, BUFSIZE, 0)) < 0) {
				perror("recv() failed.");
				exit(EXIT_FAILURE);
			} else if(recvMsgSize == 0){
				fprintf(stderr, "connection closed by foreign host.\n");
				break;
			}

			if((sendMsgSize = send(clitSock, recvBuffer, recvMsgSize, 0)) < 0){
				perror("send() failed.");
				exit(EXIT_FAILURE);
			} else if(sendMsgSize == 0){
				fprintf(stderr, "connection closed by foreign host.\n");
				break;
			}
		}

		close(clitSock);
	}

	close(servSock);

	return EXIT_SUCCESS;
}

Lines 61-77

From the 60th line onward, it is the same as the server program from the last time.

The recv () and send () introduced in the client program are as described above. The server program is implemented to return the message received from the client program to the client as it is.

Both send () and recv () are processed when the return value is 0, but by doing so, even if the connection is disconnected unintentionally by the client software, accept () will be used again. You can create a state that waits for a new connection and continue the execution of the server program.

The current server program cannot respond to requests from multiple clients at the same time, so it is not easy to use as a server program.

In fact, if you execute two or more of these client programs, the program launched later will continue to wait for processing when you enter characters with fgets and execute send (), and the client program being processed first will When it's done, it starts processing, and so on.

Even though people are steadily coming in, there is only one ATM, so it seems that people coming in later are waiting.

In order to manage this inconvenience, we will introduce the ones that have been extended to multi-process, multi-thread, or multiple processing by the same process or thread.

Regarding the next time, I saw the following article about UDP the other day, so I will once check the mechanism of data transmission and reception about UDP and the difference from TCP. Google's QUIC protocol: Migrate the Web from TCP to UDP

Reference book

-Network construction by TCP / IP <Vol.1> Principle / Protocol / Architecture -[TCP / IP network experiment programming understood from the basics-Linux / FreeBSD compatible](https://www.amazon.co.jp/%E5%9F%BA%E7%A4%8E%E3%81%8B%E3 % 82% 89% E3% 82% 8F% E3% 81% 8B% E3% 82% 8BTCP-IP-% E3% 83% 8D% E3% 83% 83% E3% 83% 88% E3% 83% AF% E3% 83% BC% E3% 82% AF% E5% AE% 9F% E9% A8% 93% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0% E2% 80% 95Linux-FreeBSD% E5% AF% BE% E5% BF% 9C-% E6% 9D% 91% E5% B1% B1 / dp / 4274065847 / ref = sr_1_4? s = books & ie = UTF8 & qid = 1471270171 & sr = 1-4 & keywords =% E5% 9F% BA% E7% A4% 8E% E3% 81% 8B% E3% 82% 89% E3% 82% 8F% E3% 81% 8B% E3% 82% 8Btcp% 2Fip) -[TCP / IP Socket Programming C Language](https://www.amazon.co.jp/TCP-IP%E3%82%BD%E3%82%B1%E3%83%83%E3%83% 88% E3% 83% 97% E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9F% E3% 83% B3% E3% 82% B0-C% E8% A8% 80% E8% AA% 9E% E7% B7% A8-Michael-Donahoo / dp / 4274065197) -[Detailed Linux Kernel 3rd Edition](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3-Linux%E3%82%AB%E3%83%BC%E3 % 83% 8D% E3% 83% AB-% E7% AC% AC3% E7% 89% 88-Daniel-Bovet / dp / 4873111313X)

Recommended Posts

Introduction to Socket API Learned in C Part 3 TCP Server / Client # 1
Introduction to Socket API Learned in C Part 4 UDP Server / Client # 1
Introduction to Socket API Learned in C Part 2 Client Edition
Introduction to Socket API Learned in C Language Part 1 Server Edition
An introduction to the modern socket API to learn in C
Sample API server to receive JSON in Golang
Introduction to PyQt4 Part 1
How to create a simple TCP server / client script
C API in Python 3
[Introduction to cx_Oracle] (Part 13) Connection using connection pool (client side)
Go language to see and remember Part 7 C language in GO language
Introduction to Ansible Part ③'Inventory'
Introduction to Ansible Part ④'Variable'
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part1-
Summary of Chapter 2 of Introduction to Design Patterns Learned in Java Language
Overview of how to create a server socket and how to establish a client socket
Chapter 4 Summary of Introduction to Design Patterns Learned in Java Language
Summary of Chapter 3 of Introduction to Design Patterns Learned in Java Language
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part2-
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part4-
Hit the New Relic API in Python to get the server status
Solving AOJ's Algorithm and Introduction to Data Structures in Python -Part3-
Introduction to Ansible Part 2'Basic Grammar'
Introduction to Python Hands On Part 1
Introduction to Protobuf-c (C language ⇔ Python)
How to wrap C in Python
Optimization learned with OR-Tools Part0 [Introduction]
Introduction to Ansible Part 1'Hello World !!'