Linux 下使用select 实现TCP server和TCP client

基于TCP(面向链接)的socket编程

Server端的流程如下:

  • 1 创建Socket套接字

  • 2 将套接字绑定到一个本地地址(IP)和端口(port)上

  • 3 将套接字设置为监听模式,准备接收客户端请求(listen)

  • 4 等待客户端请求,当请求到来后,接受链接请求,返回一个新的对应于此次链接的套接字(accept)

  • 5 用返回的套接字和客户端进行通信(send/recv read/write)

  • 6 返回,等待另一个客户请求

  • 7 关闭套接字

/*
*tcp_server.c
*/
#include "tcp-server.h"

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>


static const size_t MAX_FRAME_SIZE = 4100;
static const int PORT = 1234;

static device_t tcp_device;
static int listen_fd = -1;
static int tcp_server_init(device_t *dev) {
    struct sockaddr_in addr;
    //创建socket,获取一个socket描述符
    if ((listen_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        fprintf(stderr, "%s(): cannot create socket", __FUNCTION__);
        return -1;
    }
    //设置server的地址同通信协议
    memset(&addr, 0, sizeof(struct sockaddr_in));
    //协议族: AF_INET AF_INET6 AF_LOCAL
    addr.sin_family = AF_INET;
    //取本地任意一个地址进行通信
    // uint32_t htonl(uint32_t hostlong) 将主机无符号长整型转化为网络字节
    //INADDR_ANY=0.0.0.0
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // uint16_t htons(uint16_t hostshort) 将hostshort 转化为网络字节序
    //网络字节采用大端模式(big-ending),高位存放在低地址
    addr.sin_port = htons(PORT);
    if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
        fprintf(stderr, "%s(): bind() failed", __FUNCTION__);
        goto failed;
    }
    if (listen(listen_fd, LISTEN_BACKLOG) != 0) {
        fprintf(stderr, "%s(): listen() failed", __FUNCTION__);
        goto failed;
    }
    fprintf(stderr, "%s(): tcp_server running on %s port=%d\n", __FUNCTION__, inet_ntoa(addr.sin_addr), PORT);
    dev->recv_buf = (unsigned char *)malloc(MAX_FRAME_SIZE);
    dev->recv_cnt = 0;
    if (dev->recv_buf == NULL) {
        fprintf(stderr, "%s(): not enough memory", __FUNCTION__);
        goto failed;
    }

    dev->dev_send = write;
    return 0;
failed:
    close(listen_fd);
    listen_fd = -1;
    return -1;
}
static int tcp_server_main(int argc, char **argv) {
    //使用fd_set以及select实现非阻塞模式
    fd_set rfds, afds;
    int max_fd = 0, fd;
    int i;
    struct sigaction sa;

    //清空文件描述符集合
    FD_ZERO(&afds);
    if (listen_fd != -1) {
        FD_SET(listen_fd, &afds);
        max_fd = MAX(max_fd, listen_fd);
    }
    if (max_fd == 0) {
        fprintf(stderr, "%s(): terminated\n", __FUNCTION__);
        return -1;
    }

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = tcp_server_handle_sighup;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &sa, NULL) == -1) {
        fprintf(stderr, "%s(): sigaction() error: %d", __FUNCTION__, errno);
        return -1;
    }

    while (1) {
        rfds = afds; /* structure assignment */

        //监听所有文件描述符
        if (select(max_fd + 1, &rfds, NULL, NULL, NULL) < 0) {
            if (errno == EINTR)
                continue;
            fprintf(stderr, "%s(): select() error: %d", __FUNCTION__, errno);
            break;
        }

        if (FD_ISSET(listen_fd, &rfds)) { /* client connected */
            struct sockaddr_in addr;
            socklen_t addr_len;

            addr_len = sizeof(addr);
            fd = accept(listen_fd, (struct sockaddr *)&addr, &addr_len);
            if (fd != -1) {
                //把建议链接的客户端文件描述符添加到监听集合
                FD_SET(fd, &afds);
                fprintf(stderr, "%s(): client %s connented\n\n", __FUNCTION__, inet_ntoa(addr.sin_addr));
                max_fd = MAX(max_fd, fd);
            }
        }
        //遍历所有文件描述符,接收客户端的数据
        for (fd = 0; fd <= max_fd; fd++) {
            if (fd == listen_fd || !FD_ISSET(fd, &rfds)) {
                continue;
            }
            //XXX:这里把每一个客户端的信息都存在一个结构里面,应该为每一个链接创建不同的链接来进行通信
            tcp_device.fd = fd;
            if (tcp_server_recv(&tcp_device) <= 0) {
                /* client closed */
                close(fd);
                FD_CLR(fd, &afds);
            }
        }
        __sleep(30L);
    }
    tcp_server_close(&tcp_device);
    return 0;
}

