二十、socket套接字编程(二)——TCP

news2024/11/25 20:33:50

文章目录

  • 一、TCP套接字接口
    • (一)inet_aton (和inet_addr一样,换一种方式而已)
    • (二)socket()
    • (三)bind()
    • (四)listen()
    • (五)accept()
    • (六)connet()
  • 二、TCP套接字编程
    • (一)注意事项
      • 1.因为TCP是流式类型套接字,所以利用read和write进行读取和写入数据。
      • 2.客户端注意
      • 3.工作流程
        • (1)TCP服务器的工作
        • (2)TCP客户端的工作
    • (二)原始版本
    • (三)封装为多进程,多线程
      • 1.多进程版本1
      • 2.多进程版2
      • 3.多线程版
      • 4.线程池
  • 三、完整代码
    • (一)tcpServer.hpp
    • (二)tcpServer.cpp
    • (三)tcpClient.hpp
    • (四)tcpClient.cpp
    • (五)task.hpp

查看TCP网络服务器情况和端口使用情况

 netstat -nltp

在这里插入图片描述

一、TCP套接字接口

(一)inet_aton (和inet_addr一样,换一种方式而已)

int inet_aton(const char *cp, struct in_addr *inp);(address to net 本地字符串风格IP转网络4字节IP)cp:字符串风格IP地址。inp:转换后的存到inp中。

返回值:成功返回1;失败返回0;

注意:
(1)这类函数在转变IP风格时都会自动进行主机字节序和网络字节序之间的转换。

(2)ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));

如果手动传./serverTcpd 8080 0,IP地址传的是0,此时inet_aton(0, &local.sin_addr) 即inet_aton第一个参数传的就是0,把值为0的本地字符串风格IP转网络4字节IP存入local.sin_addr中;因为INADDR_ANY 这个宏的值就是0,0是字符串风格还是网络风格无所谓,所以就等价于:inet_aton(INADDR_ANY, &local.sin_addr));,也就等价于local.sin_addr.s_addr = INADDR_ANY;

总结:inet_aton(ip_.c_str(), &local.sin_addr) ip_为0时,inet_aton(0, &local.sin_addr) 等于 local.sin_addr.s_addr = INADDR_ANY。

(二)socket()

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;如果socket()调用出错则返回-1;应用程序可以像读写文件一样用read/write在网络上收发数据;
domain:对于IPv4, family参数指定为AF_INET;
type:对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol:protocol参数的介绍从略,指定为0即可

(三)bind()

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;
服务器需要调用bind绑定一个固定的网络地址和端口号;

  • bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听,myaddr所描述的地址和端口号;bind()成功返回0,失败返回-1。前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

我们的程序中对myaddr参数是这样初始化的:

在这里插入图片描述

  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT, 我们定义为8080;

(四)listen()

NAME
       listen - listen for connections on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog
个客户端处于连接等待状态,如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5);
listen()成功返回0,失败返回-1。

(五)accept()

NAME
       accept, accept4 - accept a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sys/socket.h>

       int accept4(int sockfd, struct sockaddr *addr,
                   socklen_t *addrlen, int flags);

三次握手完成后,服务器调用accept()接受连接;如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;

addr是一个传出参数,accept()返回时传出客户端的地址和端口号;如果给addr 参数传NULL,表示不关心客户端的地址;
addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的,
缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

(六)connet()

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

客户端需要调用connect()连接服务器;
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1

二、TCP套接字编程

(一)注意事项

1.因为TCP是流式类型套接字,所以利用read和write进行读取和写入数据。

在这里插入图片描述

2.客户端注意

①客户端操作系统会自动bind,但是不需要自己显式的bind。
②需要listen吗?不需要的!监听本身就是等待别的客户端去连接你,所以客户端本身不需要设置监听状态,因为没人去连你的客户端。
③需要accept吗?不需要的,都无法设置监听,就更不需要获取连接。

3.工作流程

(1)TCP服务器的工作

  • 创建socket
  • 填充服务器信息struct sockaddr_in
  • 将套接字和sockaddr_in 绑定bind
  • socket设置为监听状态
  • accept获取链接并获取客户端IP和port
  • 提供服务,读取内容后完成转换写回

