TCP communication based on LINUX

Preface

TCP is connection-oriented, secure, non-repetitive, and orderly.
Server initialization process: socket() ----> bind()----> listen()---->accept()
Client initialization process: socket()---->connect()

We see that many interfaces are used. Before writing, we must understand the various network programming APIs, the role of each parameter, the order and restrictions of use

Network programming interface API

1.socket

int socket(int family, int type, int protocol);

socket() creates a socket. If it succeeds, it returns a file descriptor like open(). The application can use read/write to send and receive data on the network like reading and writing files. If the socket() call fails, Returns -1. For IPv4, the family parameter is specified as AF_INET. For the TCP protocol, the type parameter is specified as SOCK_STREAM, which represents a stream-oriented transmission protocol. If it is a UDP protocol, the type parameter specifies SOCK_DGRAM, which represents a datagram-oriented transmission protocol. The protocol parameter can be specified as 0.

2.bind

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

The function of bind() is to bind the parameters sockfd and myaddr together so that sockfd, the file descriptor used for network communication,
monitors the address and port number described by myaddr. As mentioned earlier, struct sockaddr * is a universal pointer type. The myaddr parameter can actually accept sockaddr structures of multiple protocols, and their lengths are different, so a third parameter, addrlen, specifies the length of the structure.

struct sockaddr_in server_addr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr))

Let me talk about the structure variable struct sockaddr_in server_addr because it needs to be bound to the socket after initialization. In the bind function, the struct sockaddr * parameter pointer is different from the previous one. Let’s introduce these two structures. Body differences:
1) sockaddr
sockaddr under /usr/include/bits/socket.h, check the structure of sockaddr:

struct sockaddr {
    
    
	sa_family_t sa_family;
	char sa_data[14];
};

The defect of sockaddr: sa_data mixes the target address and port information. The sockaddr_in solves this defect by storing the port number and IP address separately.
2) sockaddr_in
sockaddr_in is under /usr/include/netinet/in.h, check the structure of sockaddr_in:

struct sockaddr_in {
    
    
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
	uint8_t sin_zero[8];//用于填充的0字节
};

3) The difference and connection between sockaddr_in and sockaddr:
Insert picture description here
Connection: The memory size occupied by the two is the same, so they can be transformed into each other. In this sense, they are no different.
Difference: sockaddr is often used in the parameters of bind, connect, recvfrom, sendto and other functions to indicate address information and is a universal socket address.
And sockaddr_in is the address form of the socket in the internet environment. So in network programming, we will operate the sockaddr_in structure. Use sockaddr_in to create the required information, and finally use type conversion .
4) Network byte order and host byte order
In the initialization, we used two interfaces htonl() and htons(). Explain two more by the way:
1. The host byte order
is what we usually call big-endian and little-endian Mode, the big end is the low address to store the high byte, and the little end is the low address to store the low byte. Different CPUs have different endian types. These endianness refer to the order in which integers are stored in memory. This is called host order.
2. Network byte order The
memory address is divided into large and small ends, and the network data stream is also divided into big and small ends. The sending host usually sends out the data in the sending buffer in the order of memory address from low to high, and the receiving host saves the bytes received from the network in the receiving buffer in order, also in the order of memory address from low to high. save. Therefore, the address of the network data stream should be specified as follows: the data sent first is the low address, and the data sent later is the high address.
For example: 4 bytes of 32bit values ​​are transmitted in the following order: first, 0~7bit, second, 8~15bit, then 16~23bit, and finally 24-31bit. This is big endian. That is, all the binary integers in the TCP/IP header are required to be transmitted in the network.

Conversion function between host byte order and network byte order:

#include <arpa/inet.h>
/*将32位的长整数从主机字节序转换为网络字节序,*/ 
uint32_t htonl(uint32_t hostlong);
 /*将16位的短整数从主机字节序转换为网络字节序,*/
  uint16_t htons(uint16_t hostshort); 
 /*将32位的长整数从网络字节序转换为主机字节序,*/
  uint32_t ntohl(uint32_t netlong); 
 /*将16位的短整数从网络字节序转换为主机字节序,*/ 
 uint16_t ntohs(uint16_t netshort);

