当前位置: 首页 > news >正文

Linux网络:使用TCP实现网络通信(服务端)

文章目录

      • 1. TCP网络程序的服务端(初始化)
        • 1.1 封装一个TcpServer类
        • 1.2 创建socket套接字
        • 1.3 绑定套接字
        • 1.4 监听套接字
      • 2. TCP网络程序的服务端(连接和通信)
        • 2.1 Accept建立连接
        • 2.2 提取客户端信息
        • 2.3 实现通信服务

  • 序:在上以章中,我们详细介绍了UDP网络编程的核心内容,包括服务端和客户端的实现。服务端和客户端重点讲解了如何使用recvfrom接收数据和sendto发送数据,以及如何处理客户端地址信息;此外,还探讨了IP与端口相关知识,如公网IP绑定限制、知名端口范围等。而本篇文章将讨论TCP网络编程,使用TCP来实现网络通信,看看TCP的服务端与客户端与UDP又有怎样的差距和变化。

  • 补充:

在上一章中,我们完成了使用UDP时限网络通信的程序,我们就能用此来建造一个类似于qq群聊的聊天室,由于在Linux中一切皆文件,/dev/pts/目录下存放的就是多个终端的文件,于是我们就可以做到将程序运行的结果在其他终端打印。
在这里插入图片描述
有了这个,我们就可以将所有客户端的信息都打印到同一个终端中。

1. TCP网络程序的服务端(初始化)

1.1 封装一个TcpServer类

要想启动服务端,服务端至少要一个构造,一个析构,一个初始化和一个运行的接口!!!

const int defualtfd = -1;class TcpServer
{
public:TcpServer():_listensock(defualtfd){}void InitServer(){}void Start(){}~TcpServer(){}
private:int _listensock;
};
1.2 创建socket套接字

想要使用TCP套接字,就必须先获取一个套接字,要用到socket函数来获取套接字,与获取UDP套接字不同,在获取TCP套接字的时候,第二个参数要使用字节流的选项,而非用户数据报,在上一章中,我们就说过TCP是面向字节流的,所以第二个选项选择SOCK_STREAM,第三个参数依旧填0。
在这里插入图片描述

class TcpServer
{
public:TcpServer(const uint16_t &port,const std::string &ip=defualtip):_listensockfd(defualtfd),_ip(ip),_port(port){}void InitServer(){_listensockfd = socket(AF_INET,SOCK_STREAM,0);if(_listensockfd < 0){lg(Fatal,"create sockfd error,erron: %d,strerror: %s",errno,strerror(errno));exit(SOCKET_ERR);}lg(Info,"create sockfd success, sockfd: %d",_listensockfd);}
private:int _listensock;uint16_t _port;std::string _ip;
};
1.3 绑定套接字

其中要绑定IP地址,要将点分十进制的字符串转化为in_addr的函数:inet_aton
在这里插入图片描述

要包含的头文件:

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

该函数的第一个参数是传入要转化的字符串。
第二个参数是传入一个uint32_t的四字节地址
在这里插入图片描述