int main(int argc, char **argv) {
    if (tcp_server_init(&tcp_device) != 0) {
        fprintf(stderr, "%s(): tcp_server_init failed!\n", __FUNCTION__);
        return -1;
    }

    tcp_server_main(argc, argv);

    return 0;
}

Client 端流程如下:

  • 1 创建Socket套接字

  • 2 向服务端发送链接请求(connect)

  • 3 和服务端进行通信(send/recv)

  • 4 关闭套接字

/*
*tcp_client.c
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

static int socket_fd = -1;
static int quit = 0;
static const size_t MAX_FRAME_SIZE = 4100;
static const char *tcp_server_ip = "0.0.0.0";
static const int tcp_server_port = 1234;

static void tcp_client_handle_sighup(int sig) {
    //TODO:do something while recv sighup
}

static int8_t client_connet_to_server() {
    struct sigaction sa;
    struct sockaddr_in clientAddr;
    struct sockaddr_in serverAddr;

    if (socket_fd != -1) {
        shutdown(socket_fd, SHUT_RDWR);
        close(socket_fd);
        socket_fd = -1;
    }

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGPIPE, &sa, NULL) == -1) {
        fprintf(stderr, "sigaction() Failed: %d", errno);
        return -1;
    }

    bzero(&clientAddr, sizeof(clientAddr));
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_addr.s_addr = htons(INADDR_ANY);
    clientAddr.sin_port = htons(0);

    //创建client
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0) {
        fprintf(stderr, "initial tcp_client create socket failed\n");
        return -1;
    }

    // 3.将socket建立为非阻塞,此时socket被设置为非阻塞模式
    int flags = fcntl(socket_fd, F_GETFL, 0);       //获取建立的sockfd的当前状态(非阻塞)
    fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);  //将当前sockfd设置为非阻塞

    if (bind(socket_fd, (struct sockaddr *)&clientAddr, sizeof(clientAddr)) == -1) {
        fprintf(stderr, "initial tcp_client bind socket failed\n");
        return -1;
    }

    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(tcp_server_ip);
    serverAddr.sin_port = htons(tcp_server_port);

    while (connect(socket_fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) {
        static int8_t circleTime = 0;
        ++circleTime;
        if (circleTime >= 20) {
            circleTime = 0;
        }
        if (quit) {
            fprintf(stderr, "tcp client thread terminated\n");
            return -1;
        }

        sleep(2);
        fprintf(stderr, "start to connect the host:%s\n", tcp_server_ip);
    }
    fprintf(stderr, "connect the host:%s success\n", tcp_server_ip);
    return 0;
}

int main(int argc, char **argv) {
    char *buf = "client test";
    char recv_buf[MAX_FRAME_SIZE];
    ssize_t recv_len = 0;
    int i;

    if (client_connet_to_server() == 0) {
        while (1) {
            if (write(socket_fd, buf, strlen(buf)) <= 0) {
                fprintf(stderr, "send %s to server failed!\n", buf);
            }
            recv_len = read(socket_fd, recv_buf, MAX_FRAME_SIZE);
            if (recv_len > 0) {
                //TODO:code the protocol recv here(may have start or end flag)
                fprintf(stderr, "/**************************/\n");
                fprintf(stderr, "%s\n ", recv_buf);
                fprintf(stderr, "/**************************/\n\n");
            }
            sleep(5);
        }
    };
}

完整代码

关于select 请参考

发布了8 篇原创文章 · 获赞 8 · 访问量 251

猜你喜欢

转载自blog.csdn.net/whuer_xiaojie/article/details/105159942