TCP网络套接字

引言

前面我们已经介绍了udp套接字的相关编写,本文主要介绍TCP套接字的相关接口和一些相关知识,方便大家后续对TCP的进一步理解。

相关接口

1、socket

这个接口我们已经在前面有所了解,这里我们再重新回顾一下
在这里插入图片描述
第一个参数我们依然使用AF_INET,表示使用网络通信,第二个参数我们需要使用SOCK_STREM,这里和UDP套接字稍微有点区别,需要注意下。最后一个参数我们依旧填零即可,OS会自动识别创建套接字所使用的协议。

2、bind

这个接口我们在前面也已描述,这里简单回顾一下。
在这里插入图片描述
在TCP套接字的编写中,绑定套接字依旧是不可或缺的一步,所以我们只需要像写UDP套接字一样,绑定特定的sockfd即可。
注意:在服务端的sockaddr_in 结构中,是不需要绑定特定的IP

3、listen

在了解这个接口之前,我们需要了解一个知识

TCP是面向连接的,所以在通信之前,就必须先建立连接,而服务器是被连接的,一旦tcpserver 启动,首先要做的就是一直等待客户的到来。

所以我们要让绑定的套接字进入监听状态,检测客户端发起地连接,而listen接口就是将套接字设置成监听状态。
在这里插入图片描述
在这里插入图片描述
下面介绍一下参数:第一个参数sockfd,就是需要设置成监听状态的文件描述符;第二个参数表示全连接队列长度,这涉及OS内核的相关知识,这里后面介绍。该参数不要设置地太大,也不要太小,设成十几即可。

4、accept

在TCP通信时,我们没法直接获取数据,需要先获取连接后,才能获得数据。而accep接口就是用于获取连接地。
在这里插入图片描述
其中,第一个参数就是我们之前监听的sockfd。第二个和第三个参数相当于recvfrom的最后两个参数,属于输出型参数,用于获取客户端的相关数据。

这里需要着重介绍一下accept的返回值,如果accept获取连接成功,那么就会新创建一个文件描述符,该文件描述符会被返回。如果创建失败,则返回-1,错误码被设置。每一个客户端来连接服务端时,一旦服务端成功accept到连接,就会创建一个新的文件描述符。


下面介绍一下sockfd和这些新创建的fd之间的关系。

张三
A
B
C
D
E

在日常的生活中,我们可能要经常坐火车、飞机到各个地方,在飞机站和火车站对面,我们出站后,常常会看见饭店门口有一些服务员在拉客。当你刚好想要吃饭时,拉客的服务员就会将你带进店内,此时带你进店的服务员会重新出去拉客,让别的服务员招待你。这里拉客的服务员就像sockfd,他只提供拉客服务,具体的服务事项让店内其他服务员负责。这里拉客的服务员就是sockfd,只负责监听和获取连接的服务。而其他的fd就负责具体的业务处理。

5、connect

该接口用于客户端向服务端发起连接,连接成功后,我们这里的客户端会在底层自动绑定一个端口。
在这里插入图片描述
第一个参数为socket接口创建的sockfd,第二和第三个参数是我们要连接的服务端参数。
如果连接成功返回“0”,失败返回“-1”。

示例代码