(2)TCP客户端的工作

  • 创建socket
  • 填充服务器信息struct sockaddr_in,connect向服务器发起链接请求
  • 写入数据后读出服务器转化的数据

(二)原始版本

只能给一个客户提供服务,当为一个客户提供服务进入transService后,transService是死循环,除非提供完毕,否则函数不返回,则主执行流无法继续为其他客户提供服务!

void start() {
	logMessage(NORMAL,"Thread init success\n");
	for (;;) {
		struct sockaddr_in peer;
		socklen_t len = sizeof(peer);
		int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
		if (sock < 0) {
			  logMessage(ERROR, "accept error, next");
              continue;
		}
	    serviceIO(sock);
        close(sock); //对一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致,文件描述符泄漏
	}
}

(三)封装为多进程,多线程

我们设置了对应的任务是死循环,那么线程池提供服务,就显得有不太合适。我们给线程池抛入的任务都是短任务。

因为他并没有访问任何类内成员,所以可以把执行方法提到类外,执行任务时就1.可以不用把服务和类绑定了bind(&ServerTcp::transService, this)。2.threadRoutine也没用了

我们封装服务为多进程,多线程版本的目的:多个客户端访问时,让第一个客户端访服务器时,服务器上通过子进程为客户端提供服务,然后父进程就可以继续while循环,进行下一次阻塞式获取下一个客户端的链接并为他提供服务,是并发是进行的。

小提示:为什么不用waitpid()waitpid(); 默认是阻塞等待!我们本身就是追求多进程并发,阻塞相当于还是串行了,所以我们不能用waitpid()。那WNOHANG可以吗?
——答:可以是可以,但是很麻烦,需要把各个子进程的pid保存进一个vector中,每次非阻塞等待需要轮询检测子进程pid看子进程是否退出,很麻烦,我们不选择这种方法。

1.多进程版本1

利用signal(SIGCHLD, SIG_IGN); 父进程调用signal/sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

注意:①父进程打开的文件会被子进程继承,所以子进程中本身用不到“接客”的listenSock_,所以建议关掉此文件描述符。close(listenSock_); //建议(类似管道关闭不需要的读写端一样)

②父进程accept创建的提供服务的文件描述符serviceSock就是让子进程继承使用的,那么子进程已经继承serviceSock后,父进程就用不到了,就需要关闭父进程对应的serviceSock。close(serviceSock); //这一步是一定要做的!(类似管道关闭不需要的读写端一样)。

void start() {
   
	logMessage(NORMAL,"Thread init success\n");
	signal(SIGCHLD, SIG_IGN);
	for (;;) {
		struct sockaddr_in peer;
		socklen_t len = sizeof(peer);
		int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
		if (sock < 0) {
			  logMessage(ERROR, "accept error, next");
              continue;
		}
	   pit_t id = fork();
	   if (id == 0) {
			close(_listensock);
			serverIO(sock);
			close(sock);
			exit(0);
		}
        close(sock); //对一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致,文件描述符泄漏
        // father
        pit_t res = waitpid(id,nullptr,0);  //  阻塞等待子进程退出
        if (ret > 0) {
           std::cout << "waitsuccess: " << ret << std::endl;
		}
	}
}

2.多进程版2

3.多线程版

利用多线程去服务客户,首先创造一个ThreadData类,方便函数方法调用transService传参。

 static void *threadRoutine(void *args) {
        pthread_detach(pthread_self()); //设置线程分离
        ThreadData *td = static_cast<ThreadData*>(args);
        td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
        delete td;
        return nullptr;
}
void start() {
   
	logMessage(NORMAL,"Thread init success\n");
	signal(SIGCHLD, SIG_IGN);
	for (;;) {
		struct sockaddr_in peer;
		socklen_t len = sizeof(peer);
		int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
		if (sock < 0) {
			  logMessage(ERROR, "accept error, next");
              continue;
		}
	   pthread_t tid;
       ThreadData* td = new ThreadData(this, sock);
       // 这里不需要进行关闭文件描述符吗??不需要啦
       // 多线程是会共享文件描述符表的!
       pthread_create(&tid,nullptr, threadRoutine, td);
       pthread_join(tid, nullptr);
	}
}

