Linux--Socket编程TCP

news2024/11/19 10:26:21

前文:Socket套接字编程

TCP的特点

  • 面向连接:TCP 在发送数据之前,必须先建立连接。
  • 可靠性:TCP 提供了数据传输的可靠性。
  • 面向字节流:TCP 是一个面向字节流的协议,这意味着 TCP 将应用程序交下来的数据看成是一连串的无结构的字节流。

TcpServer.hpp

创建一个Tcp服务端
在这里插入图片描述

代码

#pragma once

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include <sys/wait.h>
#include<functional>
#include<pthread.h>
#include"InetAddr.hpp"
#include"Log.hpp"
#include"Threadpool.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};

const static int defaultsockfd = -1;//默认文件描述符
const static int gbacklog = 16;//默认最大连接数

using task_t=std::function<void*()>; //任务函数的类型(V3)
class TcpServer;
//线程类型数据
class ThreadData
{
public:
    ThreadData(int fd,InetAddr addr,TcpServer* s)
    :sockfd(fd),
    clientaddr(addr),
    self(s)
    {}
public:
    int sockfd;//文件描述符
    InetAddr clientaddr;//客户端地址
    TcpServer* self;//服务端
};
class TcpServer
{
public:
    TcpServer(int port)
    :_port(port),
    _listensock(defaultsockfd),
    _isrunning(false)
    {}
    void InitServer()
    {
        //1.创建字节流套接字
        _listensock=socket(AF_INET,SOCK_STREAM,0);
        if(_listensock<0)
        {
            LOG(FATAL, "socket error");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success, sockfd is : %d\n", _listensock);

        //2.bind
        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(_listensock,(struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success, sockfd is : %d\n", _listensock);

        //3.监听客户端,等待被连接
        n=listen(_listensock,gbacklog);
        if(n<0)
        {
            LOG(FATAL, "listen error");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen success, sockfd is : %d\n", _listensock);
    }
    //将接收到的数据进行处理,完成对应服务
    void Service(int sockfd,InetAddr client)
    {
        LOG(DEBUG, "get a new link, info %s %d ,fd: %d \n ",client.Ip().c_str(),client.Port(),sockfd);

        std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.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");
                break;
            }
        }
	close(sockfd);        
    }
    //线程的处理函数
    static void* HandlerSock(void* args)
    {
        pthread_detach(pthread_self());//线程分离
        ThreadData* td=static_cast<ThreadData*>(args);//创建对应线程数据类型
        td->self->Service(td->sockfd,td->clientaddr);
        delete td;
        return nullptr;
    }
    //服务器循环运行着
    void Loop()
    {
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in peer;//sock地址peer
            socklen_t len=sizeof(peer);
            //要先通过连接才能够进行通信
            int sockfd=::accept(_listensock,(struct sockaddr *)&peer,&len);//这里的监听sock和sockfd是不同的
            if(sockfd<0)
            {
                 LOG(WARNING, "accept error\n");
                continue;
            }

            //Version0 这种版本只能接收一次需求,无法多客户端连接
           // Service(sockfd,InetAddr(peer));

            //Version1 多进程
            // pid_t id=fork();
            // if(id>0)
            // {
            //     //子进程负责连接,父进程负责监听,
            //     close(_listensock);
            //     if(fork()>0) exit(0);
            //     //孙进程负责服务,由于子进程连接之后,子进程会进行回收,因此孙进程称为孤儿进程,之后不受子进程的影响
            //     //类似线程分离,是独立的,之后受系统进行回收
            //     Service(sockfd,InetAddr(peer));
            //     exit(0);
            // }
            // //父进程只负责监听,不需要进行连接
            // close(sockfd);
            // waitpid(id,nullptr,0);

            //Version2:采用多线程
            pthread_t t;
            ThreadData* td=new ThreadData(sockfd,peer,this);
            pthread_create(&t,nullptr,HandlerSock,td);//将线程分离

            //Version3:线程池
            //task_t t = std::bind(&TcpServer::Service,this,sockfd,InetAddr(peer));
            //ThreadPool<task_t>::GetInstance()->Enqueue(t);
        }
        _isrunning=false;
    }
    ~TcpServer()
    {
        if(_listensock>defaultsockfd)
        {
            close(_listensock);
        }
    }
    
private:
    uint16_t _port;//端口号
    int _listensock;//监听的文件描述符
    bool _isrunning;//启动
};

解释

在这里插入图片描述
初始化服务端,主要完成套接字的创建绑定,已经完成对应的监听客户端,因为Tcp是有连接的,所以需要监听客户端是否有请求连接的需求;
SOCK_STREAM表示字节流

gbacklog:这个参数定义了内核应该为相应套接字排队的最大连接数。这个值至少为0;其实际值由系统限制,可以通过sysctl命令的net.core.somaxconn参数查看和设置。需要注意的是,这个值并不是指系统能处理的并发连接数,而是内核中等待accept(处理的连接队列的最大长度

在这里插入图片描述
启动服务器之后,通过循环让服务端不断运行着,在循环里面,服务端可能接收到多个客户端请求的连接,所以accpet要在循环中不断接收看是否有对应的连接;

连接完之后通过Service服务函数完成双方的通信
在这里插入图片描述
在Loop中,循环表示接收多个客户端,而Service中,循环表示每个服务端与客户端的通信保持;
由于tcp是面向字节流的,所以可以利用文件描述符的性质,运用read函数,读取对应的发送端数据.
当n大于0时,表示对方有数据发送,处理完信息后反馈给对方;
当n==0,表示sockfd被关闭了,也就是连接被断开了;
当n小于0时,表示出现错误;
如果一直没有发送数据,那么会在read函数这里发生阻塞

在这里插入图片描述
而对于服务的处理,这里有多种方法,如果直接使用Service函数的话,是不可行的,因为这样无法多客户端连接:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过多进程的方法,让父进程只负责监听,子进程负责连接,孙进程负责服务,由于孙进程是孤儿进程,相当于线程分离,这样处理服务时就不会受到父子进程的影响了;也就能完成多客户端的通信了;

在这里插入图片描述
直接通过多线程的方法,将创建的线程进行分离,完成对应的服务任务

main.cc

#include"TcpServer.hpp"
#include<memory>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}
// ./main.cc 8080
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;

}

TcpClient.cc

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<cstring>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}
// ./TcpClient.cc 127.0.0.1 8080
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip=argv[1];//服务端ip
    uint16_t serverport=std::stoi(argv[2]);//服务端端口号

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    server.sin_addr.s_addr=inet_addr(serverip.c_str());
    //连接服务端
    int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(n<0)
    {
        std::cerr << "connect error" << std::endl;
        exit(3);
    }

    while(true)
    {
        std::cout<<"Please Enter# ";
        std::string outstring;
        getline(std::cin,outstring);
        //发送对应数据
        ssize_t s=send(sockfd,outstring.c_str(),outstring.size(),0);
        if(s>0)
        {
            char inbuffer[1024];
            ssize_t m=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);//接收对应数据
            if(m>0)
            {
                inbuffer[m]=0;
                std::cout<<inbuffer<<std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }

       
    }
    close(sockfd);
    return 0;
}

