Summary of Unix network programming -- simple server implementation

Posted by tejama on Sun, 13 Feb 2022 15:13:19 +0100

1. Preface

To realize a simple server, it needs several steps to establish a connection with the client and receive the data from the client for processing.

The figure above shows the basic socket functions needed to implement the TCP client / server program.
This article focuses on the implementation of server-side.

2.sokcet()

First, call the socket function to get a listening socket file descriptor as the starting point for subsequent use of bind() and listen()
The header file "#include < sys / socket. H >" should be included when using.
server.cpp

#include <sys/socket.h>
int main(int argc, char* argv[]) {
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	... 
}

The original function is socket (int domain, int type, int protocol);
The first parameter, domain, indicates the protocol family you use. Common parameters are:

  • AF_INET: IPv4 protocol family
  • AF_INET6: IPv6 protocol family
  • AF_LOCAL, AF_UNIX: local interaction

The second parameter type specifies the socket type, or a transmission method, commonly used:

  • SOCK_STREAM: byte stream socket
  • SOCK_DGRAM: packet socket

The third parameter, protocol, is set to a constant value of a protocol, or 0, which is usually set to 0. It indicates the protocol used when the combination of the first two parameters by default is used. Here AF_INET and sock_ The stream parameter combination uses TCP protocol by default.

3.bind()

After obtaining the listening socket file descriptor through socket(), you can bind the protocol address to the server through bind().
server.cpp:

#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 80
int main(int argc, char* argv[]) {
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_ANY);
	address.sin_port = htons(PORT); 
	bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
		...
}

The original function is: bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
The first parameter sockfd is the socket file descriptor obtained earlier,
The second parameter is a pointer to the address structure, which needs to be used after initializing several parameters in the structure. Here, a concept of network byte order is involved.
General operating systems use little endian, while the data in the network adopts network byte order, that is, big endian. Therefore, the conversion functions htonl() and htons() need to be used to realize the conversion between host byte order and network byte order.
htonl : host to network long
htons: host to network short
The third parameter is the length of the address structure.

4.listen()

After binding, all you need to do is listen(), which is used to LISTEN to the behavior of the client and convert the state of the socket from CLOSED to LISTEN.
server.cpp:

#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 80
int main(int argc, char* argv[]) {
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_ANY);
	address.sin_port = htons(PORT); 
	bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
	
	listen(listen_fd, 5);
	...
}

The original function is: listen(int sockfd, int backlog);
The second parameter, backlog, indicates the sum of the size of the unfinished connection queue + the completed connection queue, which is generally set to 5. However, in Linux system, the actual value is multiplied by a number called fuzzy factor of 1.5, so it is actually 8.

5.accept()

The server calls accept() to return a completed connection file name descriptor (connect socket fd) from the header of the completed connection queue

#include <sys/socket.h>
#include <arpa/inet.h>
#define PORT 80
int main(int argc, char* argv[]) {
	int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_ANY);
	address.sin_port = htons(PORT); 
	bind(listen_fd, (struct sockaddr*)&address, sizeof(address));
	
	listen(listen_fd, 5);
	struct sockaddr_in client_addr;
	socklen_t client_addrlength = sizeof(client_addr);
	int connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_addrlength);
	...
	close(listen_fd);
	close(connfd);
}

The original function is int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Needless to say, the first parameter is used to receive a pointer to the address structure of the client, and the third parameter is its size.
Therefore, you need to create a client address structure variable for receiving.

Topics: Unix network server