4.线程池

void start() {
   
	logMessage(NORMAL,"Thread init success\n");
	signal(SIGCHLD, SIG_IGN);
	for (;;) {
		struct sockaddr_in peer;
		socklen_t len = sizeof(peer);
		int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
		if (sock < 0) {
			  logMessage(ERROR, "accept error, next");
              continue;
		}
	ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
	}
}

三、完整代码

(一)tcpServer.hpp

(二)tcpServer.cpp

(三)tcpClient.hpp

(四)tcpClient.cpp

(五)task.hpp

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

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

相关文章

ASP.NET Core MVC -- 入门

先决条件&#xff08;开发配置二选一&#xff09;&#xff1a; 带有 ASP.NET 和 Web 开发工作负载的Visual Studio Visual Studio Code Visual Studio Code用于 Visual Studio Code 的 C#&#xff08;最新版本&#xff09;.NET 7.0 SDK 创建Web应用 visual studio ctrl F5 …

云原生之深入解析Kubernetes网络流量的流转路径

一、Kubernetes 网络要求 Kubernetes 网络模型定义了一组基本规则&#xff1a; 在不使用网络地址转换 (NAT) 的情况下&#xff0c;集群中的 Pod 能够与任意其他 Pod 进行通信&#xff1b; 在不使用网络地址转换 (NAT) 的情况下&#xff0c;在集群节点上运行的程序能与同一节点…

王道《计算机网络》思维导图汇总

第一章 1.1.1 概念与功能 1.1.2 组成与分类 1.1.3 标准化工作及相关组织 1.1.4 性能指标 速率 带宽 吞吐量 时延 时延带宽积 往返时延RTT 利用率 1.2.1 分层结构、协议、接口、服务 1.2.2 OSI参考模型 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 1.2.4 TCP/IP 参…

内核角度看IO模型

聊聊Netty那些事儿之从内核角度看IO模型 网络包接收流程 当网络数据帧通过网络传输到达网卡时&#xff0c;网卡会将网络数据帧通过DMA的方式放到环形缓冲区RingBuffer中。RingBuffer是网卡在启动的时候分配和初始化的环形缓冲队列。当RingBuffer满的时候&#xff0c;新来的数据…

【AUTOSAR】BMS开发实际项目讲解(十三)----电池管理系统碰撞安全功能和SFR

SG-BMS-7 : BMS系统应避免碰撞保护功能异常引起的安全事故&#xff08;ASIL A&#xff09; 功能框图&#xff08;SG-BMS-7&#xff09; 功能组件说明 功能组件ID 功能组件名称 描述 ASIL等级 FSC-FC-05 Relay Drive 驱动继电器开启和关断 ASIL A FSC-FC-11 Detection …

【vue】可选链运算符(?.)和空值合并运算符(??):

文章目录 一、问题一:二、问题二:三、使用:【1】空值合并运算符&#xff08;??&#xff09;【2】可选链运算符&#xff08;?.&#xff09; 一、问题一: http://www.codebaoku.com/question/question-sd-1010000042870944.html //1、npm安装 npm install babel/plugin-propo…

批量修改文件命名的shell脚本

Android 制作开机动画的方法参考&#xff1a;linux开机动画制作教程 其中往往会把里面的png图片命名位XX_0001.png , 002.png……等 Window批量修改文件名时会带有空格和括号。 这里写了一个脚本&#xff0c;可以在批量修改文件名后&#xff0c;将文件名转换为XX_00001 格式&…

基于matlab使用 YOLO V2深度学习进行多类对象检测(附源码)

一、前言 此示例演示如何训练多类对象检测器。 深度学习是一种强大的机器学习技术&#xff0c;可用于训练强大的多类对象检测器&#xff0c;例如 YOLO v2、YOLO v4、SSD 和 Faster R-CNN。此示例使用该函数训练 YOLO v2 多类室内对象检测器。经过训练的物体检测器能够检测和识…

ModaHub魔搭社区:Milvus 监控指标和使用 Grafana 展示 Milvus 监控指标

