Linux下socket网络编程实战思考

        socket网络编程是每个服务端开发人员必会技能,但是目前市面上各种web服务器容器,屏蔽了很多底层实现,导致很多socket通信细节被屏蔽,本文结合在linux下C语言socket通信说明一下网络通信的一些注意点。

目录

1.多进程模型tcp服务器

2.参数SO_REUSEADDR使用

3.参数SO_REUSEPORT使用

4.nginx中参数SO_REUSEADDR和SO_REUSEPORT的使用


1.多进程模型tcp服务器

        以下代码在主进程中bind,listen监听套接字,然后fork两个子进程,子进程中进行accept等待客户端连接,主进程关闭监听套接字。子进程中将接收到客户端的信息打印并返回给客户端。客户端使用网络测试工具nc。

#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main() {
    int port = 8090;
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        perror("scoket");
        return -1;
    }
    int ret = 0;
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }
    ret = listen(lfd, 128);
    char buf[8192] = {0};
    int len = 0;
    int total = 0;
    char tmpBuf[1024] = {0};
    struct sockaddr_in cliaddr;    
    socklen_t socketlen =sizeof(cliaddr);
    pid_t pro_cli;
    signal(SIGCHLD, SIG_IGN);
    std::cout << "main process: " << getpid() << std::endl;
    for (int i = 0; i < 2; i++) {
        pro_cli = fork();
        if (pro_cli > 0) {
            std::cout << "child process: " << pro_cli << std::endl;
        }
        if (0 == pro_cli) {
            int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
            std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
            while (true) {
                if (-1 == cfd) {
                    perror("accept");
                    return -1;
                }
                memset(buf, 0x00, sizeof(buf));
                memset(tmpBuf, 0x00, sizeof(tmpBuf));
                total = 0;
                if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
                    if (total + len < sizeof(buf)) {
                        memcpy(buf + total, tmpBuf, len);
                    }
                   send(cfd, buf, len, 0);
                }
                if ('\0' != buf[0]) {
                    std::cout << buf << std::endl;
                }
                
                if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
                //
                }
                else if (0 == len) { //客户端断开了连接
                    std::cout << "close client" << std::endl;
                    close(cfd);
                }
                else {
                    //perror("recv");
                }
            }
            close(cfd);
            close(lfd);
            exit(0);
        }
    }
    close(lfd);
    sleep(999999999); 
    std::cout << "main end" << std::endl;
    

    return 0;
}

运行效果如下:

Linux下socket网络编程实战思考_第1张图片 

Linux下socket网络编程实战思考_第2张图片

Linux下socket网络编程实战思考_第3张图片

 说明:如上显示创建三个进程,主进程17973,两个子进程17974和17975。开启两个nc客户端去连接服务器,并分别发送信息给服务器,服务器回显nc发送的消息。

关于close_wait状态:
(1)只会出现在被动关闭端
(2)状态一直停留在close_wait状态,而没有发送FIN信号并迁移到LAST_ACK状态,一般都是在收到主动关闭发起方的FIN信号后没有调用进程的close方法导致状态不迁移
(3)close_wait状态是不正常的,一般是由于bug产生,应该避免

2.参数SO_REUSEADDR使用

ctrl + C杀死服务器进程时,再重启服务端发生地址已经使用错误。如下所示

Linux下socket网络编程实战思考_第4张图片

Linux下socket网络编程实战思考_第5张图片

Linux下socket网络编程实战思考_第6张图片 

 服务端是主动关闭的一方,其处于FIN_WAIT2状态,再关闭nc客户端后,其状态为TIME_WAIT,所以服务端不能在此启动。

如何能让服务端进程退出后立马能够在此监听端口呢?

int opt = -1;

int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

完整代码如下所示:

