Linux编程——多路复用实现TCP双向通信

2023-05-16

ubuntu下模拟服务器与单个客户端之间的双向通信,多路复用实现。

功能与使用

服务器与客户端可以双向收发消息,如果任意一方被外部强制断开,另一方也会退出程序。任意一方输入“quit”并发送,客户端与服务器都会退出。

服务器需要先启动,并且通过主函数传参,输入自己的ip和端口号。客户端也需要通过主函数传参把自己的ip和端口号以及要连接的服务器的ip和端口号传入。

运行平台

ubuntu的版本

效果演示

其实就是在ubuntu上打开两个终端运行程序看效果,哈哈。
编译通过
ubuntu运行的效果

需要注意和思考的地方

  • ubuntu是采用小端存储数据,而计算机网络中采用的是大端存储,因此我们需要将ip和端口号进行转换,是不是很麻烦?其实在Linux中,已经封装好相关的转换函数,直接用即可,很方便,本文中用到的有htons()和inet_addr()函数,其它的不多赘述。
  • server.c中的accept()函数需要着重留意它的第二个参数和返回值,观看源码后,会进行详细解释。
  • 多路复用中的FD_SET()函数该放在哪?循环内还是循环外?

源代码

  1. 头文件:myhead.h
#ifndef _MYHEAD_H
#define _MYHEAD_H

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/msg.h>
#include <sys/sem.h>
#include <pthread.h>
#include <semaphore.h>
#include <dirent.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif
  1. 客户端:client.c
#include "myhead.h"
/*
  多路复用实现tcp双向通信(模拟客户端)
*/

