I/O复用的高级应用三——同时处理TCP和UDP服务

news2024/9/27 23:29:12

截至目前学习,我们讨论过的服务器程序都只监听一个端口。但在实际应用中,有不少服务器程序能同时监听多个端口,比如超级服务inetd和android的调试服务adbd。 从bind系统调用的参数看,一个socket只能与一个socket地址绑定,即一个socket只能用来监听一个端口。因此,服务器如果要同时监听多个端口,就必须创建多个socket,并将它们分别绑定到各个端口上。这样一来,服务器程序就需要同时管理多个监听socket,I/O复用技术就有了用武之地。另外,即使是同一个端口,如果服务器要同时处理该端口上的TCP和UDP请求,则也需要创建两个不同的socket:一个是流socket,另一个是数据报socket,并将它们都绑定到该端口上。下面代码所示的回射服务器就能同时处理一个端口上的TCP和UDP请求。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define TCP_BUFFER_SIZE 512
#define UDP_BUFFER_SIZE 1024
//非阻塞socket
int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epollfd, int fd )
{
    epoll_event event;
    event.data.fd = fd;//指定事件所从属的目标文件描述符(listenfd或udpfd)
    //event.events = EPOLLIN | EPOLLET;
    event.events = EPOLLIN;//触发事件,可读数据到达
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );//该函数用于操作epoll内核事件表,EPOLL_CTL_ADD代表往注册表上注册epollfd上的事件
    setnonblocking( fd );//非阻塞
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;

    /*创建TCP socket,并将其绑定到端口port上*/
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
    //tcp
    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );//SOCK_STREAM表示传输层使用TCP协议
    assert( listenfd >= 0 );
    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );
    ret = listen( listenfd, 5 );
    assert( ret != -1 );
    
    /*创建UDP socket,并将其绑定到端口port上*/
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
    //udp 整个操作与TCP类似,需要将socket绑定到端口上,但是不需要监听
    int udpfd = socket( PF_INET, SOCK_DGRAM, 0 );//SOCK_DGRAM表示传输层使用UDP协议
    assert( udpfd >= 0 );
    ret = bind( udpfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];//epoll_ctl函数的一个参数
    int epollfd = epoll_create( 5 );//创建事件表
    /*epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,
    从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集*/
    assert( epollfd != -1 );
    /*注册TCP socket和UDP socket上的可读事件*/
    addfd( epollfd, listenfd );
    addfd( epollfd, udpfd );

    while( 1 )
    {   /*epoll系列系统调用的主要接口是epoll_wait函数,
        该函数成功时返回就绪的文件描述符的个数*/
        int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( number < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        for ( int i = 0; i < number; i++ )
        {
            int sockfd = events[i].data.fd;
            if ( sockfd == listenfd )//监听到有TCP连接
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof( client_address );
                int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
                addfd( epollfd, connfd );//将新事件注册到内核表中
            }
            else if ( sockfd == udpfd )//若是udp连接
            {
                //由于udp是无连接协议,所以要一次性接收传输过来的数据
                char buf[ UDP_BUFFER_SIZE ];
                memset( buf, '\0', UDP_BUFFER_SIZE );
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof( client_address );

                //必须使用recvfrom函数接收数据
                //socket文件描述符 接收缓冲区 可接收数据的最大长度 默认方式接收数据 发送方ip地址 地址长度
                ret = recvfrom( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, &client_addrlength );
                //recvfrom函数堵塞到收到来自客户的数据
                if( ret > 0 )
                {
                    //收到数据之后再把数据发给客户端
                    sendto( udpfd, buf, UDP_BUFFER_SIZE-1, 0, ( struct sockaddr* )&client_address, client_addrlength );
                }
            }
            else if ( events[i].events & EPOLLIN )//可读数据到达触发事件
            {
                char buf[ TCP_BUFFER_SIZE ];//TCP连接的接收缓冲区
                while( 1 )
                {
                    memset( buf, '\0', TCP_BUFFER_SIZE );
                    ret = recv( sockfd, buf, TCP_BUFFER_SIZE-1, 0 );
                    if( ret < 0 )
                    {
                        if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                        {
                            break;
                        }
                        close( sockfd );
                        break;
                    }
                    else if( ret == 0 )
                    {
                        close( sockfd );
                    }
                    else
                    {
                        send( sockfd, buf, ret, 0 );
                    }
                }
            }
            else
            {
                printf( "something else happened \n" );
            }
        }
    }
    close( listenfd );
    return 0;
}