In this way, h stands for host (local host), n stands for net (network), and l is unsigned long (unsigned long).
If it is little-endian, these functions will convert the parameter to big-endian and return, if it is big-endian, it will return directly without conversion.
We generally set the IP address to INADDR_ANY in order to simplify programming. If you need to use a specific IP address, you need to use inet_addr and inet_ntoa to complete the exchange of string and in_addr structure. in_addr is a member of SOCKADDR_IN, which represents the IP address.
When encountering string types, we can use:

 //将字符串转换为in_addr类型  
 sock.sin_addr.S_un.S_addr =  inet_addr("192.168.1.111");  
 sock.sin_port = htons(5000);  
 //将in_addr类型转换为字符串  
 printf("inet_ntoa ip = %s\n",inet_ntoa(sock.sin_addr));

At this time we understand the initialization situation

3.listen

int listen(int sockfd, int backlog);

A typical server program can serve multiple clients at the same time. When a client initiates a connection, the accept() called by the server returns and accepts the connection. If there are a large number of clients initiate a connection and the server can’t handle it, customers who have not yet accepted The client is in the connection waiting state, listen() declares that the sockfd is in the listening state, and at most backlog clients are allowed to be in the connection waiting state. If more connection requests are received, it will be ignored. listen() returns 0 on success and -1 on failure.

4.accept

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

After the three-way handshake is completed, the server calls accept() to accept the connection. If there is no client connection request when the server calls accept(), it blocks and waits until a client connects. cliaddr is an outgoing parameter, and the client's address and port number will be sent out when accept() returns. The addrlen parameter is an incoming and outgoing parameter (value-result argument), the length of the buffer cliaddr provided by the caller is passed in to avoid buffer overflow problems, and the actual length of the client address structure (there is It may not fill up the buffer provided by the caller). If you pass NULL to the cliaddr parameter, it means you don't care about the client's address.

while (1) {
    
    
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
......
close(connfd);
}

The whole is a while loop, each time a client connection is processed in the loop. Since cliaddr_len is an incoming and outgoing parameter, the initial value should be re-assigned each time accept() is called. The parameter listenfd of accept() is the previous listening file descriptor, and the return value of accept() is another file descriptor connfd. After that, it communicates with the client through this connfd, and finally closes the connfd to disconnect. Close listenfd and go back to the beginning of the loop listenfd is still used as the accept parameter. accept() returns a file descriptor successfully, and returns -1 on error. Since the client does not need a fixed port number, it is not necessary to call bind(), and the client's port number is automatically assigned by the kernel. Note that the client is not allowed to call bind(), but it is not necessary to call bind() to fix a port number, and the server does not have to call bind(), but if the server does not call bind(), the kernel will automatically assign a listening port to the server , The port number is different every time the server is started, and the client will encounter trouble if it wants to connect to the server.
5.connect

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

The client needs to call connect() to connect to the server. The parameters of connect and bind are in the same form. The difference is that the parameter of bind is its own address, and the parameter of connect is the address of the other party.
6.write

Ssize_t write(int fd,const void *buf,size_t nbytes);

The Write function writes the contents of nbytes in buf to the file descriptor, returns the number of bytes written if it succeeds, and returns -1 if it fails, and sets the errno variable. In a network program, there are two possibilities when we describe comfortable writing data to the socket file:

1、write的返回值大于0,表示写了部分数据或者是全部的数据,这样用一个while循环不断的写入数据,但是循环过程中的buf参数和nbytes参数是我们自己来更新的,也就是说,网络编程中写函数是不负责将全部数据写完之后再返回的,说不定中途就返回了! 

2、返回值小于0,此时出错了,需要根据错误类型进行相应的处理。 

如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。

7.read

Ssize_t read(int fd,void *buf,size_t nbyte);