const std::string defualtip ="0.0.0.0";
const uint16_t defualtport =1234;class TcpServer
{
public:TcpServer(const uint16_t &port,const std::string &ip=defualtip):_listensockfd(defualtfd),_ip(ip),_port(port){}void InitServer(){//创建套接字//...struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_aton(_ip.c_str(),&(local.sin_addr));//local.sin_addr.s_addr = INADDR_ANY;if(bind(_listensockfd,(struct sockaddr*)&local,sizeof(local)) < 0){lg(Fatal,"bind error,erron: %d,strerror: %s",errno,strerror(errno));exit(BIND_ERR);}lg(Info,"bind success, sockfd: %d",_listensockfd);}
private:int _listensock;uint16_t _port;std::string _ip;
};
1.4 监听套接字

到了这一步,TCP与UDP的不同就显现出来了,我们知道UDP是无连接的,而TCP是有连接的,也就是说,tcp在通信前,要先建立连接,所以要将自己的套接字变成监听状态,Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直等待连接到来的状态,这样,当有客户端想要来连接是,服务端就能够连接到。

使用listen函数将套接字变成监听状态:
该函数的第一个参数表示要变成监听状态的套接字
第二个参数表示底层全连接的长度
RETURN VALUE返回值:
在这里插入图片描述
成功就返回0,失败就返回-1,并将错误码设置
在这里插入图片描述

class TcpServer
{
public:void InitServer(){//创建,绑定套接字//...//Tcp是面向连接的,服务器一般是比较“被动的”,服务器一直处于一种,一直等待连接到来的状态if(listen(_listensockfd,backlog) < 0){lg(Fatal,"listen error,erron: %d,strerror: %s",errno,strerror(errno));exit(LISTEN_ERR);}lg(Info,"listen success, sockfd: %d",_listensockfd);}
private:int _listensock;uint16_t _port;std::string _ip;
};

2. TCP网络程序的服务端(连接和通信)

2.1 Accept建立连接

因为TCP是面向连接的,所以在实现通信之前要先把连接建立起来,然后再根据连接进行通信,所以我们就要用到建立连接的函数accept
在这里插入图片描述

第一个参数:当前服务器设置为监听状态的套接字
第二和第三个参数:输出型参数,用来获取客户端的IP地址和端口号等信息,就可以知道是谁发的,标识客户端的唯一性
RETURN VALUE返回值:
关键在于accept的返回值,成功则返回一个文件描述符,失败则返回-1,错误码被设置
在这里插入图片描述

其中的listensock只是为了将连接从底层给到accept,真正实行网络通信的是accept后的sockfd!!!就好比一群学生去饭店吃饭,那个给学生们招呼进来的门口的宣传的店员就是listensock,他只负责将顾客拉进店里,其他的不管,拉进店里后再又其他的服务员,也就是sockfd,给他们安排座位,上菜等!!!

2.2 提取客户端信息

之前我们说了,accept能将客户端的信息提取出来,现在我们要将网络序列转化为主机序列了,端口号要从网络序列变成主机序列,IP地址要从4字节整数变成字符串。

其中的难点在于将IP地址从4字节整数变成字符串,在UDP的通信过程中我们是用inet_ntoa函数来实现转化的,但是该函数本身的使用可能会有线程安全的问题,因为man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。但是如果我们调用多次这个函数,为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果。
在这里插入图片描述

所以我们这次使用一个新的函数inet_ntop
该函数的第一个参数:对应的协议家族
第二个参数:对应的要转为字符串的四字节地址
第三个参数:用户自己传入一个缓冲区用来存放该字符串
第四个参数:传入的缓冲区的大小

class TcpServer
{
public:void Start(){//singal(SIGCHLD,SIG_IGN);lg(Info,"TcpServer is running");while(true){//1.获取新连接struct sockaddr_in client;socklen_t len = sizeof(client);//没有接收到就是阻塞的int sockfd = accept(_listensockfd,(struct sockaddr*)&client,&len);if(sockfd < 0){lg(Fatal,"create sockfd error,erron: %d,strerror: %s",errno,strerror(errno));continue; }uint16_t clientport = ntohs(client.sin_port);//std::string clientip = inet_ntoa(client.sin_addr);char clientip[32]; inet_ntop(AF_INET,&(client.sin_addr),clientip,sizeof(clientip));//2.根据新连接来进行通信lg(Info,"get a new link...,sockfd: %d,client ip: %s,client port: %d\n",sockfd,clientip,clientport);Service(sockfd,clientip,clientport);close(sockfd);}}
private:int _listensock;uint16_t _port;std::string _ip;
};

问题一:我们在实现网络通信的过程中,我们将IP地址和端口号都进行的网络序列的转化,但是,我们没有对要发送的内容进行网络序列的转化,为什么IP地址和端口号需要,而发送的内容不需要?

在套接字里面,正常的通信内容,我们所使用的接口,他会帮我们将要发送的数据进行主机序列转网络序列,我们不需要担心,至于为什么我们要手动将IP地址和端口号进行主机序列转网络序列,是因为,IP地址和端口号比较特殊是要给操作系统的,是需要我们手动去转的,所以在实际操作中我们是不需要考虑数据的大小端问题的!!!

2.3 实现通信服务

由于TCP是面向字节流的,所以,读取数据和写入数据直接用read和write就行了

class TcpServer
{
public:void Service(int sockfd,const std::string &clientip,const uint16_t &clientport){while(true){//因为tcp是面向字节流的,所以直接用read就可以直接接收到消息char buffer[4096];ssize_t n =read(sockfd,buffer,sizeof(buffer));if(n > 0){buffer[n]=0;std::cout<<"client say@: "<<buffer<<std::endl;std::string echo_string = "tcpserver say@: ";echo_string += buffer;write(sockfd,echo_string.c_str(),echo_string.size());}else if(n == 0){lg(Info,"[%s:%d] quit,server close sockfd: %d",clientip.c_str(),clientport,sockfd);break;}else{lg(Warning,"read error,sockfd: %d,client ip: %s,client port: %d",sockfd,clientip.c_str(),clientport);break;}}}
private:int _listensock;uint16_t _port;std::string _ip;
};

总结:

本文围绕TCP网络编程核心流程展开,先封装TcpServer类实现服务端初始化(创建socket、绑定地址、监听连接),再讲解accept建立连接、inet_ntop解析客户端信息,最后通过read/write完成面向字节流的通信,完整呈现TCP服务端从搭建到交互的全流程,兼顾代码实操与原理阐释。

http://www.dtcms.com/a/482432.html

相关文章:

  • Python Web开发——WSGI接口
  • 第十章:技术路线:成为“技术扫地僧(1)
  • 苹果软件混淆与 iOS 应用加固实录,从被逆向到 IPA 文件防反编译与无源码混淆解决方案
  • Transformers中从 logits 本质到问答系统中的字符定位机制
  • c++11扩展
  • h1z1注册网站百度app官方下载
  • 阮一峰《TypeScript 教程》学习笔记——基本用法
  • LabVIEW腔衰荡信号在线处理系统
  • 为 AI Agent 行为立“规矩”——字节跳动提出 Jeddak AgentArmor 智能体安全框架
  • Arbess CICD实战(12) - 使用Arbess+GitLab实现React.js项目自动化部署
  • 网站如何做延迟加载店铺图片免费生成
  • 【每日算法C#】爬楼梯问题 LeetCode
  • 网站制作很好 乐云践新二级网站建设情况说明书
  • USDe 脱锚事件全景还原
  • 【运维实践】深入理解 rsync+inotify:实时文件同步技术的原理与实践
  • AI在生产制造过程中的实践分享
  • 建一个优化网站多少钱抖音开放平台官网入口
  • 智能电网变电站综合自动化虚拟仿真实验
  • python自动化中(包括UI自动化和API自动化)env的作用和使用
  • Xcode16 避坑
  • 论文参考文献引用:规避查重率的有效策略
  • 先楫平台使用Jlink调试
  • 偏置电阻简介
  • 【温室气体数据集】历史温室气体与气溶胶排放数据集 CEDS
  • 家具品牌网站怎么做网站建设印花税
  • 建医疗网站步骤seo优化的技巧
  • 【小白笔记】strip的含义
  • 第136期 谷歌Jules Tools反击Copilot的主导地位:重新定义工作流自动化18
  • Apifox AI 测试用例生成:提高测试效率!
  • 【SpringBoot启动异常】解决@profileActive@相关异常问题