TCP/IP Network Communication Model

Essentially, it’s just another way of inter process comunication(IPC) between tcp/ip network applications. The layered structure of the network protocol stack only decouples the complexity of communication in the application layer, which does not need to consider data loss, data duplication and transmission timeliness in the communication process. Such problems are solved by the transport layer, the application layer only focuses on messages and processing logic in the layer. Therefore, the general transport layer modules work in the system kernel to provide the same service for all processes in the system. The operating system encapsulates the transport layer and other underlying protocols and provides uniform access interfaces for the application layer. This article mainly describes how the TCP transport layer provides abstract services for the application layer, and attempts to implement a minimalist application layer protocol based on TCP, observing the TCP protocol from the perspective of the application layer.

TCP Model

TCP provides end-to-end connection service for the application layer, and full duplex communication can be achieved between the two sides. The communication data is sent and received in the form of reliable byte stream. Because UNIX systems are designed according to the everything-is-file rule, network devices are also abstracted to files, so network devices can be accessed using the common UNIX system IO. This is all that a process referring to TCP stack communication knows. As for how to ensure the reliability of data transmission, the flow control of data traffic when the network congestion happened, all these are not visible to the process.

End-to-end connection

TCP only supports communication between two processes in the network, and a connection needs to be established before the process can communicate. Typically, the two processes are client and server relationships. When a process sends and receives data, it simply reads and writes from a file descriptor bound to the network. For the system kernel, the file descriptor that establishes the connection is bound to a quad set {client IP, client TCP port number, server IP, server TCP port number}, which can uniquely identifie a TCP connection.

Full-duplex

A process that participates in communication and can send and receive data at the same time.

Byte stream

Communication data is a reliable byte stream, which means that process communication data is sent and received byte by byte according to the sort. Therefore, the application layer needs to complete the parsing and segmentation of application-layer messages.

Sequence of communication

The process implements network communication by calling the set of Socket API functions. The communication process is roughly divided into four stages: communication preparation stage, TCP connection establishment stage, data interaction stage, and TCP connection release (disconnect) stage.

Preparation

  • the client mainly calls the function socket() to apply file descriptor sockfd. If the client does not call the function bind() to bind the local TCP port number, the operating system will automatically assign a temporary port number to it. Sockfd corresponds to {client IP, client temporary TCP port number}.
  • the server mainly calls the function socket() to apply file descriptor listenfd. The listenfd is used to receive connection requests from unknown clients. The server must call the function bind() to bind the TCP port it is listening to, which is the well-known port number of the server process. The listen() function informs the system kernel that the listenfd is only used to wait on connection requests from unknown clients.

Connection establishment phase

  • the client calls the function connect() to initiate a request to establish a connection to the server. If the TCP connection is established successfully, the file descriptor sockfd binding is a quad {server IP, server TCP port number, client IP, client temporary TCP port number}.
  • the server side calls the function accept(), which blocks and waits for the TCP connection request of the unknown client. If accept() returns successfully, the return value is the file descriptor connectfd. The connectfd binding content is a quad {server side IP, server side TCP port number, client side IP, client side temporary TCP port number}.

Data interaction phase

  • At this point, both ends can use UNIX standard IO functions read() and write() or use Socket function family send() and recv() to send and receive data, the client read and write object is sockfd, the server read and write object is connectfd.

Release

  • When either end completes sending or receiving data, use the function shutdown() to close the “r”, “w”, or “rw” operations to the file descriptor, or use the function close() to close the “rw” operations to the file descriptor.

Example

The following is an implementation of the simple application layer communication protocol mentioned earlier in c. To simplify error handling in the code, the lib function in UNPv3 (“lib/unp.h”) is used.

Server: a “Server” process running on host S

Client: a “Client” process running on host C

The content of the request message sent by the client is a string, “request,hostname”

After receiving the request message, the server gets its own host name, and then replies it to the client, “response,< the obtained host name >”.

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "lib/unp.h"