1、TCPserver.hpp

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include <unistd.h>
#include <functional>
#include "InetAddr.hpp"
class TcpServer;
class ThreadDate
{
public:~ThreadDate(){}ThreadDate(int _sockfd, struct sockaddr_in _add, TcpServer *_self) : sockfd(_sockfd), addr(_add), self(_self){}public:TcpServer *self;int sockfd;struct sockaddr_in addr;
};
using task_t = std::function<void *(void *)>;
using fun = std::function<std::string(std::string)>;
class TcpServer
{const static int defaultfd = -1;const int backlog = 16;public:TcpServer(int port, fun _function) : _port(port), fun_t(_function), _listensockfd(defaultfd), _isrunning(false){}void Init_Server(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "socket create failed")}struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_port = htons(_port);server.sin_addr.s_addr = htonl(INADDR_ANY);server.sin_family = AF_INET;int n = bind(_listensockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){LOG(FATAL, "bind failed")exit(1);}LOG(INFO, "bind success")n = ::listen(_listensockfd, backlog);if (n < 0){LOG(FATAL, "listen failed")}}void Server(int sockfd, struct sockaddr_in &peer){Inetaddr addr(peer);std::string clientaddr = "[" + addr.Ip() + " : " + std::to_string(addr.Port()) + "]";while (true){char inbuffer[1024];ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0){inbuffer[n] = 0;std::cout << clientaddr << inbuffer << std::endl;std::string echo_string = "[server echo]# ";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0){// client 退出&&关闭连接了LOG(INFO, "%s quit\n", clientaddr.c_str());break;}else{LOG(ERROR, "read error\n", clientaddr.c_str());break;}sleep(5);break;}// while (true)// {//     if(CheckClose(sockfd,peer))//     {//         LOG(INFO,"client close")//         ::close(sockfd);//         return;//     }//     ssize_t count = 1024;//     std::string result;//     while (true)//     {//         char buffer[1024];//         memset(buffer, 0, sizeof(buffer));//         count = read(sockfd, buffer, sizeof(buffer) - 1);//         if (count > 0)//         {//             // std::cout << client_infor << buffer << std::endl;//             // fflush(stdout);//             result += buffer;//         }//         else if (count == 0)//         {//             // LOG(INFO, "client close...")//             //::close(sockfd);//             break;//         }//         else//         {//             LOG(ERROR, "read fail")//         }//     }//     std::string Re = fun_t(result);//     write(sockfd, Re.c_str(), count);// }}static void *Hand_task(void *arg){ThreadDate *thread = static_cast<ThreadDate *>(arg);thread->self->Server(thread->sockfd, thread->addr);return nullptr;}void Loop(){_isrunning = true;while (true){struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){LOG(WARNING, "accept fail")//     LOG(INFO, "accept success")//     pid_t id = fork();//     if (id == 0)//     {//         ::close(_listensockfd);//         pid_t rid = fork();//         if (rid == 0)//         {//             Server(sockfd, peer);//         }//         exit(0);//     }//     waitpid(id, nullptr, 0);//     ::close(sockfd);// }}else{pthread_t thread;ThreadDate *th = new ThreadDate(sockfd, peer, this);pthread_create(&thread, nullptr, Hand_task, th);}}_isrunning = false;}~TcpServer(){::close(_listensockfd);}private:int _listensockfd;int _port;bool _isrunning;//表示是否运行fun fun_t;
};

2、TCPclient.cc

#include <iostream>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <netinet/in.h>
#include <cstring>
#include "Log.hpp"
void Usage()
{std::cout << "./client ip port" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage();return 1;}struct sockaddr_in client;bzero(&client, sizeof(client));client.sin_addr.s_addr = inet_addr(argv[1]);client.sin_port = htons(std::stoi(argv[2]));client.sin_family = AF_INET;socklen_t len = sizeof(client);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){LOG(FATAL, "socket fail")return 1;}int rcount = connect(sockfd, (struct sockaddr *)&client, len);if (rcount != 0){LOG(FATAL, "connect fail....")return 1;}LOG(INFO, "connect success")while (true){std::string message;printf("please enter: ");std::getline(std::cin, message);ssize_t n = ::send(sockfd, message.c_str(), message.size(), 0);if (n > 0){printf("[server] : ");char buffer[1024];memset(buffer,0, sizeof(buffer));ssize_t rnum = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (rnum < 0){LOG(ERROR, "recv fail")continue;}if (rnum == 0){break;}std::cout << buffer;}else{LOG(ERROR, "send fail")break;}std::cout << std::endl;}::close(sockfd);return 0;
}

3、TCPserver.cc

#include "TcpServer.hpp"
#include <memory>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./tcpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);return 1;}EnableScreen();uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

相关文件