The Read function is responsible for reading the content from fd. When the reading is successful, read returns the number of bytes actually read. If the return value is 0, it means that the end of the file has been read. If it is less than 0, it means reading. error.
If the error is EINTR, it means that an interrupt error occurred during writing, and if it is EPIPE, it means that there is a problem with the network connection.
8.close

#include <unistd.h>
int close(int fd);

Turn off reading and writing.
It returns 0 on success, -1 on error, and error code errno: EBADF indicates that fd is not a valid descriptor; EINTR indicates that the close function is interrupted by a signal; EIO indicates an IO error.

Server-side socket programming

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#define PORT 2345
#define MAXSIZE 1024
int main(int argc, char *argv[])
{
    
    
int sockfd, newsockfd;
//定义服务端套接口数据结构
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_zise, portnumber;
//发送数据缓冲区
char buf[MAXSIZE];
//定义客户端套接口数据结构
int addr_len = sizeof(struct sockaddr_in);
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
    
    
fprintf(stderr, "create socket failed\n");
exit(EXIT_FAILURE);
}
puts("create socket success");
printf("sockfd is %d\n", sockfd);
//清空表示地址的结构体变量
bzero(&server_addr, sizeof(struct sockaddr_in));
//设置addr的成员变量信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
//设置ip为本机IP
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//server_addr.sin_addr.s_addr = inet_addr("192.168.69.129"),指定ip创建服务器端
if (bind(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) < 0)
{
    
    
fprintf(stderr, "bind failed \n");
exit(EXIT_FAILURE);
}
puts("bind success\n");
if (listen(sockfd, 10) < 0)
{
    
    
perror("listen fail\n");
exit(EXIT_FAILURE);
}
puts("listen success\n");
int sin_size = sizeof(struct sockaddr_in);
printf("sin_size is %d\n", sin_size);
if ((newsockfd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size)) < 0)
{
    
    
perror("accept error");
exit(EXIT_FAILURE);
}
printf("accepted a new connetction\n");
printf("new socket id is %d\n", newsockfd);
printf("Accept clent ip is %s\n", inet_ntoa(client_addr.sin_addr));
printf("Connect successful please input message\n");
char sendbuf[1024];
char mybuf[1024];
while (1)
{
    
    
int len = recv(newsockfd, buf, sizeof(buf), 0);
if (strcmp(buf, "exit\n") == 0)
break;
fputs(buf, stdout);
send(newsockfd, buf, len, 0);
memset(sendbuf, 0 ,sizeof(sendbuf));
memset(buf, 0, sizeof(buf));
}
close(newsockfd);
close(sockfd);
puts("exit success");
exit(EXIT_SUCCESS);
return 0;
}

Client socket programming

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#define PORT 2345
int count = 1;
int main()
{
    
    
int sockfd;
char buffer[2014];
struct sockaddr_in server_addr;
struct hostent *host;
int nbytes;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
    
    
fprintf(stderr, "Socket Error is %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//server_addr.sin_addr.s_addr = inet_addr("192.168.69.129"),指定ip的代码
//客户端发出请求
if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
{
    
    
fprintf(stderr, "Connect failed\n");
exit(EXIT_FAILURE);
}
char sendbuf[1024];
char recvbuf[2014];
while (1)
{
    
    
fgets(sendbuf, sizeof(sendbuf), stdin);
send(sockfd, sendbuf, strlen(sendbuf), 0);
if (strcmp(sendbuf, "exit\n") == 0)
break;
recv(sockfd, recvbuf, sizeof(recvbuf), 0);
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sockfd);
exit(EXIT_SUCCESS);
return 0;
}

It can be directly compiled for connection. In order to simplify programming, we generally replace the IP address directly with INADDR_ANY. If you want to enter the specified ip string, you can use the inet_addr() interface conversion. The conversion of these ips has been distinguished before.

Result (The server with ip 192.168.69.129 is specified, because it is on a virtual machine, so the client ip=server ip)

Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_42271802/article/details/108713114