Linux下socket网络编程实战思考

news2025/1/11 8:13:58

        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 <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>


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;
}

运行效果如下:

 

 说明:如上显示创建三个进程,主进程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杀死服务器进程时,再重启服务端发生地址已经使用错误。如下所示

 

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

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

int opt = -1;

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

完整代码如下所示:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>


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使用

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

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

完整代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <stdio.h> 
#include <stdlib.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <ctype.h>
#include <signal.h>


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;
}

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

 

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

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

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/627657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows11安装mysql8.0.32步骤

1、下载mysql安装包 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 2、添加配置文件 mysql安装文件目录下新建一个文件名&#xff1a;my.inimy.ini文件中复制如下内容&#xff1a; [mysqld] # 设置3306端口 port3306 # 设置mysql的安装目录 ---这里输入你安装…

Linux中的C/C++语言调试工具 GDB

Linux中的C/C语言调试工具 GDB GDB&#xff08;GNU Debugger&#xff09;是一个功能强大的调试器&#xff0c;用于调试和分析程序的执行过程。它是GNU项目的一部分&#xff0c;可以在多个操作系统上使用&#xff0c;包括Linux、Unix和Windows。 GDB主要用于调试编译后的可执行…

大模型扎堆「赶考」,语文还是国产AI行,文言文能力超过95%考生

量子位 | 公众号 QbitAI ChatGPT诞生后的第一场全国高考&#xff0c;考生忙&#xff0c;大模型们也很忙。 这不&#xff0c;高考还没结束&#xff0c;AI挑战高考题就成了刷屏热搜话题&#xff0c;各家大模型都被拉出来写起了高考作文&#xff0c;甚至是参与整场考试。 &#…

【MySQL事务】保证数据完整性的利器

1、事务的认识 事务&#xff1a;事务就是将多个SQL给打包在一起&#xff0c;组成一个整体。组成这个整体的各个SQL&#xff0c;要么全部成功&#xff0c;要么全部失败。 举例说明&#xff1a; 情人节到了&#xff0c;滑稽老铁打算给他女朋友小美发给红包&#xff0c;但是他又害…

msvcp140.dll重新安装的解决方法(一键安装方法)

msvcp140.dll电脑文件中的dll文件&#xff0c;即动态链接库文件&#xff0c;若计算机中丢失了某个dll文件&#xff0c;就会导致某些软件和游戏等程序无法正常启动运行&#xff0c;并且导致电脑系统弹窗报错&#xff0c;其安装方法&#xff1a;1、打开浏览器输入“【dll修复程序…

[Diffusion] Speed is all your need

论文简要总结 刚读了下speed is all you need这个论文, https://arxiv.org/pdf/2304.11267.pdf 只是用的SD1.4没有对网络进行改造。 只做了4个改动 1 是对norm采用了groupnorm (GPU shader加速) 2 采用了GELU (GPU shader加速) 3 采用了两种attention优化&#xff0c;是partiti…

一个简单的MCU内存管理模块(附源码)

现在非常多的的MCU性能都还不错&#xff0c;同时用户也会去扩展一些外部RAM&#xff0c;那么如何高效便捷的管理这些内存是一个重要话题。 今天给大家分享一份源码&#xff1a;基于无操作系统的STM32单片机开发&#xff0c;功能强大&#xff0c;可申请到地址空间连续的不同大小…

如何应对公民开发者的挑战?低代码流程自动化助你打造高效系统

如今&#xff0c;企业越来越倾向于采用公民开发作为提升流程自动化和数字化转型的手段。这种方法让非程序员&#xff08;如客户和员工&#xff09;&#xff0c;有机会为特定任务创建自己的应用程序。它还使公司能够快速调整以适应客户偏好或业务需求。 尽管这种方法有很多好处&…

Prometheus+Grafana普罗米修斯搭建+监控MySQL

PrometheusGrafana普罗米修斯搭建监控MySQL 一&#xff0c;Prometheus 1.什么是Prometheus&#xff1f; ​ Prometheus 是 Cloud Native Computing Foundation 的一个监控系统项目, 集采集、监控、报警等特点于一体。 ​ Prometheus主要受启发于Google的Brogmon监控系统&…

数字化转型浪潮下,汽车零部件企业如何逐鹿突围?