int main(int argc, char **argv)
{
//一些初始化工作
int sockfd = 0;
char response[MAXLINE + 1];
char request[] = "request,hostname\r\n";
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
bzero(response, MAXLINE + 1);

//设置将要连接服务器进程的IP地址和TCP端口号
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(1982);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

//创建Socket,AF_INET表示IPv4,SOCK_STREAM表示TCP
sockfd = Socket(AF_INET, SOCK_STREAM, 0);

//发起连接请求, sockfd和Server进程的IP地址,TCP端口号绑定
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

//发送请求消息
Write(sockfd, (char*) request, strlen(request));

//等待从Server进程接收响应消息
Readline(sockfd, response, MAXLINE);

//将结果输出到标准输出
printf("%s", response);

//关闭Socket,通知服务进程通信结束
Close(sockfd);

//进程结束
exit(0);
}

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "lib/unp.h"

int main(int argc, char** argv)
{
//一些初始化工作
int listenfd = 0; //Server进程监听文件描述符
int connectfd = 0; //Server进程连接文件描述符
int length = 0; //Server进程接收到请求长度
char responsbuf[MAXLINE] = { 0 }; //Server响应消息缓存
char requestbuf[MAXLINE] = { 0 }; //Client请求消息缓存
char response_prefix[] = "response,"; //响应消息前缀
char response_suffix[] = "\r\n"; //响应消息后缀

//连接Server进程的Client进程的地址信息
struct sockaddr_in clntaddr;
bzero(&clntaddr, sizeof(clntaddr));
socklen_t sock_length = sizeof(struct sockaddr_in);

//Server进程所在的IP地址和监听端口号1982
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET; //使用IPv4,实际上也可以使用IPv6
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //监听各个网络接口
servaddr.sin_port = htons(1982); //监听端口号1982

//创建Socket,AF_INET表示IPv4,SOCK_STREAM表示TCP
listenfd = Socket(AF_INET, SOCK_STREAM, 0);

//将服务器的地址,端口号绑定到新创立的socket文件描述符listenfd
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

//通知OS Kernel,新创建的socket文件描述符用于服务器进程
Listen(listenfd, LISTENQ);

//开始处理来自Client进程的请求
while (1)
{
//接受来自Client进程的连接请求,后两个参数保存正在发起连接请求的Client进程信息
connectfd = Accept(listenfd, (SA *) &clntaddr, &sock_length);

//接收Client进程的消息
bzero(requestbuf, sizeof(requestbuf));

Readline(connectfd, requestbuf, sizeof(requestbuf));

printf("Receive message from client process on %s: %s\n",
inet_ntoa(clntaddr.sin_addr), requestbuf);

//Server进程返回响应消息给Client进程,消息格式为"response, <hostname>\r\n"
bzero(responsbuf, sizeof(requestbuf));
strncpy(responsbuf, response_prefix, strlen(response_prefix));
gethostname(responsbuf + strlen(response_prefix), MAXLINE);
strncat(responsbuf, response_suffix, strlen(response_suffix));
Write(connectfd, responsbuf, strlen(responsbuf));

//再次阻塞等待Client进程的消息,这次等到的是EOF,收到EOF后阻塞态结束,Readline返回
Readline(connectfd, requestbuf, sizeof(requestbuf));

//结束与Client进程通信
Close(connectfd);
}

exit(0);
}

Run

Server

1
2
3
4
toto@ServerOS:~$ server 
Receive message from client process on 10.16.56.2: request,hostname
Receive message from client process on 10.16.56.2: request,hostname
~阻塞等待

Client

1
2
3
4
toto@ClientOS:~$ client 10.22.5.3
response,guru
toto@ClientOS:~$ client 10.22.5.3
response,guru

Reference

Some more reference

  • RFC2616,“Hypertext Transfer Protocol/1.1”和《HTTP The Definitive Guide》
  • RFC3117,“On the Design of Application Protocols”
  • RFC3205,“On the use of HTTP as a Substrate”
  • RFC5321,“Simple Mail Transfer Protocol”