int main(int argc,char **argv)
{
  int tcpsock;        //套接字文件描述符
  int ret;      //返回值
  char sbuf[100];    //存放发送的消息的数组
  char rbuf[100];    //存放接收的消息的数组
  //定义一个文件描述符集合变量并初始化清空
  fd_set myset;
  FD_ZERO(&myset);
  
  //定义客户端的ipv4地址结构体变量
  struct sockaddr_in bindaddr;
  bzero(&bindaddr,sizeof(bindaddr));
  bindaddr.sin_family = AF_INET;
  bindaddr.sin_port = htons(atoi(argv[2]));  //自己指定一个端口号
  bindaddr.sin_addr.s_addr = inet_addr(argv[1]); //指定自己的ip
  
  //定义服务器的ipv4地址结构体变量
  struct sockaddr_in serveraddr;
  bzero(&serveraddr,sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  serveraddr.sin_port = htons(atoi(argv[4]));  //服务器端口号
  serveraddr.sin_addr.s_addr = inet_addr(argv[3]); //服务器的ip
  
  //创建套接字
  tcpsock = socket(AF_INET,SOCK_STREAM,0);
  if(tcpsock == -1)
  {
    perror("无法创建套接字!");
    return -1;
  }
  
  //绑定ip和端口号
  ret = bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
  if(ret == -1)
  {
    perror("绑定失败");
    return -1;
  }
  
  //连接服务器
  ret = connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
  if(ret == -1)
  {
    perror("连接服务器失败");
    return -1;
  }
  
  /* 
  FD_SET(tcpsock,&myset);
  FD_SET(0,&myset); 
  */
  
  //死循环接收发消息
  while(1)
  {
    //将要监测的(套接字)文件描述符添加进集合变量
    FD_SET(tcpsock,&myset);
    //将要监测的(键盘)文件描述符添加进集合变量
    FD_SET(0,&myset);
    //调用select函数去监测文件描述符集合变量
    ret = select(tcpsock+1,&myset,NULL,NULL,NULL);
    if(ret == -1)
    {
      perror("监测失败!");
      return -1;
    }
    else if(ret == 0)
    {
      perror("监测超时!");
      return -1;
    }
    else 
    {
      //判断键盘在不在集合中,在则说明键盘处于读就绪
      if(FD_ISSET(0,&myset))
      {
        bzero(sbuf,100);
        scanf("%s",sbuf);
        send(tcpsock,sbuf,strlen(sbuf),0);
        if(strncmp(sbuf,"quit",4) == 0)
          exit(0);
      }
      //判断套接字描述符在不在集合中,在则说明套接字处于读就绪
      if(FD_ISSET(tcpsock,&myset))
      {
        bzero(rbuf,100);
        ret = recv(tcpsock,rbuf,100,0);
        if(strncmp(rbuf,"quit",4) == 0)
          exit(0);
        if(ret == 0)  //表示对方断开连接了
        {
          printf("服务器已断开!即将退出程序!\n");
          exit(0);
        }
        printf("服务器发送过来的信息:%s\n",rbuf);
      }
    }
  }
}
  1. 服务器:server.c
#include "myhead.h"
/*
  多线程实现tcp双向通信(模拟服务器)
*/

int main(int argc,char **argv)
{
  int tcpsock;     //套接字文件描述符(此套接字用于监听和接受连接时)
  int newsock;     //新的套接字文件描述符(用于与客户端通信)
  int ret;    //返回值
  char sbuf[100];  //存放发送的消息的数组
  char rbuf[100];  //存放接收的消息的数组
  //定义一个文件描述符集合变量并初始化清空
  fd_set myset;
  FD_ZERO(&myset);
  
  //定义服务器的ipv4地址结构体变量
  struct sockaddr_in bindaddr;
  bzero(&bindaddr,sizeof(bindaddr));
  bindaddr.sin_family=AF_INET;
  bindaddr.sin_port=htons(atoi(argv[2]));  //服务器自己的端口号
  bindaddr.sin_addr.s_addr=inet_addr(argv[1]); //服务器自己的ip
  
  /*
  定义客户端的ipv4地址结构体变量
  无需程序员赋值,
  accept函数自动存放连接的客户端的ip和端口号
  */
  struct sockaddr_in clientaddr;
  bzero(&clientaddr,sizeof(clientaddr));
  int addrsize=sizeof(clientaddr);
  
  //创建套接字
  tcpsock=socket(AF_INET,SOCK_STREAM,0);
  if(tcpsock==-1)
  {
    perror("创建套接字失败!");
    return -1;
  }
  
  //绑定ip和端口号
  ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
  if(ret==-1)
  {
    perror("绑定失败");
    return -1;
  }
  
  //监听
  ret=listen(tcpsock,5);
  if(ret==-1)
  {
    perror("监听失败");
    return -1;
  }
  //接受客户端的连接请求
  newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
  if(newsock==-1)
  {
    perror("无法接受客户端的连接请求");
    return -1;
  }
  
  //打印出连接上服务器的客户端的ip和端口号
  printf("ip为%s,端口号为%d的客户端成功连接服务器!\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
  
  /* 
  FD_SET(newsock,&myset);
  FD_SET(0,&myset); */
  
  //死循环接收发消息
  while(1)
  {
    //将要监测的套接字文件描述符添加进集合变量
    FD_SET(newsock,&myset);
    //将要监测的键盘文件描述符添加进集合变量
    FD_SET(0,&myset);
    //调用select函数去监测集合变量
    ret = select(newsock+1,&myset,NULL,NULL,NULL);
    if(ret == -1)
    {
      perror("监测失败!");
      return -1;
    }
    else if(ret == 0)
    {
      perror("监测超时!");
      return -1;
    }
    else
    {
      //判断新的套接字文件描述符在不在集合中,在则说明套接字处于读就绪
      if(FD_ISSET(newsock,&myset))
      {
        bzero(rbuf,100);
        ret=recv(newsock,rbuf,100,0); //一定不能用旧的套接字
        if(strncmp(rbuf,"quit",4) == 0)
          exit(0);
        if(ret==0)  //表示对方断开连接了
        {
          printf("客户端已断开!即将退出!\n");
          exit(0);
        }
        printf("客户端发送过来的信息:%s\n",rbuf);
      }
      
      //判断键盘在不在集合中,在则说明键盘处于读就绪
      if(FD_ISSET(0,&myset))
      {
        bzero(sbuf,100);
        scanf("%s",sbuf);
        send(newsock,sbuf,strlen(sbuf),0);
        if(strncmp(sbuf,"quit",4) == 0)
          exit(0);
      }
    }
  }
}

写到最后

  1. 说说这个accept()函数,它的原型是:

    *int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);
    它的第二个参数是一个存放成功连接的客户端的ip和端口号的结构体,注意它是自动存放的(可参考server.c中的第65行代码),不需要程序员自己去写ip和端口号。 最后说一下它的返回值,其是一个int类型的数,失败返回-1,没啥好说的,重点是如果成功呢,实际上如果成功会返回一个文件描述符,该文件描述符是一个新的套接字的文件描述符,一定要区分这个新的套接字与旧的套接字是不一样的,新的套接字是用于与客户端进行通信的,旧的套接字是用在监听和接受连接请求的。
    我在学习accept()函数中的过程中也有点迷糊,后来老师说了一个形象的比喻,可以借以理解:假如一个女孩子(服务器)同时接受了5个朋友的连接请求,那么假如A朋友(客户端A)发过来了一条消息,那么这个女孩子该怎么回复A朋友呢?难道用接受连接的手机(套接字)?那这样大家都能看到消息了,显然是不行的,因为大家既然都能通过这部手机连上你,那么大家就是相通的,这时女孩需要另一部手机来回复A朋友,同理,如果,女孩子需要和另外四个人进行通信,那么需要另外四个新的手机,在这其中累计一共有了6个套接字,一个是socket()函数返回的用于监听和接受连接请求的,另外5个分别是用于与5个不同的朋友通信的。也许不是很恰当的说法,但是大概就是那么一个意思。

  2. 开头注意要点中的最后一点,FD_SET()到底放在哪呢?我们需要知道的是:当select返回后,会移除除当前文件描述符以外其他所有的文件描述符。好了,答案很明显了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux编程——多路复用实现TCP双向通信 的相关文章

  • LEGO-LOAM(LOAM)部分公式推导---未完待续

    一 featureAssociation相关推导 1 xff09 帧间匹配雅可比矩阵推导 首先明确LEGO LOAM中 xff0c 运动坐标系 xff08 符合右手系 xff09 的设置为 xff1a 因此对于平面运动来说 xff0c 影响
  • 基于激光雷达的机器人楼梯自主识别

    机器人如何自主识别楼梯是一个非常有意思的话题 这里基于激光雷达 xff0c 介绍一种简单粗暴的方法 xff0c 如果环境不复杂 xff0c 则可以将楼梯很好的检测出来 github源码 xff0c 较为简洁 xff0c 易于理解 xff1a
  • 双激光雷达标定程序

    链接如下 xff1a GitHub BIT MJY Multiple Lidar Calibration The code for calibration between lidars Chinese Version 此双激光雷达标定程序基
  • GeForce RTX 3070 with CUDA capability sm_86 is not compatible with the current PyTorch.

    此前一直使用tensorflow2 最近入坑pytorch 配置环境后 xff0c 使用时pytorch出现 GeForce RTX 3070 with CUDA capability sm 86 is not compatible wit
  • git push方法

    自己的代码提交到自己的原主分支 克隆下来以后进入文件夹 git add git commit m 34 doing 34 git config global user email 34 xxxx 64 xxxx 34 git config
  • os.system()中路径包含空格导致无法读取文件

    问题 os system 中路径包含空格导致无法读取文件 pcd root 61 34 media mjy My Passport data bkp 1208 02 1208 1 02 34 cmd 61 34 ls 34 43 pcd r
  • Github SSH方式避开私有仓密码输入

    SSH配置流程 xff1a https blog csdn net u013778905 article details 83501204 克隆方式 xff1a git clone git 64 github com xxx xxx git
  • Github常用License总结(MIT/Apache/GPL)

    MIT 比较宽松的许可证是 MIT 许可证 xff0c 只要用户在项目副本中包含了版权声明和许可声明 xff0c 他们就可以拿你的代码做任何想做的事情 xff0c 你也无需承担任何责任 from https www zhihu com qu
  • FAISS C++版本安装教程

    FAISS的Python版本安装起来是很快乐的 xff0c conda就可以轻松安装 但是C 43 43 版本需要花费一点功夫 网上关于C 43 43 的教程除了官方以外少之有少 xff0c 官方教程又过于繁琐 xff0c 特此整理自己的安
  • not a genuine ST device,可能是个“假”芯片

    浅析Keil5报错Connection refused due to device mismatch 的原因 哔哩哔哩 not a genuine ST device 事情就得从这个截图说起 xff0c 看来看去 xff0c 试了试其他的都
  • NCLT数据集

    NCLT数据集的groundtruth文件 xff0c 包含的是velodyne在odom坐标系中的位姿
  • onnxruntime (C++/CUDA) 编译安装及部署

    前几天使用了LibTorch对模型进行C 43 43 转换和测试 xff0c 发现速度比原始Python的Pytorch模型提升了将近2倍 现在尝试以下另一种跨平台的模型转换方式 Onnx xff0c 可实现跨X86 ARM架构的迁移应用
  • 激光雷达闭环检测/地点识别算法OverlapTransformer/SeqOT(2022)

    最新激光雷达闭环检测 地点识别算法 OverlapTransformer已经完整开源 xff0c 相关论文已经被RAL IROS 2022收录 https github com haomo ai OverlapTransformer Ove
  • 我的创作纪念日

    初心未改 xff0c 继续向前
  • 最新激光雷达闭环检测/地点识别算法CVTNet(2023)

    CVTNet以激光点云多类投影生成的二维图为输入 xff0c 利用cross transformer将多类信息交叉融合 xff0c 为激光点云提取强特异性描述子 xff0c 实现SLAM闭环检测或全局定位功能 此外 xff0c CVTNet
  • 判断一条sql是否走了索引

    参考 https zhuanlan zhihu com p 137647823 准备 CREATE TABLE 96 book 96 96 id 96 int 11 NOT NULL AUTO INCREMENT 96 name 96 va
  • C语言网络编程——TCP

    C语言网络编程 TCP 1 1 套接字1 1 1 流套接字 xff08 SOCK STREAM xff09 1 1 2 数据报套接字 xff08 SOCK DGRAM xff09 1 1 3 原始套接字 xff08 SOCK RAW xff
  • ROS入门学习三——launch文件

    launch作用 xff1a 便于一次启动多个节点 xff0c 可启动本地节点和远程节点及修改添加参数服务器参数 lt launch gt lt launch gt 是所有launch文件的根标签 lt node gt node为子集标签
  • ssh连接不上 ping不通 ssh连接虚拟机连不上 远程连接不上可能出现的问题

    如果检查过ssh服务启动 防火墙也关闭了 sshd也安装了 但是怎么ping都ping不通虚拟机网关ip 但是本机可以ping通的话 检查是否虚拟机端 wifi 和 你得xshell 或者finshell 是否在同一个局域网 同一个wifi
  • Shell脚本

    文章目录 第 1 章 shell基础1 1 运维 amp shell 了解 1 1 1 运维基础1 1 2 shell简介 1 2 shell脚本 记忆 1 2 1 创建脚本1 2 2 脚本使用 1 3 变量 应用 1 3 1 什么是变量1

随机推荐

  • Hive基本DDL操作

    1 数据库的DDL操作 xff08 1 xff09 创建数据库 数据库在HDFS上的默认存储路径是 user hive warehouse 数据库名 db hive gt create database db hive 两者等价hive g
  • Windows和Linux双系统时间不对的问题。

    解决Windows与Ubuntu双系统时间同步问题 2016年07月11日 21 05 30 阅读数 xff1a 33115 1 问题发现 本子上装的是Window 10 win7升级 和Ubuntu GNOME 14 04的双系统 一直以
  • stm32移植freertos报错keil

    Using Compiler 39 V5 06 update 7 build 960 39 folder 39 d Keil v5 ARM ARMCC Bin 39 compiling main c USER FreeRTOSConfig
  • ARMv8 (arrch64)pytorch深度学习环境搭建

    Aarch64 安装Anaconda 和 pytorch 一 安装miniconda 参考链接 xff1a 82条消息 Jetson Nano xff08 aarch64 xff09 搭建miniconda 和mmdetection环境 瑾
  • GPU 选择 深度学习 图像识别

    GPU 选择 深度学习 图像识别 1 显卡1 1 Nvidia显卡分类1 1 1 Geforce系列1 1 2 Quadro系列1 1 3 Tesla系列1 2 GPU几个比较重要的参数GPU架构 xff1a CUDA核心数量 xff1a
  • git 合并两个分支

    这里讲解具体的思想 xff0c 具体的提交命令百度上有 目前的场景是将两个非master上的分支进行合并 xff0c 并且不造成冲突 比如现在有两个分支a xff0c b 现在将b合并到a 首先及将b分支上的代码clone下来 xff0c
  • ubuntu中解决ROS--Gazebo添加模型库,解决打开后无模型的问题

    ubuntu中解决Gazebo添加模型库 xff0c 解决打开后无模型的问题 1 在主目录中ctrl 43 h 下载https bitbucket org osrf gazebo models downloads ExBot ROS专区 x
  • 大小端模式

    定义 xff1a 数据存放在地址中 xff0c 一个地址码对应一个字节 内存的低地址存放数据的低位 xff0c 则存储方式为小端存储 xff1b 内存的低地址存放数据的高位 xff0c 则为大端存储 判断大小端的方法有两种 xff1a 数据
  • Matlab二维插值

    Matlab二维插值 interp2函数 xff08 网格节点 xff09 Y 61 interp2 x y z xi yi method method nearest 最邻近插值linear xff08 默认 xff09 双线性插值cub
  • 4、ROS话题通信实战,代码逐行解析

    在前面小节已经实现了ROS工作空间的创建 xff0c 这里开始功能包的开发实战了 首先创建功能包 xff1a cd ROS src 创建功能包topic xff0c 指定依赖roscpp和std msgs包 catkin create pk
  • 大数据学习(三十)JOIN过程中的mapreduce阶段

    前言 xff1a join分为mapjoin 和 common 普通 join mapjoin 是没有reduce阶段 只有map阶段 在map阶段进行join操作 xff08 此知识点也会在大表join小表中体现 xff09 common
  • Gunicorn介绍、安装及使用

    1 简介 Gunicorn Green Unicorn 是一个 UNIX 下的 WSGI HTTP 服务器 xff0c 它是一个 移植自 Ruby 的 Unicorn 项目的 pre fork worker 模型 它既支持 eventlet
  • Spring Boot POM 详解

    正如这幅图所展示的那样 xff0c 在Spring IO Framework体系中 xff0c Spring Boot处在Execution layer xff0c 来看看官方对这层的解释 xff1a The Spring IO Execu
  • 基于阿木实验室P200飞行器simulink模型开发的多旋翼无人机自抗扰控制器(ADRC)参数调整和仿真

    1 飞行器模型参数 参考P200飞行器参数 利用网站https www flyeval com 计算飞行器如下 xff1a 2 参数设置具体如下 1 xff09 模型主要包括总线 期望数据生成模块 控制器 PWM生成器 飞行器对象模型 2
  • mkdir()函数

    一 创建目录 1 1 direct h 头文件 int mkdir const char path mode t mode 函数名 mkdir 功 能 建立一个目录 用 法 int mkdir const char dirname 头文件库
  • react路由基础与传参

    react router dom 1 react的一个插件 xff0c 专门用来实现单页面 2 基于react的项目基本都会用到它 路由的基本使用 1 路由导航区标签为Link或NavLink标签 lt Link to 61 34 xxx
  • ROS TF工具的使用

    1 打印坐标系转换关系 tf echo 命令格式 xff1a rosrun tf tf echo lt source frame gt lt target frame gt 输出数据类型 xff1a geometry msgs Transf
  • RK平台defconfig,Kconfig,Makefile配置

    1 简介 本文是基于RK平台 xff0c defconfig Kconfig Makefile配置总结 在编译内核之前 xff0c 我们可以对内核源代码进行配置 配置的目的主要是确定哪些模块会编译到内核当中 每次添加移植驱动文件时都要修改M
  • EEG信号中常见的干扰和噪声信号

    原文章 转载是为收集整理 xff0c 方便看 xff0c 如不允许 xff0c 联系可删 在EEG ERP研究中 xff0c 最令人头痛的问题之一是各种干扰和噪声信号混入到EEG ERP信号中 xff0c 因此 xff0c 数据分析的第一步
  • Linux编程——多路复用实现TCP双向通信

    ubuntu下模拟服务器与单个客户端之间的双向通信 xff0c 多路复用实现 功能与使用 服务器与客户端可以双向收发消息 xff0c 如果任意一方被外部强制断开 xff0c 另一方也会退出程序 任意一方输入 quit 并发送 xff0c 客