一、汽车行业当下趋势 数据来源&#xff1a;汽车工业协会 1.乘用车及新能源车市场&#xff1a;2023年1-4月&#xff0c;乘用车销量同比增长6.8%&#xff0c;新能源车销量同比增长35.8%。 2.行业趋势&#xff1a;汽车行业生存压力大&#xff0c;4月中国汽车经销商库存系数下降…

中国科学院稀土研究院-PLC采集项目案例

1.项目背景 中国科学院稀土研究院&#xff08;江西稀土研究院&#xff09;隶属中国科学院。 作为中国国内唯一专注于稀土科技创新的国立科研机构&#xff0c;中国科学院稀土研究院将面向国家战略和产业发展重大需求&#xff0c;围绕稀土绿色、高效、均衡、高值化利用的核心科学…

【Spring MVC】没有Spring MVC,Spring Boot表现得毫无用处? ? ?走进Spring MVC,学习热部署自动运行程序 ! ! !

前言: 大家好,我是良辰丫,从今天开始,我们就要进入Spring MVC的学习了,前面的文章中我们的很多注解都没有讲解,是不是看的脑瓜嗡嗡的,哈哈,没关系,在我们Spring MVC中我们就要详细讲解那些注解了,请跟随良辰的脚步,gogogo! ! !&#x1f48c;&#x1f48c;&#x1f48c; &#x…

希望之星、黄昏之星、三只乌鸦……怎么用 DolphinDB 快速计算 K 线?

K 线技术分析是股票投资中很常用的一种分析方法&#xff0c;主要通过历史价格图表中的数据来预测未来市场趋势。一根 K 线包括四个价格&#xff1a;开盘价、收盘价、最高价和最低价&#xff0c;通常简称为 OHLC。K 线按照周期一般可以分为日、周、月、年&#xff0c;以及五分钟…

Selenium自动化落地实践

01、自动化测试流程图 02、主要过程描述 1、自动化测试的切入点 开展自动化测试的时间点很关键&#xff0c;需要在系统已经过多版本的系统测试&#xff0c;达到稳定之后。 2、可行性分析 在进行项目自动化测试之前&#xff0c;第一步就是要确认其可行性&#xff0c;是否可以…

为何唐宋诗词鼎盛,而到了明清变成了小说

我国是一个历史悠久的国家&#xff0c;在漫长的历史长河中&#xff0c;随着朝代的更替&#xff0c;很多事也发生了有趣的变化。 例如唐宋时期盛行的是诗词&#xff0c;而到了明清时代&#xff0c;小说又开始盛行了起来&#xff0c;那么造成这种文风改变的原因是什么呢&#xf…

2023安博会进行时!英码科技1个重磅+4大看点隆重亮相

2023年6月7日&#xff0c;第十六届&#xff08;2023&#xff09;中国国际社会公共安全产品博览会&#xff08;以下简称&#xff1a;安博会&#xff09;在北京首钢会展中心盛大开幕&#xff01;本届安博会以“自主创新数智融合”为主题&#xff0c;共设置了7大主题展馆&#xff…

linux开发:Linux下查看端口占用

前段时间有学生问到&#xff0c;怎么查看已经被占用的端口&#xff1f;下面我就统一给大家解释一下。 提到端口&#xff0c;那首先来回顾端口定义&#xff0c;为了区分一台主机接收到的数据包应该转交给哪个任务来进行处理&#xff0c;使用端口号来区别&#xff1b;我们知道TCP…

【活动预告】ACDU 中国行 · 深圳站精彩抢先看

近期活动 在当今数字化时代&#xff0c;数据库是各行各业中最核心的信息管理系统之一。 随着技术的飞速发展&#xff0c;数据库领域也不断涌现出新的前沿技术和创新应用。 数据库运维和开发人员需要紧跟前沿技术&#xff0c;才能保持竞争力&#xff0c;并实现更高效、更智…

Java-API简析_java.lang.Float类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131129886 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

JMeter测试笔记(五):JDBC请求

引言&#xff1a; 进行性能测试时&#xff0c;我们有时需要模拟数据库的访问和操作&#xff0c;而JMeter中的JDBC请求可以帮助我们实现这个目的。 在本文中&#xff0c;我们将深入了解JDBC请求的使用方式&#xff0c;并学习如何正确配置它们来模拟对数据库的访问和操作。 如…