Linux网络编程——tcp套接字

文章目录

    • 主要代码
    • 关于构造
    • listen监听
    • accept
    • telnet测试
    • 读取信息
    • 掉线重连
    • 翻译服务器演示

本章Gitee仓库:tcp套接字

主要代码

客户端:

#pragma once#include"Log.hpp"#include<iostream>
#include<cstring>#include<sys/wait.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>#include"threadPool.hpp"
#include"Task.hpp"const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5;  //不要设置太大
Log log;enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LITSEN_ERR
};
class TcpServer;class ThreadData
{
public:ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t):t_sockfd_(fd), t_clientip_(ip), t_clientport_(port), t_tsvr_(t){}
public:int t_sockfd_;std::string t_clientip_;uint16_t t_clientport_;TcpServer *t_tsvr_; //需要this指针
};class TcpServer
{public:TcpServer(const uint16_t &port, const std::string &ip = defaultip):listensockfd_(defaultfd),port_(port),ip_(ip){}//初始化服务器void Init(){//创建套接字listensockfd_ = socket(AF_INET, SOCK_STREAM, 0); //sock_stream提供字节流服务--tcpif(listensockfd_ < 0){log(Fatal, "create socket, errno: %d, errstring: %s",errno, strerror(errno));exit(SOCKET_ERR);}log(Info, "create socket success, sockfd: %d",listensockfd_);int opt = 1;setsockopt(listensockfd_, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));  //防止偶发性服务器无法进行立即重启//本地套接字信息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));//bindint bd = bind(listensockfd_, (struct sockaddr*)&local, sizeof(local));if(bd < 0){log(Fatal, "bind error, errno: %d, errstring: %s",errno, strerror(errno));exit(BIND_ERR);}log(Info, "bind success");//tcp面向连接, 通信之前要建立连接//监听if(listen(listensockfd_, backlog) < 0){log(Fatal, "listen error, errno: %d, errstring: %s",errno, strerror(errno));exit(LITSEN_ERR);}log(Info, "listen success");}static void *Routine(void *args){pthread_detach(pthread_self());//子线程无需关闭文件描述符ThreadData *td = static_cast<ThreadData*>(args);td->t_tsvr_->Service(td->t_sockfd_, td->t_clientip_, td->t_clientport_);delete td;}void Start(){signal(SIGPIPE, SIG_IGN);threadPool<Task>::GetInstance()->Start();//signal(SIGCHLD, SIG_IGN);  //直接忽略进程等待 V2log(Info, "server is running...");while(true){//获取新链接struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(listensockfd_, (struct sockaddr*)&client, &len);if(sockfd < 0){log(Warning, "accpet error, errno: %d, errstring: %s",errno, strerror(errno));continue;}uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));log(Info, "get a new link..., sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip, clientport);//根据新链接进行通信//V1--单进程//Service(sockfd, clientip, clientport);//close(sockfd);//V2--多进程// pid_t id = fork();// if(id == 0)// {//     //child//     close(listensockfd_);  //子进程可以看到父进程的文件描述符表,关闭不需要的 //     if(fork() > 0)  exit(0);//父进程创建的子进程直接退出//     Service(sockfd, clientip, clientport);  //孙子进程执行, 由于孙子的父进程退出, 由系统领养//     close(sockfd);//     exit(0);// }// //father// close(sockfd);  //存在引用计数,不会这个关闭这个文件// pid_t tid = waitpid(id, nullptr, 0);// (void)tid;//V3--多线程(创建进程成本较高,换线程)// ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);// pthread_t tid;// pthread_create(&tid, nullptr, Routine, td);//pthread_join(tid, nullptr);   //已进行线程分离,无需等待(并发执行)//V4--线程池Task t(sockfd, clientip, clientport);threadPool<Task>::GetInstance()->Push(t);//sleep(1); }}void Service(int sockfd, const std::string &clientip, const uint16_t &clientport){char buffer[4096];while(true){ssize_t n = read(sockfd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_str = "tcpserver echo# ";echo_str += buffer;write(sockfd, echo_str.c_str(), echo_str.size());}else if(n == 0){log(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{log(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);break;}memset(buffer, 0, sizeof(buffer));}}~TcpServer(){}
private:int listensockfd_;uint16_t port_;std::string ip_;
};

客户端:

#include<iostream>
#include<cstring>
#include<unistd.h>#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>void Usage(const std::string &proc)
{std::cout << "\n\tUsage: " << proc << "serverip serverport" << std::endl;
}int main(int argc, char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 填充信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));//连接模块while (true){int sockfd = 0;int cnt = 5;bool isreconnect = false;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 1;}do{// tcp客户端也无需显示bind// 向目标发起连接(connect的时候进行自动随机bind)int cn = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (cn < 0){isreconnect = true;cnt--;  //重连5秒std::cerr << "connet error, reconnect: " << cnt << std::endl;close(sockfd);sleep(1);   //每隔一秒重连一次}else{break;}}while(cnt && isreconnect);if(cnt == 0){std::cerr << "server offline..." << std::endl;break;}//服务模块//while (true){std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);if (message.empty())continue;int wn = write(sockfd, message.c_str(), message.size());if (wn < 0){std::cerr << "write error" << std::endl;//break;}char inbuffer[4096];int rn = read(sockfd, inbuffer, sizeof(inbuffer));if (rn > 0){inbuffer[rn] = 0;std::cout << inbuffer << std::endl;}else{//break;}}close(sockfd);}return 0;
}

关于构造

关于构造和初始化,可以直接在构造的时候,将服务器初始化,那为什么还要写到init初始化函数里面呢?

构造尽量简单一点,不要做一些“有风险”的操作。

listen监听