服务器端运行该程序 

 一个客户端进行TCP连接

 一个客户端进行udp连接

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

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

相关文章

聚焦 5 大技术领域,腾讯2023 年度犀牛鸟开源人才培养计划启动报名

如今&#xff0c;开源已成为全球科技创新的重要方式&#xff0c;而人才建设则是中国开源生态健康发展的关键。 6 月 28 日&#xff0c;在开放原子校源行清华大学站上&#xff0c;腾讯 2023 年度犀牛鸟开源人才培养计划正式启动&#xff0c;将聚焦AI、大数据、数据库、基础软件…

MATLAB代码:配网节点电价 DLMP 考虑网损,电压,阻塞的配电网二阶锥节点电价 (DLMP)需要gurobi求解器

MATLAB代码&#xff1a;配网节点电价 DLMP 关键词&#xff1a;DLMP SOCP lindistflow 参考文档&#xff1a;《Distribution Locational Marginal Pricing (DLMP) for Congestion Management and Voltage Support》2018 SCI一区 IEEE Transactions on Power System 非完美复现 …

使用 Jetpack Compose 构建 CircularProgressIndicator

欢迎阅读这篇关于如何使用 Jetpack Compose 构建 CircularProgressIndicator&#xff08;圆形进度指示器&#xff09;的博客。Jetpack Compose 是 Google 推出的一款现代化 UI 工具包&#xff0c;用于构建 Android 界面。其声明式的设计使得 UI 开发更加简洁、直观。 一、什么…

Spring 中的 bean 是线程安全的吗?

spring 是一款非常流行的 Java 开源框架&#xff0c;它主要用于构建企业级应用程序。Spring 的 IoC 和AOP技术能够帮助开发人员更加便捷地组织和管理 Java 代码。 在 Spring 中&#xff0c;bean 默认是单例模式&#xff0c;也就是说&#xff0c;每个 bean 只会被 Spring 容器创…

Mac电脑硬件/软件运行状况查看工具

iStat Menus是一款系统监控和管理工具&#xff0c;旨在帮助Mac用户实时监控电脑的各项硬件和软件信息。它以直观和定制化的方式提供了丰富的系统状态指标&#xff0c;让用户能够全面了解和管理自己的Mac电脑。 iStat Menus提供了一系列的菜单栏指示项目&#xff0c;可以显示诸如…

NoSQL之 Redis 配置与优化

目录 一、关系型数据库与非关系型数据库1.1 关系型数据库&#xff1a;1.2 非关系型数据库1.3 关系型数据库和非关系数据库的区别1.3.1 数据存储方式不同1.3.2 扩展方式不同1.3.3 对事务性的支持不同 1.4 非关系型数据库的产生背景1.5 总结 二、Redis介绍三、 Redis 的优点四、 …

深入浅出设计模式 - 建造者模式

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; &#x1f495;&#x1f495; 感兴趣的同学可以收…

fatal: not a git repository (or any of the parent directories): .git 解决方案

fatal: not a git repository (or any of the parent directories): .git致命:不是git存储库(或任何父目录):.git 解决办法&#xff1a; 在命令行 输入 git init 然后回车就好了 git init

JSON-TO-PROTOBUF

url https://json2pb.vercel.app/

PMP认证考试的价值和前景:值得投资吗?