结果

在这里插入图片描述

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

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

相关文章

简单的数据结构:栈

1.栈的基本概念 1.1栈的定义 栈是一种线性表&#xff0c;只能在一端进行数据的插入或删除&#xff0c;可以用数组或链表来实现&#xff0c;这里以数组为例进行说明 栈顶 &#xff1a;数据出入的那一端&#xff0c;通常用Top表示 栈底 :相对于栈顶的另一端&#xff0c;也是固…

【无标题】shell脚本的基本命令+编写shell脚本

shell脚本 一.shell基础 1.shell概念 2.shell脚本 3.shell脚本编写注意事项 二.编写shell脚本 1.编写一个helloworld脚本&#xff0c;运行脚本 [rootshell ~]# vim helloworld.sh #!/bin/bash //声明 echo "hello world!" ls -lh /etc/ 运行脚本(四种方式)&…

react版本判断是否面包含

react-admin: react版本 import { useState,useEffect } from react import ./Secene.css import { Checkbox } from "antd"; import* as turf from turf/turf; import type { CheckboxProps } from antd; // const onChange: CheckboxProps[onChange] (e) >…

bugku-web-cookies

进来以后看到一个巨长的字符串, 源码同样,发现url后面是base64编码解码得keys.txt 还有一个line参数&#xff0c;修改并没有发生任何变化。我想不到要改keys.txt成index.php&#xff08;base64加密格式&#xff1a;aW5kZXgucGhw&#xff09; line1时&#xff1a; line2时&…

Linux系统编程(2):信号

Linux内核提供了各种各样的内核对象用于协调进程间的通讯&#xff0c;如信号、管道、消息队列等&#xff0c; 本章针对Linux内核的信号对象进行讲解。 1. 信号的基本概念 1.1. 概述 信号&#xff08;signal&#xff09;&#xff0c;又称为软中断信号&#xff0c;用于通知进程…

Go语言垃圾回收GC(完整)

垃圾回收的概念 GC(垃圾回收)是 Go 语言中的一个重要机制&#xff0c;用于自动管理内存 在 Go 语言中&#xff0c;GC 会自动发现和回收那些不再被使用的内存空间&#xff0c;从而防止内存泄漏和有 效利用内存。 内存垃圾怎样产生 程序在内存上被分为堆区、栈区、全局数据区、…