InetAddr.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}bool operator == (const InetAddr &addr){// if(_ip == addr._ip)if(_ip == addr._ip && _port == addr._port) // 方便测试{return true;}return false;}struct sockaddr_in Addr(){return _addr;}uint16_t Port(){return _port;}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

Log.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <pthread.h>
#include <fstream>
enum Level
{INFO = 0,DEBUG,WARNING,ERROR,FATAL};
std::string Level_tostring(int level)
{switch (level){case INFO:return "INFO";case DEBUG:return "DEBUG";case WARNING:return "ERROR";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "Unkown";}
}pthread_mutex_t _glock = PTHREAD_MUTEX_INITIALIZER;
bool _is_save = false;
const std::string filename = "log.txt";void SaveLog(const std::string context)
{std::ofstream infile;infile.open(filename,std::ios::app);if(!infile.is_open()){std::cout << "open file failed" << std::endl;}else{infile << context;}infile.close();
}
std::string Gettime()
{time_t cur_time = time(NULL);struct tm *time_data = localtime(&cur_time);if (time_data == nullptr){return "None";}char buffer[1024];snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d",time_data->tm_year + 1900,time_data->tm_mon + 1,time_data->tm_mday,time_data->tm_hour,time_data->tm_min,time_data->tm_sec);return buffer;
}
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = Level_tostring(level);std::string time = Gettime();// 可变参数char buffer[1024];va_list args;va_start(args, format);vsnprintf(buffer, sizeof(buffer), format, args);va_end(args);std::string context = "[" + levelstr + "]" + "[" + time + "]" + "[" + "line : " + std::to_string(line) + "]" + "[" + filename + "]" + ": " + buffer;pthread_mutex_lock(&_glock);if(!issave){std::cout << context << std::endl;}else{SaveLog(context);}pthread_mutex_unlock(&_glock);
}#define LOG(level, format, ...)                                          \do                                                                   \{                                                                    \LogMessage(__FILE__, __LINE__, _is_save, level, format, ##__VA_ARGS__); \} while (0);
#define EnableFile()    \do                  \{                   \_is_save = true; \} while (0);
#define EnableScreen()   \do                   \{                    \_is_save = false;\} while (0);

相关代码问题

1、如何处理多个请求

在TcpServer中,我是用了多线程处理请求,如果我们使用单线程处理请求,很容易导致一个请求一直在循环等待,其他请求根本得不到处理的情况出现。所以这里我们需要将请求的处理函数设置成多进程或者是多线程的,由于这里转发消息属于一种短时服务,所以我们可以优先使用多线程,当然也可以使用多进程。

多线程代码比较简单,这里不详细介绍,而多进程想要对请求进行灵活地处理,就必须对文件描述符进行合理地管理。这里我们可以创建多个进程,让每个进程管理一个fd。其中父进程我们可以让其管理listensockfd,然后把其他请求生成的fd关闭,这样可以避免误操作,然后让孙子进程对请求生成地fd进行管理。父进程必须要对子进程进行wait,但是这样就会造成一个问题,每次循环结束后,子进程直接被回收了,请求生成的fd直接被关闭了。为了解决这个问题,我们可以将孙子进程管理请求生成的fd,由于子进程被wait,孙子进程会直接被系统领养,就会一直处理请求。

示例:TCPserver中loop接口中的实现方式

                    pid_t id = fork();if (id == 0){::close(_listensockfd);pid_t rid = fork();if (rid == 0){Server(sockfd, peer);}exit(0);}waitpid(id, nullptr, 0);::close(sockfd);}

当然,这里我们也可以使用线程池或者是进程池的方式,提前申请创建一批线程或者进程。

2、上述接收数据部分存在的问题

TCP协议是面向连接的,这种协议在通信时较udp会比较安全,但是也存在一些缺点。例如,在tcp协议中,数据并不是向udp协议通信一样,一整个直接发过来,所以上述的代码是存在一定错误的。Tcp协议并不能保证一次性接收的信息就是整条信息,所以需要我们人工对其进行识别。

