Linux——网络通信TCP通信常用的接口和tco服务demo

2024-02-15 01:52

本文主要是介绍Linux——网络通信TCP通信常用的接口和tco服务demo,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • TCP通信所需要的套接字
    • socket()
    • bind()
    • listen()
    • accept
    • connect()
  • 封装TCP socket

TCP通信所需要的套接字

socket()

socket()函数主要作用是返回一个描述符,他的作用就是打开一个网络通讯端口,返回的这个描述符其实就可以理解为一个文件描述符,tcp在通讯的时候是会开辟一个缓存空间的,我们发送和读取消息可以理解为在这个缓存空间中进行的。因此这里我们可以知道我们可以直接用write和read函数进行消息的写入和读取,如果socket()调用出错则返回-1;
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol参数的介绍从略,指定为0即可。
代码如下以ipv4协议为例

int socketfd=socket(AF_INET,SOCK_STREAM,0)

bind()

bind()函数的作用是为了让socket返回的描述符与端口号进行绑定在bind函数之前我们还需要做一个工作就是将我们的信息存入一个结构体sockaddr_in中,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
在这里插入图片描述
我们的程序中对myaddr参数是这样初始化的:

sockaddr_in local;
bzero(&local, sizeof(local));//将整个结构体清0
local.sin_port = htons(port_);//porty_是我们的端口号这里是将这个端口号转化为网络序列一般我们的端口号都设置为8000以上
local.sin_family = AF_INET;//设置地址类型为AF_INET
inet_aton(ip_.c_str(), &(local.sin_addr));//网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;

listen()

在这里插入图片描述
liasten函数生命socketfd正处于监听状态并且最多可以允许backlog个客户端处于连接等待状态如果收到更多连接请求就会忽略
listen成功返回0 失败则是-1

accept

在这里插入图片描述
accept函数是为了接受来自客户端的连接请求的函数它是在三次握手之后发挥作用,这里也会有一些问题比如说accept发挥作用的时候此时并没有客户发送连接请求该怎么办呢?这里会陷入阻塞等待的状态也就是一直等到有连接请求为止。

connect()

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

封装TCP socket

tcp_socket.hpp

#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) if (!(exp)) {\return false;\
}
class TcpSocket {
public:TcpSocket() : fd_(-1) { }TcpSocket(int fd) : fd_(fd) { }bool Socket() {fd_ = socket(AF_INET, SOCK_STREAM, 0);if (fd_ < 0) {perror("socket");return false;}printf("open fd = %d\n", fd_);return true;}bool Close() const {close(fd_);printf("close fd = %d\n", fd_);return true;}bool Bind(const std::string& ip, uint16_t port) const {sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_port = htons(port);int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("bind");return false;}return true;}bool Listen(int num) const {int ret = listen(fd_, num);if (ret < 0) {perror("listen");return false;}return true;}bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const {sockaddr_in peer_addr;socklen_t len = sizeof(peer_addr);int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);if (new_sock < 0) {perror("accept");return false;}printf("accept fd = %d\n", new_sock);peer->fd_ = new_sock;if (ip != NULL) {*ip = inet_ntoa(peer_addr.sin_addr);}if (port != NULL) {*port = ntohs(peer_addr.sin_port);}return true;}bool Recv(std::string* buf) const {buf->clear();char tmp[1024 * 10] = {0};// [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完// 参考 man 手册 MSG_WAITALL 节. ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);if (read_size < 0) {perror("recv");return false;}if (read_size == 0) {return false;}buf->assign(tmp, read_size);return true;}bool Send(const std::string& buf) const {ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);if (write_size < 0) {perror("send");return false;}return true;}bool Connect(const std::string& ip, uint16_t port) const {sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_port = htons(port);int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("connect");return false;}return true;}int GetFd() const {return fd_;}private:int fd_;
};

tcp_server.hpp

#pragma once
#include <functional>
#include "tcp_socket.hpp"
typedef std::function<void (const std::string& req, std::string* resp)> Handler;
class TcpServer {
public:TcpServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {}bool Start(Handler handler) {// 1. 创建 socket;CHECK_RET(listen_sock_.Socket());// 2. 绑定端口号CHECK_RET(listen_sock_.Bind(ip_, port_));// 3. 进行监听CHECK_RET(listen_sock_.Listen(5));// 4. 进入事件循环for (;;) {// 5. 进行 acceptTcpSocket new_sock;std::string ip;uint16_t port = 0;if (!listen_sock_.Accept(&new_sock, &ip, &port)) {continue;}printf("[client %s:%d] connect!\n", ip.c_str(), port);// 6. 进行循环读写for (;;) {std::string req;// 7. 读取请求. 读取失败则结束循环bool ret = new_sock.Recv(&req);if (!ret) {printf("[client %s:%d] disconnect!\n", ip.c_str(), port);// [注意!] 需要关闭 socketnew_sock.Close();break;}// 8. 计算响应std::string resp;handler(req, &resp);// 9. 写回响应new_sock.Send(resp);
printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port,req.c_str(), resp.c_str());}}return true;}
private:TcpSocket listen_sock_;std::string ip_;uint64_t port_;
};
我们开开心心的在一起

这篇关于Linux——网络通信TCP通信常用的接口和tco服务demo的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/710165

相关文章

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

springboot项目中常用的工具类和api详解

《springboot项目中常用的工具类和api详解》在SpringBoot项目中,开发者通常会依赖一些工具类和API来简化开发、提高效率,以下是一些常用的工具类及其典型应用场景,涵盖Spring原生... 目录1. Spring Framework 自带工具类(1) StringUtils(2) Coll

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子