Linux Socket Programming (1): Simple realization of TCP communication between processes

The server and client communicate through Socket

Reference blog:
[Relationship between connect(), listen() and accept() in TCP network programming] -> The following figure is from this blog (https://blog.csdn.net/tennysonsky/article/details/ 45621341)
Web socket programming basic api
Insert picture description here

Server:

  1. socket(): Create a socket, set the socket IP address type and transmission protocol type.
  2. bind(): Bind the ip address and port number to the socket.
  3. listen(): Turn the socket into a passive connection listening socket. The port number of the listening socket, ready to receive connection requests from the client at any time.
    The listen() function does not block. The main thing it does is tell the Linux kernel the socket and the connection queue length corresponding to the socket, and then the listen() function ends.
  4. accept(): Block until a client successfully connects, and remove the completed connection in the queue.
    The accept() function takes out a completed connection from the head of the established connection queue. If there is no completed connection in this queue, the accept() function will block the current thread until the completed client connection in the queue is taken out .
  5. read()\write(), recv()\send(): exchange data with the client.
    read() is a blocking I/O mode, the server process will be blocked until the complete data is received in the kernel buffer and copied from the kernel buffer to the process.
  6. close(): Close the socket, that is, close the connection.

Client:

  1. socket(): Create a socket, set the socket IP address type and transmission protocol type.
    The client is the party that sends the connection request, and the port does not need to be fixed, but can be randomly assigned, so there is no need to bind.
  2. connect(): The client requests a connection with the server according to the server's ip address and port number.
    The client actively connects to the server, and the connection is established through a three-way handshake. The process of this connection is completed by the kernel, not by this function. The function of this function is only to notify the Linux kernel to automatically complete the TCP three-way handshake connection, and finally Return the result of the connection to the return value of this function (0 for successful connection, -1 for failure).
  3. read()\write()、recv()\send()
  4. close()

Server code

The main function is to convert all lowercase letters in the string sent by the client into uppercase letters, and then return to the client. (PS: I am new to learning, so the code comments are more detailed, in fact, the amount of code is not much) Reference blog: Linux socket programming

#include <iostream>
#include <stdio.h>
#include <cstring>       // void *memset(void *s, int ch, size_t n);
#include <sys/types.h>   // 数据类型定义
#include <sys/socket.h>  // 提供socket函数及数据结构sockaddr
#include <arpa/inet.h>   // 提供IP地址转换函数,htonl()、htons()...
#include <netinet/in.h>  // 定义数据结构sockaddr_in
#include <ctype.h>       // 小写转大写
#include <unistd.h>      // close()、read()、write()、recv()、send()...
using namespace std;

const int flag = 0; // 0表示读写处于阻塞模式
const int port = 8080;
const int buffer_size = 1<<20;


int main(int argc, const char* argv[]){
    
    
    // 创建服务器监听的套接字。Linux下socket被处理为一种特殊的文件,返回一个文件描述符。
    // int socket(int domain, int type, int protocol);
    // domain设置为AF_INET/PF_INET,即表示使用ipv4地址(32位)和端口号(16位)的组合。
    int server_sockfd = socket(PF_INET,SOCK_STREAM,0);  
    if(server_sockfd == -1){
    
    
        close(server_sockfd);
        perror("socket error!");
    }
    // /* Enable address reuse */
    // int on = 1;
    // int ret = setsockopt( server_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

    // 此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr)); // 结构体清零
    server_addr.sin_family = AF_INET;  // 协议
    server_addr.sin_port = htons(port);  // 端口16位, 此处不用htons()或者错用成htonl()会连接拒绝!!
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地所有IP
    // 另一种写法, 假如是127.0.0.1
    // inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);


    // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 
    // bind()函数的主要作用是把ip地址和端口绑定到套接字(描述符)里面
    // struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
    // 一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中。
    if(bind(server_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1){
    
    
        close(server_sockfd);
        perror("bind error");
    }
    // 第二个参数为相应socket可以排队的准备道来的最大连接个数
    if(listen(server_sockfd, 5) == -1){
    
    
        close(server_sockfd);
        perror("listen error");
    }

    printf("Listen on port %d\n", port);
    while(1){
    
    
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        // accept()函数从处于established状态的连接队列头部取出一个已经完成的连接,
        // 如果这个队列没有已经完成的连接,accept()函数就会阻塞当前线程,直到取出队列中已完成的客户端连接为止。
        int client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);

        char ipbuf[128];
        printf("client iP: %s, port: %d\n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
            ntohs(client_addr.sin_port));

        // 实现客户端发送小写字符串给服务端,服务端将小写字符串转为大写返回给客户端
        char buf[buffer_size];
        while(1) {
    
    
            // read data, 阻塞读取
            int len = recv(client_sockfd, buf, sizeof(buf),flag);
            if (len == -1) {
    
    
                close(client_sockfd);
                close(server_sockfd);
                perror("read error");
            }else if(len == 0){
    
      // 这里以len为0表示当前处理请求的客户端断开连接
                break;
            }
            printf("read buf = %s", buf);
            // 小写转大写
            for(int i=0; i<len; ++i) {
    
    
                buf[i] = toupper(buf[i]);
            }
            printf("after buf = %s", buf);

            // 大写串发给客户端
            if(send(client_sockfd, buf, strlen(buf),flag) == -1){
    
    
                close(client_sockfd);
                close(server_sockfd);
                perror("write error");
            }
            memset(buf,'\0',len); // 清空buf
        }
        close(client_sockfd);
    }
    close(server_sockfd);

    return 0;
}

Client code

// client 端相对简单, 另外可以使用nc命令连接->nc ip prot
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>


const int port = 8080;
const int buffer_size = 1<<20;
int main(int argc, const char *argv[]) {
    
    
    

    int client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sockfd == -1) {
    
    
        perror("socket error");
        exit(-1);
    }
    

    struct sockaddr_in client_addr;
    bzero(&client_addr, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(port);
    inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr.s_addr);
    
    int ret = connect(client_sockfd, (struct sockaddr*)&client_addr, sizeof(client_addr));
    if (ret == -1) {
    
    
        perror("connect error");
        exit(-1);
    }
    
    while(1) {
    
    
        char buf[buffer_size] = {
    
    0};
        fgets(buf, sizeof(buf), stdin); // 从终端读取字符串
        write(client_sockfd, buf, strlen(buf));
        //接收, 阻塞等待
        int len = read(client_sockfd, buf, sizeof(buf));
        if (len == -1) {
    
    
             perror("read error");
             exit(-1);
        }
        printf("client recv %s\n", buf);
        
    }
    
    close(client_sockfd);
    return 0;
}