3、Tcp支持全双工的原因

在前面文件的部分种,我们了解到,其实文件通过系统调用向磁盘中写入是一个拷贝的过程,就是找出数据根据对应进程的文件描述符表中对应文件描述符,再由系统调用将数据拷贝到内核缓冲区中,由内核决定何时将缓冲区中的内容刷入对应的磁盘中。根据Linux系统一切皆文件的理念,我们可以得知当我们将数据交由系统调用后,数据被拷贝到发送缓冲区中,发送时机由内核(TCP协议)决定*(怎么发、错了怎么办都和上层没有关系)*,整个过程其实就是将数据从一台主句的发送缓冲区发送到另一台主机的接收缓冲区,相当是两个OS进行通信,对于接收方也是同理。

数据经过协议栈在不同主机进行相互拷贝,所以应用层的用户并不用细节,只需要将数据交由下层协议,让下层协议将数据拷贝到不同主机之间。

而我们的缓冲区中没有数据时,使用read、recv等接口就会阻塞,等待数据的发送过来。从另外一个角度看,其实缓冲区就像是我们前面学习的生产者消费者模型,只不过这里生产者变成了用户,消费者变成了OS。

当缓冲区被写满时,对应的系统调用接口会被阻塞,其本质就是用户层在进行同步,确保数据能够准确地发送给对端。
在这里插入图片描述

总结一下:其实从上面我们就可以看出,通信的本质就是拷贝。

通过上图我们看到,每个主机都两个缓冲区,这两个缓冲区互不干扰,这就是tcp支持全双工的原因,也是一个文件fd即支持写又支持读的原因。

以上就是所有内容,感谢阅读!!!

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

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

相关文章

rustdesk 自建服务

RustDesk 部署RustDesk sudo docker image pull rustdesk/rustdesk-server sudo docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v pwd:/root -td --nethost rustdesk/rustdesk-server hbbs sudo docker run --name hbbr -p 2111…

【一篇搞定配置】网络分析工具WireShark的安装与入门使用

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;各种软件安装与配置_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1.…

RK3568平台开发系列讲解(DMA篇)什么是DMA

🚀返回专栏总目录 文章目录 一、什么是DMA二、DMA的产生:背景三、理解 DMA:协处理器沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将带领大家深刻理解DMA。 一、什么是DMA DMA (Direct Memory Access) is used to copy data directly between devices and R…

AD20使用操作第三部分

常见CHIP封装的创建 SOD-123 第一个&#xff0c;顶视图&#xff0c;第二个&#xff0c;侧视图&#xff0c;第三个&#xff0c;侧视图。 E表示&#xff1a;丝印的本体 D表示&#xff1a;宽度 b&#xff1a;焊盘的宽度 A&#xff1a;高度 L:管脚长度 PCB焊盘&#xff1a;焊接器…

Ansible--自动化运维工具

Ansible自动化运维工具介绍 1.Ansible介绍 Ansible是一款自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。…

软件工程相关-用PD画类图-设置方法的参数

前提&#xff1a;pd16安装完成&#xff0c;已经画好了类 1、为类添加方法。 2、双击方法&#xff0c;如下图所示 3、此时会有弹窗&#xff0c;选择参数栏&#xff0c;按需求进行设置。 &#xff08;总是忘记&#xff0c;故记录下自用

Qt配置Opencv环境