tcp是面向连接的,通信之前要建立连接,服务器处于等待连接到来的状态:

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);

image-20240205161303727

accept

获取新链接:

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

后两个参数输出型参数,获取对方的信息

这里返回也是一个套接字,这和最开始创建的listensockfd_有什么区别呢?

这就好比去吃饭,门口有一个拉客的,问“帅哥,来不来我们这里吃饭啊”,如果你去了,那就是“欢迎光临,几位呀”,你回答“我和我女朋友”,然后这个人大喊“两位客人,来一个服务员”,这时候就来了一个服务员招呼你坐下,然后给你菜单,点完菜之后给你上菜;在这个期间站在门口的拉客的人,一直在招揽客人,有人来就喊服务员招招待,人家不来就继续拉。

listensockdf_就是这个拉客的,真正给我们提供服务的是accept返回的

telnet测试

telnet可以对指定服务的远程连接

image-20240205192444936

127.0.0.1本地环回,再加上端口号就可以测试了

ctrl + ],然后回车

读取信息

tcp是面向字节流的,管道是面向字节流的、读取普通文件也是面向字节流的,所以可以采用read进行读取。

掉线重连

当读端关闭之后,系统会杀掉写端,采用signal(SIGPIPE, SIG_IGN)忽略这个信号

翻译服务器演示

GIF 2024-2-11 20-17-30

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

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

相关文章

【java】11:IDEA常用快捷键+包

1. IDEA 常用快捷键 删除当前行, 默认是 ctrl Y 自己配置 ctrl d复制当前行, 自己配置 ctrl alt 向下光标补全代码 alt /添加注释和取消注释 ctrl / 【第一次是添加注释&#xff0c;第二次是取消注释】导入该行需要的类 先配置 auto import , 然后使用 altenter 即可快速…

AlmaLinux右键菜单(基于GNOME桌面)

文章目录 前言前提说明在文件上右键在文件夹上右键 前言 在使用VSCode的过程中&#xff0c;AlmaLinux没能像Windows一样在右键菜单上显示打开方式&#xff0c;所以找了一下解决方案&#xff0c;罗列出来 前提说明 虽然说无论是media还是StackOverflow都推荐使用这条命令&…

PLC在物联网中位置—承上启下,与上位机下位机的关联。

谈到物联网&#xff0c;就绕不开PLC&#xff0c;本文着重介绍PLC的定义、与单片机的区分&#xff0c;价值、物联网中的位置&#xff0c;以及和上位机、下位机的关联&#xff0c;让友友们对PLC有个全面的认知。 一、什么是PLC PLC是可编程逻辑控制器&#xff08;Programmable L…

「递归算法」:反转链表

一、题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a…

springboot集成elasticsearch

一、依赖下载 创建好一个springboot项目&#xff0c;需要集成es&#xff1a; 因为springboot默认集成了es&#xff0c;但是版本号需要与本地或者服务器es的版本号一致&#xff0c;我本地es版本是7.14.0&#xff0c;所以需要在<properties></properties>中指定es版…

12.状态模式

文章目录 状态模式总结 状态模式 介绍 状态模式它允许一个对象在其内部状态改变时改变其行为&#xff0c;使对象看起来似乎修改了其类。状态模式的主要目的是将对象的状态封装成不同的类&#xff0c;并将对象的行为委托给当前状态。 组成 Context&#xff08;环境&#xff09;&…

开启Android学习之旅-1

最近在学习《第一行代码 Android》&#xff0c;两天看书把所有代码都敲了一遍。由于之前没有接触过 Kotlin&#xff0c;导致了囫囵吞枣&#xff0c;跟着书会敲&#xff0c;离开就忘了。Android 大佬开发的各种框架、控件好像大部分都用了 Kotlin。看他们的源码&#xff0c;理解…

UnityShader——01Shader和渲染管线

Shader和渲染管线 什么是Shader Shader中文翻译为着色器&#xff0c;是一种较为短小的程序片段&#xff0c;用于告诉图形硬件如何计算和输出图像&#xff0c;过去由汇编语言编写&#xff0c;现在也可以使用高级语言进行编写。 即&#xff0c;Shader是一种可编程图形管线的算法…

第五篇:MySQL常见数据类型

MySQL中的数据类型有很多&#xff0c;主要分为三类:数值类型、字符串类型、日期时间类型 三个表格都在此网盘中&#xff0c;需要者可移步自取&#xff0c;如果觉得有帮助希望点个赞~ MySQL常见数据类型表 数值类型 &#xff08;注&#xff1a;decimal类型举例&#xff0c;如1…

从左值和右值的角度分析a++和++a

摘自牛客上的一个题目&#xff1a; int a5,则 (a)的值是? 答案是会编译报错。 原因&#xff1a; a返回的是右值(rvalue)&#xff0c;而我们不能对一个右值进行自增操作。所以(a)会报错。 怎么理解呢&#xff1f; &#xff08;a)返回的是a在1之前的值&#xff0c;这个值是一个…

system V——进程间通信

上一篇博客中我介绍了system V进程间通信中的内存共享&#xff0c;但是其中还有两 种通信方式&#xff1a;消息队列、和信号量&#xff0c;接下来我将简单介绍一下&#xff0c;消息队列和 信号量以及操作系统是如何看待system V进程间通信的。1. 消息队列 a. 大致介绍 消息队…

Python:解析数组二分查找算法bisect

简介&#xff1a;bisect模块提供对维护一个已排序列表而无须在每次插入后对该列表重排序的支持。对于具有大量条目需要大量比较运算的长列表&#xff0c;这改进了原来的线性搜索或频繁重排序。之所以被命名为 bisect 是因为它使用了基本的二分算法来完成任务。 不同于其他搜索特…