Encounter problems

  1. connect error: Connection refused, When the client connected to the server, the connection was refused.
    Reason: Due to the incorrect use of the host byte order and network byte order conversion functions, the port conversion is used htonl(). If the port is 16-bit, htons() and ntohs() should be used; if the IP is 32-bit, htonl() and ntohl() should be used.
    (Note: There are big-endian byte order and little-endian byte order in the data stream. The TCP/IP protocol stipulates that the network data stream adopts big-endian byte order. Through the analysis of the storage principle of the big-endian endianness, it can be found that for the char data, Because it only occupies one byte, this problem does not exist. This is one of the reasons why the data buffer is generally defined as char type. For non-char data such as IP address and port number, the data must be sent to the network Convert it to big-endian mode before uploading, and convert it to the storage mode that matches the receiving host after receiving the data.)
// Linux 系统为主机字节序和网络字节序的转换提供了4个函数
#include <arpa/inet.h>
/*主机字节顺序 --> 网络字节顺序*/
uint32_t htonl(uint32_t hostlong);/* IP */
uint16_t htons(uint16_t hostshort);/* 端口 */
/*网络字节顺序 --> 主机字节顺序*/
uint32_t ntohl(uint32_t netlong);/* IP */
uint16_t ntohs(uint16_t netshort);/* 端口 */
  1. bind error: Address already in use, The bound address (ip+port) is already in use.
    Reason: Ctrl+Z interrupts the execution of the task, but the task is not over, it just maintains a suspended state in the process. (Ctrl+C is to forcibly terminate the execution of the program and end the process)
    Use to netstat -anp | grep 8080view the status of the server listening port 8080. It is found that the server process with pid 4600 and the client process with 4623 are still in the ESTABLISHEDstate and have not ended. With the kill -9 pidend client and server processes, release port.
    Problem: Address already in use

Guess you like

Origin blog.csdn.net/XindaBlack/article/details/105599656