下载opencv后配置环境变量(官网下载&#xff09; 然后ok了

MongoDB相关问题

视频教程 【GeekHour】20分钟掌握MongoDB Complete MongoDB Tutorial by Net Ninja MongoDB开机后调用缓慢的原因及解决方法 问题分析&#xff1a; MongoDB开机后调用缓慢&#xff0c;通常是由于以下原因导致&#xff1a; 索引重建&#xff1a; MongoDB在启动时会重建索引…

网络安全在现代企业中的重要作用

网络安全是这个数字时代最令人担忧的事情之一。对技术的依赖性越来越强&#xff0c;使其同时面临多种网络威胁。其声誉和法律后果的大幅下降可能归因于一次妥协。 这使得良好的网络安全成为所有企业的选择和必需品。本文介绍了网络安全的重要性、企业中常见的网络威胁以及公司…

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…

怎样使用sys.dm_os_wait_stats

文章目录 sys.dm_os_wait_stats 支持诊断 SQL Server 性能问题的基本指标。如果在 SQL Server 引擎中遇到一些问题&#xff08;CPU、内存、I/O、锁、闩锁等&#xff09;&#xff0c;sys.dm_os_wait_stats 数据揭示一些问题。SQL Server Management Studio 中的活动监视器&#…

Hbase2.2.7集群部署

环境说明 准备三台服务器&#xff0c;分别为&#xff1a;bigdata141&#xff08;作为Hbase主节点&#xff09;、bigdata142、bigdata143确保hadoop和zookeeper集群都先启动好我这边的hadoop版本为3.2.0&#xff0c;zookeeper版本为3.5.8 下载安装包 下载链接&#xff1a;In…

自研芯片逾十年,亚马逊云科技Graviton系列芯片全面成熟

在云厂商自研芯片的浪潮中&#xff0c;亚马逊云科技无疑是最早践行这一趋势的先驱。自其迈出自研芯片的第一步起&#xff0c;便如同一颗石子投入平静的湖面&#xff0c;激起了层层涟漪&#xff0c;引领着云服务和云上算力向着更高性能、更低成本的方向演进。 早在2012年&#x…

掌上单片机实验室 — RT - Thread+ROS2 浅尝(26)

前面化解了Micro_ROS通讯问题&#xff0c;并在 RT-Thread Studio 环境下&#xff0c;使用Micro_ROS软件包中的例程&#xff0c;实现了STM32F411CE核心板和ROS2主机的通讯。之后还尝试修改例程 micro_ros_sub_twist.c &#xff0c;实现了接收 turtle_teleop_key 所发出的 turtle…

【Leetcode 每日一题】25. K 个一组翻转链表

25. K 个一组翻转链表 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单…

Android 图形系统之一:概览

Android 图形系统是一套完整的架构&#xff0c;用于管理从应用绘制到显示屏幕的整个流程。它涉及多个层次和组件&#xff0c;从应用程序到硬件&#xff0c;确保每一帧都能准确、高效地呈现到用户的设备屏幕上。 1. Android 图形系统的架构 Android 图形系统的架构可以分为以下…

【C语言】指针与数组的例题详解:深入分析与高级用法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;题目一详细分析与解答代码逐步解析 &#x1f4af;进一步优化和拓展1. 指针与数组的关系2. 指针运算的注意事项3. 常见的错误和陷阱4. 拓展&#xff1a;指针操作的应用场…

Windows修复SSL/TLS协议信息泄露漏洞(CVE-2016-2183)

打开服务器&#xff0c;运行gpedit.msc&#xff0c;打开“本地组策略编辑器”&#xff0c;依次打开计算机配置-管理模板-网络-SSL配置设置。 打开“SSL密码套件顺序”&#xff0c;更改为已启用&#xff0c;并修改套件算法&#xff0c;去掉TLS1.1版本算法。 TLS_ECDHE_ECDSA_WIT…

go-zero(八) 中间件的使用

go-zero 中间件 一、中间件介绍 中间件&#xff08;Middleware&#xff09;是一个在请求和响应处理之间插入的程序或者函数&#xff0c;它可以用来处理、修改或者监控 HTTP 请求和响应的各个方面。 1.中间件的核心概念 请求拦截&#xff1a;中间件能够在请求到达目标处理器之…

vscode ctrl+/注释不了css

方式一.全部禁用插件排查问题. 方式二.打开首选项的json文件,注释掉setting.json,排查是哪一行配置有问题. 我的最终问题:需要将 "*.vue": "vue",改成"*.vue": "html", "files.associations": { // "*.vue": &qu…