因为我身边很多小伙伴都在纠结考个PMP证书要花小一万&#xff0c;完事到底值不值得投资这个问题&#xff1f;那咱们今的主题就是浅说一下PMP考试的价值和前景叭~ PMP的价值 1.PMP证书能够适用于很多行业&#xff0c;就业范围比较广&#xff0c;比如航天、通讯、电子、计算机、…

【雕爷学编程】Arduino动手做(132)---KY-027魔术光环模块

7款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&…

UDP套接字的通信(实现英汉互译/程序替换/多线程聊天室/Windows与Linux通信)

实现英汉互译 思路 我们在客户端发英文&#xff0c;服务端做翻译工作&#xff0c;让翻译好的中文再次发给我们的客户端&#xff0c;然后打印出来。 服务端代码 翻译的操作 创建一个txt文件里面包含英汉互译的数据 dict.txt banana:香蕉 apple:苹果 pig:猪 beef:牛肉 hello…

Liux上使用POI将Word转PDF中文字体无法显示问题解决

错误信息&#xff1a; Windows测试下载PDF没有任何问题&#xff0c;打包上传Linux服务器以后下载PDF只显示数字。 解决方法&#xff1a; 1、Word转换PDF成功但是中文显示不出都是因为字体的问题 下面的方法简单粗暴&#xff0c;但是解决了我的问题&#xff1a; a、将C:\Wind…

面试官:“你还有什么想问我的吗?”(攻略版,建议收藏系列)

面试官&#xff1a;“你还有什么想问我的吗&#xff1f;”&#xff08;攻略版&#xff0c;建议收藏系列&#xff09; 此类问题大致分为三种情形&#xff1a;部门负责人面、HR面、高层领导面。 一、部门负责人面 【判断自己的业务能力和性格特点是否与岗位匹配度高】 此时&am…

ADB usage

查看手机设备的信息 获取设备的Android版本号 adb shell getprop ro.build.version.release 获取设备的API版本号 adb shell getprop ro.build.version.sdkAdb 获得 sdk版本 adb shell getprop ro.build.version.sdk27 Adb 获得Android版本 adb shell getprop ro.build.vers…

深入了解glibc的互斥锁的加锁过程

深入了解glibc的互斥锁 互斥锁是多线程同步时常用的手段&#xff0c;使用互斥锁可以保护对共享资源的操作。共享资源也被称为临界区&#xff0c;当一个线程对一个临界区加锁后&#xff0c;其他线程就不能进入该临界区&#xff0c;直到持有临界区锁的线程释放该锁。 本文以gli…

品达通用权限系统-Day03

文章目录 1. 概述2. lombok&#xff08;编码效率工具&#xff09;2.1 lombok 简介2.2 安装lombok插件2.3 lombok常用注解2.4 lombok入门案例 3. Nacos&#xff08;服务注册和配置中心&#xff09;4. Redis&#xff08;Windows版安装及使用&#xff09; 1. 概述 本节主要讲述&a…

同城上门送酒小程序 uniapp用户端+vue/php后端+商家端+配送端源码

前端uniapp 跨平台框架 后端php vue.js框架 php7.2 mysql数据库 mysql5.6 <template> <view> <view id"mainPage" :style"{height:MainPageHeightrpx}"> <PageHome v-show"showPageinitIndex" …

序列到序列学习

将最后时刻的隐藏状态传给解码器。特定的“”表示序列开始词元&#xff0c;表示开始翻译。将此次翻译的结果作为下一次的输入&#xff0c;并将隐藏状态传递给下一时刻。最后可以拿到整个语言句子的输出。 将编码器最后一次的隐藏状态与解码器的第一次的输入&#xff0c;放在一…

工具篇9--Window 虚拟机安装

文章目录 前言一、虚拟机是什么&#xff1f;二、虚拟机安装1.下载虚拟机软件&#xff1a;2.下载centos 系统镜像&#xff1a;3.虚拟机安装&#xff1a;3.1 关闭杀毒软件&#xff1a;3.2 重启后继续安装&#xff1a;3.3 修改vm 安装的位置&#xff1a;3.4 勾掉用户体验后下一步完…