目录 Milvus 监控指标 Milvus 性能指标 系统运行指标 硬件存储指标 Milvus 监控指标 Milvus 会生成关于系统运行状态的详细时序 metrics。你可以通过 Prometheus、Grafana 或任何可视化工具展现以下指标&#xff1a; Milvus 性能指标系统运行指标&#xff1a;CPU/GPU 使用…

单片机学习 11-中断系统(定时器中断+外部中断)

中断系统 中断介绍 ​ 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的&#xff0c;中断功能的存在&#xff0c;很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一&#xff0c;是我们学习单片机必须要掌握的。很多初学者被困在…

全网超全,Pytest自动化测试框架pytest-xdist分布式测试插件(实战)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 平常我们功能测试…

Markdown 扩展语法

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

Python在命令行模式下如何退出命令行

文章目录 python退出命令行模式结语 刚学习python的时候是用的命令行的方式&#xff0c;刚接触不知道如何退出命令行&#xff0c;百度参考了好几篇文章&#xff0c;这里记录一下&#xff0c;希望能帮助到有需要的小伙伴们。 python退出命令行模式 总结一下&#xff0c;共有三种…

Redis处理⾼并发 实现分布式锁

Redisson Redisson是架设在Redis基础上的⼀个Java驻内存数据⽹格&#xff08;In-Memory Data Grid&#xff09;。 Redisson在基于NIO的Netty框架上&#xff0c;充分的利⽤了Redis键值数据库提供的⼀系列优势&#xff0c;在Java实⽤⼯具包中常⽤ 接⼝的基础上&#xff0c;为使⽤…

高压功率放大器在光学测量中的应用有哪些

高压功率放大器在光学测量中有许多应用&#xff0c;例如在激光器和LED驱动、光电探测器和光电转换器中等。这些应用大多需要将输入信号放大到高电平输出&#xff0c;以便驱动高电压或大功率负载。 在激光器和LED驱动应用中&#xff0c;高压功率放大器可以将低电平的控制信号放大…

nginx纳入skywalking调用链监控

nginx纳入skywalking调用链监控 一、说明二、nginx部署2.1 OpenResty介绍2.2 准备SkyWalking Nginx Agent2.3 docker方式部署OpenResty2.3.1 修改配置文件2.3.2 启动OpenResty容器 2.4 验证 一、说明 服务器中已部署好skywalking&#xff0c;并将tomcat纳入skywalking监控(tom…

JavaSE基础语法--接口

接口在现实生活中比比皆是。比如电脑的USB接口&#xff0c;插座的接口。这些接口我们发现都是一样的规范。比如插座的有双孔插&#xff0c;有三孔插。那么对应就有双脚设备&#xff0c;和三脚的设备。从这我们就能摸清楚规律&#xff1a;接口就是统一规范的提供服务。Java中接口…

七年老Android推荐 : 日常开发中好用的工具 (二)

1. 前言 作为一名拥有七年经验的Android开发工程师&#xff0c;在日常开发中&#xff0c;总希望能提升自己的开发效率&#xff0c;对此也积累了一些工具&#xff0c;本文对此总结了一些好用的工具。 2. draw.io draw.io用来编写流程图非常好用&#xff0c;是一个免费的在线图…

Splunk Enterprise 9.1.0 (macOS, Linux, Windows) - 机器数据管理和分析

Splunk Enterprise 9.1.0 (macOS, Linux, Windows) - 机器数据管理和分析 请访问原文链接&#xff1a;https://sysin.org/blog/splunk-9/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 混合世界的数据平台 快速、大规模地从…

【海思SS528】MPP媒体处理软件V5.0 | 音频模块 - 学习笔记

目录 &#x1f384;一、概述&#x1f384;二、音频输入(AI) 和 音频输出(AO)✨2.1 音频接口和 AI、 AO 设备✨2.2 录音和播放原理✨2.3 AI、AO 通道✨2.4 重采样 &#x1f384;三、音频编码和解码✨3.1 音频编解码流程✨3.2 音频编解码协议✨3.3 语音帧结构 &#x1f384;四、总…