#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main() {
    int port = 8090;
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        perror("scoket");
        return -1;
    }
    int ret = 0;
    int opt = -1;
    ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }
    ret = listen(lfd, 128);
    char buf[8192] = {0};
    int len = 0;
    int total = 0;
    char tmpBuf[1024] = {0};
    struct sockaddr_in cliaddr;    
    socklen_t socketlen =sizeof(cliaddr);
    pid_t pro_cli;
    signal(SIGCHLD, SIG_IGN);
    std::cout << "main process: " << getpid() << std::endl;
    for (int i = 0; i < 2; i++) {
        pro_cli = fork();
        if (pro_cli > 0) {
            std::cout << "child process: " << pro_cli << std::endl;
        }
        if (0 == pro_cli) {
            int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
            std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
            while (true) {
                if (-1 == cfd) {
                    perror("accept");
                    return -1;
                }
                memset(buf, 0x00, sizeof(buf));
                memset(tmpBuf, 0x00, sizeof(tmpBuf));
                total = 0;
                if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
                    if (total + len < sizeof(buf)) {
                        memcpy(buf + total, tmpBuf, len);
                    }
                   send(cfd, buf, len, 0);
                }
                if ('\0' != buf[0]) {
                    std::cout << buf << std::endl;
                }
                
                if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
                //
                }
                else if (0 == len) { //客户端断开了连接
                    std::cout << "close client" << std::endl;
                    close(cfd);
                }
                else {
                    //perror("recv");
                }
            }
            close(cfd);
            close(lfd);
            exit(0);
        }
    }
    close(lfd);
    sleep(999999999); 
    std::cout << "main end" << std::endl;
    

    return 0;
}

3.参数SO_REUSEPORT使用

Linux下socket网络编程实战思考_第7张图片

Linux下socket网络编程实战思考_第8张图片

 如上所示,端口处于LISTEN状态,再其动一个服务端程序,出现地址已经使用错误,如何在端口已经监听的情况下再次监听端口呢?

setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

完整代码如下:

#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main() {
    int port = 8090;
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        perror("scoket");
        return -1;
    }
    int ret = 0;
    int opt = -1;
    ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (-1 == ret) {
        perror("setsockopt");
        return -1;
    }
    
    setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    if (-1 == ret) {
        perror("setsockopt");
        return -1;
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;
    ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
    if (-1 == ret) {
        perror("bind");
        return -1;
    }
    ret = listen(lfd, 128);
    char buf[8192] = {0};
    int len = 0;
    int total = 0;
    char tmpBuf[1024] = {0};
    struct sockaddr_in cliaddr;    
    socklen_t socketlen =sizeof(cliaddr);
    pid_t pro_cli;
    signal(SIGCHLD, SIG_IGN);
    std::cout << "main process: " << getpid() << std::endl;
    for (int i = 0; i < 2; i++) {
        pro_cli = fork();
        if (pro_cli > 0) {
            std::cout << "child process: " << pro_cli << std::endl;
        }
        if (0 == pro_cli) {
            int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
            std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
            while (true) {
                if (-1 == cfd) {
                    perror("accept");
                    return -1;
                }
                memset(buf, 0x00, sizeof(buf));
                memset(tmpBuf, 0x00, sizeof(tmpBuf));
                total = 0;
                if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
                    if (total + len < sizeof(buf)) {
                        memcpy(buf + total, tmpBuf, len);
                    }
                   send(cfd, buf, len, 0);
                }
                if ('\0' != buf[0]) {
                    std::cout << buf << std::endl;
                }
                
                if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
                //
                }
                else if (0 == len) { //客户端断开了连接
                    std::cout << "close client" << std::endl;
                    close(cfd);
                }
                else {
                    //perror("recv");
                }
            }
            close(cfd);
            close(lfd);
            exit(0);
        }
    }
    close(lfd);
    sleep(999999999); 
    std::cout << "main end" << std::endl;
    

    return 0;
}

网络状态和运行状态如下:

Linux下socket网络编程实战思考_第9张图片 

 通过运行看,不同进程可以监听相同的端口

4.nginx中参数SO_REUSEADDR和SO_REUSEPORT的使用

Linux下socket网络编程实战思考_第10张图片

Linux下socket网络编程实战思考_第11张图片 

 

 

你可能感兴趣的:(socket)