黑马头条vue2.0项目实战(一)——项目初始化

1. 图标素材&#xff08;iconfont简介&#xff09; 制作字体图标的工具有很多&#xff0c;推荐使用&#xff1a;iconfont-阿里巴巴矢量图标库。 注册账户 创建项目 可以根据项目自定义 class 前缀 上传图标到项目 生成链接&#xff0c;复制 css 代码&#xff0c;在项目中使用…

数组与链表谁访问更快

一、线性表 线性表是数据结构中的一种基本类型&#xff0c;它由一组线性排列的元素组成。线性表的特点是可以进行顺序访问&#xff0c;但不支持随机访问。 二、非线性表 非线性表是数据结构中另一种类型&#xff0c;如树和图&#xff0c;它们由多个节点组成&#xff0c;节点…

【云原生】Docker搭建知识库文档协作平台Confluence

目录 一、前言 二、企业级知识库文档工具部署形式 2.1 开源工具平台 2.1.1 开源工具优点 2.1.2 开源工具缺点 2.2 私有化部署 2.3 混合部署 三、如何选择合适的知识库平台工具 3.1 明确目标和需求 3.2 选择合适的知识库平台工具 四、Confluence介绍 4.2 confluence特…

链表篇: 01-从尾到头打印链表

解题思路: 直接往数组中加入数据&#xff0c;然后通过Java提供的工具类(coollections) 直接进行数组的反转。 代码实现: import java.util.*; /** * //ListNode 的数据结构 * * public class ListNode { * int val; * ListNode next null; * * …

韩顺平0基础学java——第39天

p820-841 jdbc和连接池 1.JDBC为访问不同的数据库提供了统一的接口&#xff0c;为使用者屏蔽了细节问题。 2.Java程序员使用JDBC&#xff0c;可以连接任何提供了JDBC驱动程序的数据库系统&#xff0c;从而完成对数据库的各种操作。 3.jdbc原理图 JDBC带来的好处 2.JDBC带来的…

10 Vue 特性要点

Vue2 特性要点 Vue2 源码理解 Vue 双向数据绑定 先从单向绑定切入单向绑定非常简单,就是把Mode1绑定到view,当我们用Javascript代码更新Model时, view就会自动更新 双向绑定就很容易联想到了,在单向绑定的基础上,用户更新了View, Mode1的数据也自动被更新了 因为 Vue 是数据双向…

手把手教你集成GraphRag.Net:打造智能图谱搜索系统

在人工智能和大数据发展的背景下&#xff0c;我们常常需要在项目中实现知识图谱的应用&#xff0c;以便快速、准确地检索和使用信息。 今天&#xff0c;我将向大家详细介绍如何在一个新的.NET项目中集成GraphRag.Net&#xff0c;这是一个参考GraphRag实现的.NET版本&#xff0c…

Flutter——全网最精致木鱼APP可上架应用市场

研发背景 工作之余&#xff0c;闲来无事&#xff0c;想着研发一款用户可能会经常用到的一款APP,并且能够顺便掌握一下Flutter Material Design 3 UI&#xff0c;所以就有了这款比较精致的木鱼APP的诞生。 开源代码 https://github.com/z244370114/woodenfish

YOLOV8源码解读-C2f模块-以及总结c2模块、Bottleneck

c2f模块是对c2模块的改进 c2模块图解解读 先给出YOLOV8中卷积的定义模块一键三连-卷积-BN-激活函数 def autopad(k, pNone, d1): # kernel, padding, dilation"""Pad to same shape outputs."""if d > 1:k d * (k - 1) 1 if isinstance…

linux练习2

一、搭建nfs服务器&#xff0c;客户端可从服务端/share目录上传与下载文件 **服务端** 1、下载相关安装包 [rootserver ~]# yum install rpcbind -y [rootserver ~]# yum install nfs-utils -y 2、 创建共享文件夹/share并授予权限 [rootserver ~]# mkdir /share [rootserv…

结构体笔记

结构体 C语言中的数据类型&#xff1a; 基本数据类型&#xff1a;char/int/short/double/float/long 构造数据类型&#xff1a;数组&#xff0c;指针&#xff0c;结构体&#xff0c;共用体&#xff0c;枚举 概念&#xff1a; 结构体是用户自定义的一种数据类型&#xff0c…

【七】Hadoop3.3.4基于ubuntu24的分布式集群安装

文章目录 1. 下载和准备工作1.1 安装包下载1.2 前提条件 2. 安装过程STEP 1: 解压并配置Hadoop选择环境变量添加位置的原则检查环境变量是否生效 STEP 2: 配置Hadoop2.1. 修改core-site.xml2.2. 修改hdfs-site.xml2.3. 修改mapred-site.xml2.4. 修改yarn-site.xml2.5. 修改hado…