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

手搓TCP服务器实现基础IO

在有了UDP服务器的基础,搓一个TCP服务器就简单很多了,套接字、绑定,结构体协议家族都是套路,我们主要看二者的区别在代码上的体现。其中TCP是有连接的,也就是说当有客户端访问服务器想发送数据包时,需先和服务器进行连接,然后进行相关操作,而UDP的无连接则不需要,直接向服务器发送数据即可。建议先看一下UDP服务器的代码再看此文会更加清晰。、

套接字与UDP服务器代码https://blog.csdn.net/czt230610/article/details/149817538?fromshare=blogdetail&sharetype=blogdetail&sharerId=149817538&sharerefer=PC&sharesource=czt230610&sharefrom=from_link上面说过 socket的套路不变,但其通信方式我们需要改变一下,在UDP中是SOCK_DGRAM,而TCP是SOCK_STREAM。

除此之外,TCP需要时刻观察是否有客户端访问,就像饭店老板时刻等待顾客一样,因此我们需要给绑定的套接字加上监听功能。

第一个参数就是我们需要监听的套接字文件描述符,第二个参数是排队的容量(即如果有多个客户端访问最多支持排多少个,这里我们定义了宏为8)。

除此之外,我们也要进行“揽客”即告诉客户端我这里可以连接了。

第一个参数是我们负责“揽客”的套接字(最初绑定的),就相当于饭店外面揽客的人,他们并不在里面吃饭,但是会告诉别人让他们来吃饭,因此这个描述符不做IO工作,只是单纯的“揽客”。后两个是输出型参数,代表哪个客户端来访问了。

可是,他返回值也是一个文件描述符。这个和前面的功能一样吗?

这个返回的文件描述符,其实才是真正做IO工作的。

图中的sockfd才是我们真正做IO的,我们类内定义的是_listensockfd。

接下来就是IO工作,我们单独写一个函数来处理任务,由于TCP是面向字节流的,IO更像文件,因此我们用文件的读写接口。

void HandlerRequest(int sockfd){while(true){char inbuffer[4096];ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);if(n>0){inbuffer[n]=0;std::string echo_str="server echo";echo_str+=inbuffer;::write(sockfd,echo_str.c_str(),echo_str.size());}else if (n=0)//客户端退出{std::cout<<"client exit"<<std::endl;break;  }else{break;}}::close(sockfd);}

这个函数能够基础的进行IO但是只支持一个客户端连接,先来看客户端的编写。

在进行套接字的创建后,我们遇到了新的问题,如何建立连接?因为TCP是有连接的,需要连接才能进行read write操作。因此我们介绍一个新接口。

即把创建的套接字与目标服务器的ip+port连接。(客户端不需要显示的bind)

剩下的IO工作和UDP就类似了,只是接口换一下。

以上即可满足单个客户端进行不断重连且能进行IO的服务器操作了。

完整代码:

服务器hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
static const uint16_t gport=8080;#define BACKLOG 8 //排队容量
class TcpServer
{
public:TcpServer(int port=gport):_port(port),_isrunning(false){}void InitServer(){_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){std::cout << "socket falied" << std::endl;exit(1);}std::cout<<"socket success"<<std::endl;struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;int n=::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){std::cout<<"bind error"<<std::endl;}std::cout<<"bind success"<<std::endl;//tcp随时等待被连接//需要将tcp的socket设置为监听状态n=::listen(_listensockfd,BACKLOG);if(n<0){std::cout<<"listen failed"<<std::endl;}std::cout<<"listen success"<<std::endl;}void HandlerRequest(int sockfd){while(true){char inbuffer[4096];ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);if(n>0){inbuffer[n]=0;std::string echo_str="server echo";echo_str+=inbuffer;::write(sockfd,echo_str.c_str(),echo_str.size());}else if (n=0)//客户端退出{std::cout<<"client exit"<<std::endl;break;  }else{break;}}::close(sockfd);}void Start(){_isrunning =true;while(_isrunning){//获取新连接struct sockaddr_in peer;socklen_t peerlen;//获取客户端信息int sockfd=::accept(_listensockfd,(struct sockaddr*)&peer,&peerlen);if(sockfd<0){//揽客失败std::cout<<"accept failed"<<std::endl;continue;}//获取成功std::cout<<"success : "<<sockfd<<std::endl;//EchoHandlerRequest(sockfd);}}void Stop(){_isrunning=false;}~TcpServer(){}private:int _listensockfd;uint16_t _port;bool _isrunning;
};

服务端.cc

#include "TcpServer.hpp"
#include <memory>int main()
{std::unique_ptr<TcpServer> tsvr=std::make_unique<TcpServer>();tsvr->InitServer();tsvr->Start();return 0;
}

客户端.cc

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>// ./client_tcp ip port
int main(int argc, char* argv[])
{if(argc!= 3){std::cout << "Usage:./client_tcp ip port" << std::endl;return 1;}std::string server_ip = argv[1];int server_port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){std::cout << "Error in creating socket" << std::endl;return 2;}//服务器的socket已传入struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port);server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());int n=::connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));if(n < 0){std::cout << "Error in connecting to server" << std::endl;return 3;}std::string message;while(true){std::cout << "Enter message to send: ";std::getline(std::cin, message);int n=::write(sockfd, message.c_str(), message.size());char buffer[1024];if(n>0){int m=::read(sockfd, buffer, sizeof(buffer));if(m>0){buffer[m]=0;std::cout << "Received message: " << buffer << std::endl;}}}::close(sockfd);return 0;
}

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

相关文章:

  • Go语言高并发价格监控系统设计
  • TCP 协议的“无消息边界”(No Message Boundaries)特性
  • sqli-labs-master/Less-31~Less-40
  • 内联函数:提升效率的空间换时间艺术
  • 移动端 WebView 视频无法播放怎么办 媒体控件错误排查与修复指南
  • 官宣!多功能DC-DC数字电源控制器重磅首发
  • 应用药品GSP证书识别技术,提升药品流通各环节的合规管理效率和风控水平
  • 数据工程与处理:AI时代的数据基石与智能化管道
  • java~final关键字
  • doris `unicode` 是多语言混合类型分词与elasticsearch分词差异
  • Java从入门到精通 - 算法、正则、异常
  • MQTT:安装部署
  • 【AI 加持下的 Python 编程实战 2_13】第九章:繁琐任务的自动化(中)——自动批量合并 PDF 文档
  • CMake进阶: 使用FetchContent方法基于gTest的C++单元测试
  • Docker-07.Docker基础-数据卷挂载
  • 在CAPL自动化脚本中巧用panel函数
  • 关键领域软件研发如何构建智能知识管理体系?从文档自动化到安全协同的全面升级
  • 实现Trie(前缀和)C++
  • 【REACT18.x】封装react-rouer实现多级路由嵌套,封装登录态权限拦截
  • PyTorch :三角函数与特殊运算
  • python:讲懂决策树,为理解随机森林算法做准备,以示例带学习,通俗易懂,容易理解和掌握
  • 张 事实关注增强模型:提升AI准确率新方法
  • 设备电机状态监测中的故障诊断与定位策略
  • 【AI论文】VL-Cogito:面向高级多模态推理的渐进式课程强化学习
  • Redis之Hash和List类型常用命令
  • [特殊字符] Ubuntu 下 MySQL 离线部署教学(含手动步骤与一键脚本)
  • 小鹏汽车前端面经
  • 笔记本电脑联想T14重启后无法识别外置红米屏幕
  • 【银河麒麟服务器系统】自定义ISO镜像更新内核版本
  • Axure日期日历高保真动态交互原型