Linux——网络基础Socket编程

news2024/11/13 12:08:46

目录

一计算机网络背景

二协议 

1初始协议

1.1协议分层

1.2OSI七层模型

1.3TCP/IP五层模型 

2再始协议 

2.1为什么要有TCP/IP协议

2.2TCP/IP与OS的关系

2.3所以什么是协议

三网络传输基本流程

1局域网(以太网)通信原理

1.1认识mac地址

2同网段主机间收发消息的过程

2.1报文与报头

四跨网络传输流程图

1认识IP地址

2IP地址的意义 

 3IP地址VSMac地址

​编辑

 五Socket 编程预备

1IP与进程

2端口号

3传输层 

3.1认识Tcp协议

3.2认识Udp协议

4网络字节序

 5socket常见接口

socket API

sockaddr 结构

​编辑

 编码结构

六UDP网络编程 

1基础版本 

2翻译版本

3聊天室版本 

七TCP网络编程 

1基础版本

2命令版本 

八网络命令

Ping 命令

netstat命令 

pidof命令

九不同环境下的通信

1Windows作为client访问Linux(UDP)

2Linux作为clinet访问Windows(TCP) 

 十connect的断线重连


一计算机网络背景

在网络未出现之前,计算机之间是处于相互独立的状态;

如果要进行小组之前的资源处理,要通过软盘一个一个进行处理

 而随着后来有了网络,多台计算机能够连接在一起,完成数据共享

局域网(子网): 通过交换机和路由器连接在一起组成的网段

 广域网: 将远隔千里的计算机都连在一起组成的网段

记住:计算机是人的工具, 人要协同工作, 注定了网络的产生是必然的 

二协议 

1初始协议

比如:在以前上大学时,在外求学的大学生通常是用电话来给家里人进行联系;以前电话费用是按分钟来计算的,与家里人联系时恨不得在1分钟内把要说的话给说完:此时聪明的你在来之前与家里人做了约定:如果打了一次过来后挂了就代表没事,打了两次后挂了就代表没钱了,打了三次挂了就代表有事要联系,需要家里那边打过来...

• 打电话约定电话铃响的次数 == 协议

也就是说:协议是一种约定~

计算机之间的传输媒介是光信号和电信号:通过 "频率" 和 "强弱" 来表示 0 和 1 这样的信息

要想传递各种不同的信息,就需要约定好双方的数据格式

但是:两台通信的主机,只要约定好协议就可以了??

虽然共同知道0 1要表达的电流强弱,但是语言的不同,势必让通信再次成为阻碍~

所以,协议的完善需要更加细致化的规定,更需要参与进来的人遵守

在计算机的世界里: 

• 计算机生产厂商有很多
• 计算机操作系统, 也有很多
• 计算机网络硬件设备, 还是有很多

如何让不同厂商之间进行通畅的通信,就需要有人站出来(有着只身实力与众多专利),约定一个共同的标准,让使用的人去遵守这一标准,这就是网络协议

1.1协议分层

协议本质也是软件:在设计上为了更好的进行模块化,解耦合,也是被设计成层状结构

好处:合理的分层可以实现解耦合,让软件维护的成本更低:这也是判断软件好坏之一

软件分层的逻辑在学习语言也同样适用:学习面向对象的三大特点:封装继承多态时,我们要进行资源管理首先要先进行描述:对象的属性特点...在将所有的信息进行组织封装,成为设计中的一层,而继承多态则是对每一层关系的体现,让每一层在解耦合的同时在逻辑上有时紧密的关系~

1.2OSI七层模型

OSI(Open System Interconnection, 开放系统互连) 七层网络模型称为开放式系统互联参考模型, 即是一个逻辑上的定义和规范而已

但OSI终归只是个参考模型,实际上只有5层被实现出来 

1.3TCP/IP五层模型 

TCP/IP 通讯协议采用了 5 层的层级结构, 每一层都呼叫它的下一层所提供的网络来完成自身需求

物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、 早期以太网采用的的同轴电缆(现在主要用于有线电视)、 光纤, 现在的 wifi 无线网使用电磁波等都属于物理层的概念。 物理层的能力决定了最大传输速率、 传输距离、 抗干扰性等. 集线器(Hub)工作在物理层.

数据链路层: 负责设备之间的数据帧的传送和识别. 例如网卡设备的驱动、 帧同步(就是说从网线上检测到什么信号算作新帧的开始)、 冲突检测(如果检测到冲突就自动重发)、 数据差错校验等工作. 有以太网、 令牌环网, 无线 LAN 等标准. 交换机(Switch)工作在数据链路层.

网络层: 负责地址管理和路由选择. 例如在 IP 协议中, 通过 IP 地址来标识一台主机, 并通过路由表的方式规划出两台主机之间的数据传输的线路(路由). 路由器(Router)工作在网路层.

传输层: 负责两台主机之间的数据传输. 如传输控制协议 (TCP), 能够确保数据可靠的从源主机发送到目标主机.

应用层: 负责应用程序间沟通, 如简单电子邮件传输(SMTP) 、 文件传输协
议(FTP) 、 网络远程访问协议(Telnet) 等. 我们的网络编程主要就是针对应用层.

物理层我们考虑的比较少, 我们只考虑软件相关的内容. 因此很多时候我们直接称为四层模型

2再始协议 

2.1为什么要有TCP/IP协议

首先:即便是单机,你的计算机内部是存在协议的,如内存与其它设备进行通信时的通信协议,只是我们感知不到罢了;这些通信都是在本地主机内进行,距离短,出现问题少~

其次:随着主机间通信距离的变长,大大小小会出现各种问题:

1.我要把 你好啊 数据发送给主机B怎么办? 路由器

2.网络上怎么多主机,怎么准确地给主机B发送过去? IP地址

3.如果数据在发出去的途中丢失了怎么办? 超时重传

4.主机B收到数据,它怎么知道对新到来的进行处理? 协议

而新问题的出现往往伴随着新协议的产生来进行解决

所以:为什么要有TCP/IP协议呢? 本质是通信主机距离变长了;它是一种解决方案

而我们也要知道一个事实:发数据不是目的,而是手段,用数据才是目的

2.2TCP/IP与OS的关系

主机之间操作系统不同,但它们之间还能够互相通信:

1.主机的网络协议栈必须是按照标准进行相同的实现的

2.最重要的协议栈是传输层与网络层是由操作系统来实现的,操作系统再怎么不同大家都是要来遵守的

而两者最著名的相关协议是TCP与IP,所以这个网络协议栈叫做TCP/IP协议不过分吧~

从上图我们可以看出:整个协议栈,既要涉及硬件又要设计OS,甚至用户;所以这个协议一定时要各行IT事业来配合

2.3所以什么是协议

截止到目前, 我们还没接触过任何协议, 但是如何朴素的理解协议, 我们已经可以试试了。

问题: 主机 B 能识别 data, 并且准确提取 a=10, b=20, c=30 吗?

能!因为双方都同样有结构体类型:用“相同”的代码来实现协议,用同样的结构体来定义类型,双方之间不就有天然的“共识”,自然就能识别并提取了:这不就是约定吗!

所以,我们来朴素地理解协议:所谓协议, 就是通信双方都认识的结构化的数据类型

三网络传输基本流程

1局域网(以太网)通信原理

两台主机在同一局域网,能否直接通信? 

能!原理就类似上课:在同一间教室里,老师说的话在教室的所有人都能听到(包括老师自己);如果老师点名某某同学起来回答问题时,同学会根据老师的话(数据)来判断站或者不站(身份匹配,名字一般具有唯一性)

而每台主机在局域网中,也是有代表只身唯一性的标识:mac地址

1.1认识mac地址

• MAC 地址用来识别数据链路层中相连的节点;

• 长度为 48 位, 及 6 个字节. 一般用 16 进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)

• 在网卡出厂时就确定了, 不能修改. mac 地址通常是唯一的(虚拟机中的 mac 地址不是真实的 mac 地址, 可能会冲突; 也有些网卡支持用户配置 mac 地址)

主机A发数据(你好)个主机E的过程中,主机B,C,D都会收到A发过来的数据,只是它们在对比des的地址不是自己后不对该数据处理(进行丢弃)知道主机E收到数据并判断是主机A给自己的数据后进行处理

注:两个主机的接收数据要理解成两个协议栈收发数据!!

• 以太网中, 任何时刻, 只允许一台机器向网络中发送数据
• 如果有多台同时发送, 会发生数据干扰, 我们称之为数据碰撞
• 所有发送数据的主机要进行碰撞检测和碰撞避免

• 没有交换机的情况下, 一个以太网就是一个碰撞域
• 局域网通信的过程中,主机对收到的报文确认是否是发给自己的:通过目标mac 地址判定

•从系统的角度路径以太网:

可把它看成临界资源;而碰撞检测与碰撞避免则类似加锁方式进行保护

2同网段主机间收发消息的过程

其中每层都有协议, 所以在进行上述传输流程的时候, 要进行封装和解包
 

2.1报文与报头

每层协议都要从上层获取有效载荷(数据)后添加上这层的报头(结构体字段);

报文 = 报头 + 有效载荷

 注:数据在网络中发送的时候,一定是要依靠硬件支持

而每一层的协议是多种的:

每一层有这么多协议,在进行解包与分用时是怎么知道要用哪个协议??

这就要认识到网络协议的‘共性’:

1.报头与有效载荷分离的问题——解包

2.除了应用层,每一层协议都解决了一个问题:

自己的有效载荷应该交到上层哪个协议中——分用 

能够解决的根本是在封装的过程中添加的报头就代表了这一层的哪个协议 

四跨网络传输流程图

1认识IP地址

IP 协议有两个版本, IPv4 和 IPv6. 我们整个的课程, 凡是提到 IP 协议, 没有特殊说明的,
默认都是指 IPv4

IP 地址是在 IP 协议中, 用来标识网络中不同主机的地址;
• 对于 IPv4 来说, IP 地址是一个 4 字节, 32 位的整数;
• 我们通常也使用 "点分十进制" 的字符串表示 IP 地址, 例如 192.168.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;

跨网段的主机的数据传输:数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器

2IP地址的意义 

• 为什么要去目标主机, 先要走路由器?
• 目的 IP 的意义?

来看看一下详细流程图:

用户A要把“你好”的数据发送给用户B时:要依次进行协议栈的封包;

网络层:在封装的报头中有两个IP地址:起始地址与目的地址

数据链路层:封装的报头中有两个mac地址:起始地址与目的地址

再往下就要把报文交给路由器来进行转发(路由器也是主机),而局域网内的连接的其它主机也同时收到该报文(但发现目的mac地址不是自己会丢弃)

路由器收到报文后会进行向上进行解包:在网络层中得到的报文与用户A在网络层的报文时一样的;路由器发现目的地址是在自己的局域网中后,再往下进行封包(数据链路层在封包时的mac地址就要发生改变改变),直接发送给用户B(往上解包得到数据):而解包到网络层时此时的报文与用户A在网络层的报文也是是相同的!

 总结:

1.网络层(就是IP层)向上(包括网络层)看到的所有的报文是一样的,都至少是IP报文

2.IP可以屏蔽底层网络的差异

(数据链路层的种类很多:以太网,令牌环网,无线...,但从IP层往上看到的都是一样的)

3.所有的网络都是IP网络

(这也就是为什么手机,电脑...智能设备都能连接网络进行通信的原因)

 3IP地址VSMac地址

以唐僧取经为例:

唐僧从长安出发,要前往西天取经时势必要经过很多地方:车迟国,女儿国,火焰山...

到达车迟国时,国王问他:唐僧,你从哪里来,要到哪里去?

唐僧答:国王我从东土大唐来,要到西方去求取真经。上一站是长安,下一站是车迟国:接下来我要到哪里去?

国王想了一下:不就是要去西边嘛,下一站你要去的是女儿国。

知道下一站后,唐僧一行人出发前往女儿国了;在路上时,唐僧想:如果到了女儿国时国王问我从哪来到哪去时,我就是把上一站改成车迟国,下一站改成女儿国就行了...

从这个故事中:我们可以发现:

‘从东土大唐来,到西天去’这个目标是固定不变的,也就是我们所说的IP地址

而达到每个地方时唐僧的上一站与下一站都在进行改变,自然就是Mac地址

唐僧到达的每个地方就相当于路由器:告诉我们下一站要去的地方

IP地址在这个过程一直不变不就是唐僧的长远目标吗!

Mac地址不断发生改变不就是在实现长远目标的短期目标(当下目标)吗!

结论:目的 IP 是路径选择的重要依据, mac 地址是局域网转发的重要依据

以下用图来表示网络通信的宏观流程: 

 五Socket 编程预备

1IP与进程

• IP 在网络中, 用来标识主机的唯一性

但是这里要思考一个问题: 数据传输到主机是目的吗

不是的。 因为数据是给人用的。比如:聊天是人在聊天,下载是人在下载...
但是人是怎么看到聊天信息的呢? 怎么执行下载任务呢? 怎么浏览网页信息呢?

通过启动的 qq, 迅雷, 浏览器。而启动的 qq, 迅雷, 浏览器都是进程。 换句话说:进程是人在系统中的代表,只要把数据给进程,人就相当于就拿到了数据。
所以: 数据传输到主机不是目的, 而是手段。 到达主机内部, 在交给主机内的进程,才是目的。
但是系统中, 同时会存在非常多的进程, 当数据到达目标主机之后, 怎么转发给目标进程?

有人会说:表示进程的唯一性不就是pid吗?的确如此;但在网络中不用pid,而用端口号来表示~

2端口号

端口号(port)是传输层协议的内容

• 端口号是一个 2 字节 16 位的整数;
• IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程;
• 一个端口号只能被一个进程占用 

我在手机端打开抖音时,应用层通过:IP + port 标识唯一的一个进程后数据往下进行封装后,通过网络将数据发送到部署抖音的服务器(也是主机)中,往上进行解包分用获取数据,再将处理好的数据往下进行封装通过网络发送我的手机端,往上进行解包获取数据(也就能看到打开后的视频了)

在这个过程中不就是两个进程通过跨网络在进行进程间通信吗!

在这个过程中有一些细节:

0.两个毫不相干的进程在通信时的前提:要看到同一份资源;这里的资源就是:网络

1.一个进程通过网络发送给另一个进程时的前提:携带源IP与目的IP

IP + 端口号 在实现时我们把它叫做套接字socket

2.服务端(客户端)在启动时,一定要和端口号进行绑定(IP已经是有了的)

3.在学习进程时:pid就能标识进程的唯一性,为什么还要有端口号呢?用pid行不行?

a.端口号是多少不重要,端口号用来标识进程唯一性才重要

b.从技术角度上:用pid实现是可能的:本来pid就具有唯一性;但这会有问题:

pid是系统层面的概念:每次启动时是不同的;每次在通信时都要发生改变,造成强耦合性;

有了网络层上的端口号概念:每次启动时先进行绑定后续都不会发送改变(解耦合);

这也是我们在现实生活中:学校为了同一管理给每个分配学号不用身份证来标识唯一性的道理一样,从社会层面与国家层面进行解耦;

而有了端口号:在主机内部那些进程有端口号就表面该进程需要通信管理起来方便~

端口号范围划分:

0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024-65535: 操作系统动态分配的端口号. 客户端程序的端口号就是由操作系统从这个范围分的.

3传输层 

        如果我们了解了系统, 也了解了网络协议栈, 我们就会清楚, 传输层是属于内核的, 那么我们要通过网络协议栈进行通信, 必定调用的是传输层提供的系统调用, 来进行的网络通信。

3.1认识Tcp协议

此处我们先对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识:

• 传输层协议
• 有连接
可靠传输
• 面向字节流

3.2认识Udp协议

此处我们也是对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识:

• 传输层协议
• 无连接
不可靠传输
• 面向数据报

4网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分:在协议中是怎么规定的呢??

• 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
• 接收主机把从网络上接到的字节依次保存在接收缓冲区中,按内存地址从低到高的顺序保存;
• TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节

以下库函数做网络字节序和主机字节序的转换:

 5socket常见接口

socket API

C 
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

sockaddr 结构

利用C++中多态属性实现C语言版的多态

 编码结构

sockaddr结构

socketaddr_in结构

in_addr结构

六UDP网络编程 

1基础版本 

云服务器上,服务端不能直接(也强烈不建议)bind 自己的公网IP

服务器IP我们一般指定为0(服务器bind任何IP)(INADDR_ANY)

// UdpServer.hpp
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>

#include "Log.hpp"
#include "Nocopy.hpp"
#include "InetAddr.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};

class UdpServer : public Nocopy
{
public:
    UdpServer(const uint16_t &localport = 8888, const int &localfd = -1)
        : _localport(localport), _localfd(localfd), _isrunning(false)
    {
    }
    void Init()
    {
        // 1.创建  --> (网卡打开了)
        _localfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_localfd < 0)
        {
            LOG(FATAL, "sockfd create fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "sockfd create sucess,_sockfd: %d\n", _localfd);
        // 2.绑定
        // 2.1填充本地信息
        struct sockaddr_in local;
        // 定义出来先清空
        memset(&local, 0, sizeof(local));
        // ##左右拼接再替换
        local.sin_family = AF_INET;
        // 主机序列要转成网络序列
        local.sin_port = htons(_localport);
        // a.需要4字节IP b.需要网络序列的IP
        // local.sin_addr.s_addr = inet_addr(_locallip.c_str());
        local.sin_addr.s_addr = INADDR_ANY; // 0
        // 2.2将填充好的结构体设置进内核中
        int n = ::bind(_localfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind fail\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind sucess\n");
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 接收信息
            char buffer[1024];       // 收到的信息
            struct sockaddr_in perr; // 是谁发的
            socklen_t len = sizeof(perr);
            ssize_t n = recvfrom(_localfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&perr, &len);
            if (n < 0)
            {
                LOG(ERROR, "received fail\n");
            }
            buffer[n] = 0;
            InetAddr addr(perr);
            // uint16_t perrport = ntohs(perr.sin_port);
            // std::string perrip = inet_ntoa(perr.sin_addr);
            std::cout << "[" << addr.Ip() << ' ' << addr.Port() << " ]: " << buffer << std::endl;

            // 发送消息
            std::string echo_string = "[client]: ";
            echo_string += buffer;

            ssize_t m = sendto(_localfd, echo_string.c_str(), echo_string.size(), 0, (sockaddr *)&perr, len);
            if (m < 0)
            {
                std::cerr << "sended fail" << std::endl;
            }
        }
    }
    ~UdpServer()
    {
        if (_localfd < 3)
            close(_localfd);
    }

private:
    int _localfd;
    uint16_t _localport; // 端口
    // std::string _locallip; // ip 后面要特殊处理
    bool _isrunning;
};

// InetAddr.hpp  --> 储存客户端的信息
#pragma once
#include<iostream>
#include<string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr
{
    void ToHost()
    {
        _ip=inet_ntoa(_addr.sin_addr);
        _port=ntohs(_addr.sin_port);
    }
public:
    InetAddr(struct sockaddr_in addr)
    :_addr(addr)
    {
        ToHost();
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    struct sockaddr_in Addr()
    {
        return _addr;
    }
private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

//NoCopy.hpp  --> 不让服务器对象进行拷贝
class Nocopy
{
public:
    Nocopy(){}
    ~Nocopy(){}
    Nocopy(const Nocopy&)=delete;
    Nocopy& operator=(const Nocopy&)=delete;
};

client不让用户自己设定,而是OS随机选择(客户端冲突)

client需要bind自己的IP和端口,但是client 不需要‘显示’bind自己的IP和端口

client首次向server发送数据时,OS自动给clinet bind自己的IP和端口

// UdpClient.hpp
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>

#include "Nocopy.hpp"
#include "Log.hpp"

// 客户端在未来一定要知道服务端的IP和端口号
class UdpClient : public Nocopy
{
public:
    UdpClient(const std::string &locallp, uint16_t localport, int socketfd = -1)
        : _locallp(locallp), _localport(localport), _socketfd(socketfd), _isrunning(false)
    {
    }

    void Start()
    {
        _isrunning = true;
        // 创建
        _socketfd = ::socket(AF_INET6, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            LOG(FATAL, "create sockfd fail\n");
            exit(1);
        }

        // 客户端不让用户自己设定,而是OS随机选择(客户端冲突)
        // client需要bind自己的IP和端口,但是client 不需要‘显示’bind自己的IP和端口
        // client首次向server发送数据时,OS自动给clinet bind自己的IP和端口
        struct sockaddr_in line;
        memset(&line, 0, sizeof(line));
        line.sin_family = AF_INET;
        line.sin_port = htons(_localport); // 转换
        line.sin_addr.s_addr = inet_addr(_locallp.c_str()); // 转换

        while (_isrunning)
        {
            std::string buffer;
            std::cout << "Please Write: ";
            getline(std::cin, buffer);

            ssize_t n = ::sendto(_socketfd, buffer.c_str(), buffer.size(), 0, (sockaddr *)&line, sizeof(line));
            if (n < 0)
            {
                LOG(ERROR,"Sended Fail\n");
                break;
            }
            char message[1024];
            struct sockaddr_in tmp;
            socklen_t len=sizeof(tmp);
            ssize_t m=::recvfrom(_socketfd,&message,sizeof(message)-1,0,(struct sockaddr*)&tmp,&len);
            if(m<0)
            {
                LOG(ERROR,"Recvform Fail\n");
                break;
            }
            message[m]=0;
            std::cout<<message<<std::endl;
        }
    }
    ~UdpClient()
    {
        if (_socketfd < 3)
            ::close(_socketfd);
    }

private:
    std::string _locallp;
    uint16_t _localport;
    int _socketfd;
    bool _isrunning;
};
//ServerMain.cc
#include"UdpServer.hpp"

//Server-IP 建议为0(收到任意IP)
int main(int args,char* argv[])
{
    if(args!=2)
    {
        std::cerr<<"User: "<<argv[0]<<" Server-Port"<<std::endl;
        exit(1);
    }
    uint16_t Port=std::stoi(argv[1]);
    //std::unique_ptr<UdpServer> user=std::make_unique<UdpServer>(ip); //c++14
    UdpServer user(Port);
    
    user.Init();
    user.Start();
    return 0;
}

//ClientMain.cc
#include"UdpClient.hpp"

int main(int args,char* argv[])
{
    if(args!=3)
    {
        std::cerr<<"User: "<<argv[0]<<"Server-Ip Server-Port"<<std::endl;
        exit(1);
    }
    
    std::string Ip=argv[1];
    uint16_t Port=std::stoi((argv[2]));
    //std::unique_ptr<UdpServer> user=std::make_unique<UdpServer>(ip); //c++14
    UdpClient user(Ip,Port);
    user.Start();
    return 0;
}

2翻译版本

将IO逻辑(服务器收到消息)与业务逻辑(进行翻译)进行解耦:借助function函数实现

进行回调时只需通过上层提供的方法进行回调后返回

//UdpServer.hpp
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <functional>

#include "Log.hpp"
#include "InetAddr.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};

using fun_t = std::function<std::string(std::string)>;

// IO逻辑 与 业务逻辑 进行解耦
class UdpServer 
{
public:
    UdpServer(fun_t fuc,const uint16_t &localport = 8888, const int &localfd = -1)
        : _fuc(fuc),_localport(localport), _localfd(localfd), _isrunning(false)
    {
    }
    void Init()
    {
        // 1.创建  --> (网卡打开了)
        _localfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_localfd < 0)
        {
            LOG(FATAL, "sockfd create fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "sockfd create sucess,_sockfd: %d\n", _localfd);
        // 2.绑定
        // 2.1填充本地信息
        struct sockaddr_in local;
        // 定义出来先清空
        memset(&local, 0, sizeof(local));
        // ##左右拼接再替换
        local.sin_family = AF_INET;
        // 主机序列要转成网络序列
        local.sin_port = htons(_localport);
        // a.需要4字节IP b.需要网络序列的IP
        // local.sin_addr.s_addr = inet_addr(_locallip.c_str());
        local.sin_addr.s_addr = INADDR_ANY; // 0
        // 2.2将填充好的结构体设置进内核中
        int n = ::bind(_localfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind fail\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind sucess\n");
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 接收信息
            char buffer[1024];       // 收到的信息
            struct sockaddr_in perr; // 是谁发的
            socklen_t len = sizeof(perr);
            ssize_t n = recvfrom(_localfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&perr, &len);
            if (n < 0)
            {
                LOG(ERROR, "received fail\n");
            }
            buffer[n] = 0;
            InetAddr addr(perr);
            std::cout << "[" << addr.Ip() << ' ' << addr.Port() << " ]: " << buffer << std::endl;

            //进行翻译(回调)
            std::string result = _fuc(buffer);

            ssize_t m = sendto(_localfd, result.c_str(), result.size(), 0, (sockaddr *)&perr, len);
            if (m < 0)
            {
                LOG(ERROR, "sendto fail\n");
            }
        }
    }
    ~UdpServer()
    {
        if (_localfd < 3)
            close(_localfd);
    }

private:
    int _localfd;
    uint16_t _localport; // 端口
    // std::string _locallip; // ip 后面要特殊处理
    bool _isrunning;
    fun_t _fuc;
};

写一个实现翻译功能的对象:

1.从文件中读取单词与单词对应的解释(储存在unordered_map)

2. 获取用户输入的单词在容器中查找并给出结果

//Dict.hpp
#include<iostream>
#include<string>
#include<unordered_map>
#include<fstream>

#include"Log.hpp"

const static std::string tmp=": ";
class Dict
{
private:
    void LoadWorld()
    {
        std::ifstream in(_path);
        if(!in.is_open())
        {
            LOG(ERROR,"open file fail\n");
            exit(1);
        }
        std::string line;
        while(std::getline(in,line))
        {
            if(line=="") continue;

            size_t pos=line.find(tmp);
            if(pos==std::string::npos) continue;

            std::string key=line.substr(0,pos);
            if(key=="") continue;
            std::string valute=line.substr(pos+2);
            if(valute=="") continue;

            _dict.insert({key,valute});
            LOG(DEBUG,"Add %s sucess\n",line.c_str());
        }
        LOG(DEBUG,"Add Finish\n");
        in.close();
    }
public:
    Dict(const std::string& path)
    :_path(path)
    {
        LoadWorld();
    }
    std::string Translate(const std::string& world)
    {
        if(world=="") return "None";
        auto result=_dict.find(world);
        if(result==_dict.end()) return "None";
        else return result->second;
    }

private:
    std::string _path;
    std::unordered_map<std::string,std::string> _dict;
};

//ServerMain.cc
#include<memory>

#include"UdpServer.hpp"
#include"Dict.hpp"

int main(int args,char* argv[])
{
    if(args!=2)
    {
        std::cerr<<"User: "<<argv[0]<<" Port"<<std::endl;
    }

    uint16_t Port=std::stoi(argv[1]);

    Dict dict("./dict.txt");
    auto translate=std::bind(&Dict::Translate,&dict,std::placeholders::_1);
    
    std::unique_ptr<UdpServer> user=std::make_unique<UdpServer>(translate,Port); //c++14
    user->Init();
    user->Start();
    return 0;
}

3聊天室版本 

设计client端时要解决:如果在聊天室有人不发消息时它也能看到别人发的消息

通过双线程(一个收,一个发解决)

//Client.cc

#include<functional>
#include "Log.hpp"
#include "UdpClient.hpp"
#include "ThreadPool.hpp"

void ReceveMessage(UdpClient *r)
{
    r->Receve();
}

void SendMessage(UdpClient *r)
{
    r->Send();
}

int main(int args, char *argv[])
{
    if (args != 3)
    {
        std::cerr << "User: " << argv[0] << "Ip Port" << std::endl;
        exit(0);
    }

    std::string Ip = argv[1];
    uint16_t Port = std::stoi((argv[2]));

    UdpClient *r = new UdpClient(Ip, Port);

    r->Init();
    // 创建线程进行发送与接收 -- 解决信息积压
    Thread recever("Thread_Recever", std::bind(&ReceveMessage, r));
    Thread sender("Thread_Sender", std::bind(&SendMessage, r));

    recever.Start();
    sender.Start();

    recever.Join();
    sender.Join();

    return 0;
}

//UdpClient.hop
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>

#include "Nocopy.hpp"

class UdpClient : public Nocopy
{
public:
    UdpClient(const std::string &locallp, uint16_t localport, int socketfd = -1)
        : _locallp(locallp), _localport(localport), _socketfd(socketfd), _isrunning(false)
    {
    }
    int Sockfd()
    {
        return _socketfd;
    }
    void Init()
    {
        _socketfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            LOG(ERROR, "socket fail\n");
            exit(1);
        }
        
    }
    void Send()
    {
        struct sockaddr_in line;
        memset(&line, 0, sizeof(line));
        line.sin_addr.s_addr = ::inet_addr(_locallp.c_str()); // 转换
        line.sin_family = AF_INET;
        line.sin_port = htons(_localport); // 转换
        // 发送
        while (true)
        {
            std::string buffer;
            std::cout << "Please Write :";
            std::getline(std::cin, buffer);
            ssize_t n = ::sendto(_socketfd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&line, sizeof(line));
            if (n < 0)
            {
                LOG(ERROR, "sendto fail\n");
                break;
            }
        }
    }

    void Receve()
    {
        // 接收
        while (true)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int n = recvfrom(_socketfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                // 将消息打到标准错误中
                std::cerr << buffer << std::endl;
            }
            else
            {
                LOG(ERROR, "recvfrom error\n");
                break;
            }
        }
    }

    ~UdpClient()
    {
        if (_socketfd < 3)
            ::close(_socketfd);
    }

private:
    std::string _locallp;
    uint16_t _localport;
    int _socketfd;
    bool _isrunning;
};

//InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr
{
    void ToHost()
    {
        //_ip=inet_ntoa(_addr.sin_addr);//4字节地址->char* 理论与实践有冲突 不用
        _port = ntohs(_addr.sin_port);

        char buffer[124];
        inet_ntop(AF_INET, &_addr.sin_addr, buffer, sizeof(buffer));
        _ip = buffer;
    }

public:
    InetAddr(const struct sockaddr_in &addr)
        : _addr(addr)
    {
        ToHost();
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    struct sockaddr_in Addr()
    {
        return _addr;
    }

    bool operator==(const InetAddr &ad)
    {
        return this->_ip == ad._ip && this->_port == ad._port;
    }

    std::string User()
    {
        std::string tmp = _ip + " " + std::to_string(_port);
        return tmp;
    }

private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

设计Route对象来完成信息转发给所有人(通过线程池进行sendto)

设计时要注意:用来储存在线人数的容器可能存在线程安全问题,要进行加锁保护

(如果没用线程池当我没说)

//Route.hpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>

#include "LockGuard.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using task_t = std::function<void()>;

using namespace threadpool;
class Route
{
public:
    Route()
    {
        pthread_mutex_init(&_mutex,nullptr);
    }

    bool Vis(InetAddr &who)
    {
        LockGuard lock(&_mutex);

        for (auto &user : _users)
        {
            if (user == who)
            {
                return false;
            }
        }
        return true;
    }

    void Erase(InetAddr &who)
    {
        LockGuard lock(&_mutex);

        auto it = _users.begin();
        while (it != _users.end())
        {
            if ((*it) == who)
            {
                _users.erase(it);
                LOG(DEBUG,"Erase [%s]\n",who.User().c_str());
                break;
            }
            it++;
        }
    }

    void Send(int socketfd, const std::string message, InetAddr who) // 线程执行方法
    {
        LockGuard lock(&_mutex);

        std::string send_message="["+who.User()+"]: "+message;
        for (auto &user : _users)
        {
            struct sockaddr_in perr = user.Addr();
            sendto(socketfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&perr,sizeof(user)); 
        }
    }

    void Forward(int socketfd, const std::string &message, InetAddr &who) // 外面调用方法
    {
        // 用户在不在users里
        if (Vis(who))
        {
            LOG(DEBUG, "Add [%s]\n",who.User().c_str());
            _users.push_back(who);
        }
        // 用户想退出了
        if (message == "q" || message == "quit")
        {
            LOG(DEBUG, "[%s] quit\n",who.User().c_str());
            Erase(who);
        }

        // 用户一定在user中
        // 收到信息进行发送(单进程执行效率低)
        // Send(socketfd,message);

        // 使用多线程进行任务发送(方法绑定)
        task_t t = std::bind(&Route::Send, this, socketfd, message, who); 
        ThreadPool<task_t>::GetInstance()->Push(t);
    }

    ~Route()
    {
        pthread_mutex_destroy(&_mutex);
    }

private:
    std::vector<InetAddr> _users;//线程安全(加锁)
    pthread_mutex_t _mutex;
};

//LockGuard.hpp
#pragma once

#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

//Nocopy.hpp

class Nocopy
{
public:
    Nocopy(){}
    ~Nocopy(){}
    Nocopy(const Nocopy&)=delete;
    Nocopy& operator=(const Nocopy&)=delete;
};

//ThreadPool.hpp
#pragma once

#include<queue>

#include "Thread.hpp"
using namespace mythread;

const static int gnum = 5;
namespace threadpool
{
    void Lock(pthread_mutex_t &_mx)
    {
        pthread_mutex_lock(&_mx);
    }
    void UnLock(pthread_mutex_t &_mx)
    {
        pthread_mutex_unlock(&_mx);
    }
    template <typename T>
    class ThreadPool
    {
    private:
        void Sleep()
        {
            LOG(DEBUG, "Thread Sleep\n");
            pthread_cond_wait(&_cond, &_mutex);
        }
        void Wake()
        {
            LOG(DEBUG, "Thread Wake\n");
            pthread_cond_signal(&_cond);
        }
        void WakeAll()
        {
            pthread_cond_broadcast(&_cond);
        }
        void Excute()
        {
            while (true)
            {
                Lock(_mutex);
                while (_task.empty() && _isrunning)
                {
                    _sleep_num++;
                    Sleep();
                    _sleep_num--;
                }
                // 没任务要退出了
                if (_task.empty() && !_isrunning)
                {
                    UnLock(_mutex); // 先解锁
                    LOG(DEBUG, "Thread Quit\n");
                    break;
                }
                // 有任务继续执行
                T t = _task.front();
                _task.pop();
                UnLock(_mutex);
                // 在外面处理任务
                t();
            }
        }

        ThreadPool(int num = gnum)
            : _num(num), _sleep_num(0), _isrunning(false)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        ThreadPool(const ThreadPool<T> &) = delete;
        void operator=(const ThreadPool<T> &) = delete;

        void Init()
        {
            for (int i = 0; i < _num; i++)
            {
                std::string name = "thread-" + std::to_string(i + 1);
                _threads.emplace_back(name, std::bind(&ThreadPool::Excute, this));
            }
        }
        void Start()
        {
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
            }
        }

    public:
        // 懒汉模式
        static ThreadPool<T> *GetInstance()
        {
            if (_tp == nullptr)
            {
                LOG(DEBUG, "Creat Threadpool\n");
                Lock(_sig_mutex);
                if (_tp == nullptr)
                {
                    _tp = new ThreadPool(); // new 对象出来
                    _tp->Init();
                    _tp->Start();
                }
                UnLock(_sig_mutex);
            }
            else
            {
                LOG(DEBUG, "Get Threadpool\n");
            }
            return _tp;
        }

        void Push(const T &in)
        {
            Lock(_mutex);
            if (_isrunning)
            {
                _task.push(in);
                if (_sleep_num > 0)
                    Wake();
            }
            UnLock(_mutex);
        }
        void Stop()
        {
            Lock(_mutex);
            _isrunning = false;
            WakeAll();
            UnLock(_mutex);
        }
        void Join()
        {
            for(auto& thread:_threads)
            {
                thread.Join();
            }
        }

    private:
        int _num;
        std::vector<Thread> _threads;
        std::queue<T> _task;
        bool _isrunning;
        int _sleep_num;

        pthread_mutex_t _mutex;
        pthread_cond_t _cond;

        static ThreadPool<T> *_tp;
        static pthread_mutex_t _sig_mutex;
    };
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
    template <typename T>
    pthread_mutex_t ThreadPool<T>::_sig_mutex = PTHREAD_MUTEX_INITIALIZER;
}

//Thread.hpp
#pragma once
#include<iostream>
#include<functional>
#include<string>

#include "Log.hpp"

namespace mythread
{
    //typedef void (*fun_t)(const std::string &name); // 用户线程执行的回调方法
    using fun_t=std::function<void()>;

    class Thread
    {
    public:
        void Excute()
        {
            _IsRun = true; // 线程开始跑起来了
            //_result = _func(_name); // 线程结束时返回结果
            _func();
        }

    public:
        Thread(const std::string &name, const fun_t func)
            : _name(name), _func(func)
        {
            LOG(DEBUG,"%s created\n", _name.c_str());
        }

        static void *ThreadRun(void *args) // 1.设置成static->属于类不属于对象->没有this干扰
        {
            //_func(_name);2.写在这里不能访问私有化成员
            Thread *Self = (Thread *)args; // 获得当前对象

            Self->Excute(); // 执行用户定义的方法(回调函数)
            
            return nullptr;
        }

        bool Start()
        {
            int n = ::pthread_create(&_ptd, nullptr, ThreadRun, this); // 参数传对象
            if (n == 0)
                return true;
            else
                return false;
        }

        void Stop()
        {
            if (_IsRun)
            {
                pthread_cancel(_ptd);
                _IsRun = false;
                LOG(DEBUG,"%s stop\n",_name.c_str());
            }
        }

        void Join()
        {
            pthread_join(_ptd, nullptr);
            LOG(DEBUG,"%s join\n",_name.c_str());
        }

    private:
        pthread_t _ptd;
        fun_t _func;
        std::string _result;

        std::string _name;
        bool _IsRun;
    };
}

//Log.hpp
#pragma once
#include <pthread.h>
#include <fstream>
#include <syscall.h>
#include <stdarg.h>
#include <unistd.h>
#include <cstring>

enum
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

std::string Getlevel(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
        break;
    case INFO:
        return "INFO";
        break;
    case WARNING:
        return "WARNING";
        break;
    case ERROR:
        return "ERROR";
        break;
    case FATAL:
        return "FATAL";
        break;
    default:
        return "";
        break;
    }
}

std::string Gettime()
{
    time_t now = time(nullptr);
    struct tm *time = localtime(&now);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
             time->tm_year + 1900,
             time->tm_mon + 1,
             time->tm_mday,
             time->tm_hour,
             time->tm_min,
             time->tm_sec);
    return buffer;
}

struct log_message
{
    std::string _level;
    int _id;
    std::string _filename;
    int _filenumber;
    std::string _cur_time;
    std::string _message;
};

#define SCREAM 1
#define FILE 2

#define DEVELOP 3
#define OPERATION 4

const std::string gpath = "./log.txt";
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;

class log
{
public:
    log(const std::string &path = gpath, const int status = DEVELOP)
        : _mode(SCREAM), _path(path), _status(status)
    {
    }
    void SelectMode(int mode)
    {
        _mode = mode;
    }
    void SelectStatus(int status)
    {
        _status = status;
    }

    void PrintScream(const log_message &le)
    {
        printf("[%s][%d][%s][%d][%s] %s",
               le._level.c_str(),
               le._id,
               le._filename.c_str(),
               le._filenumber,
               le._cur_time.c_str(),
               le._message.c_str());
    }
    void PrintFile(const log_message &le)
    {
        std::fstream in(_path, std::ios::app);
        if (!in.is_open())
            return;
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "[%s][%d][%s][%d][%s] %s",
                 le._level.c_str(),
                 le._id,
                 le._filename.c_str(),
                 le._filenumber,
                 le._cur_time.c_str(),
                 le._message.c_str());
        in.write(buffer, strlen(buffer)); // 不用sizeof
        in.close();
    }
    void PrintLog(const log_message &le)
    {
        // 过滤
        if (_status == OPERATION)
            return;

        // 线程安全
        pthread_mutex_lock(&gmutex);

        switch (_mode)
        {
        case SCREAM:
            PrintScream(le);
            break;
        case FILE:
            PrintFile(le);
            break;
        default:
            break;
        }

        pthread_mutex_unlock(&gmutex);
    }
    void logmessage(int level, const std::string &filename, int filenumber, const char *message, ...)
    {
        log_message le;
        le._level = Getlevel(level);
        le._id = syscall(SYS_gettid);
        le._filename = filename;
        le._filenumber = filenumber;
        le._cur_time = Gettime();

        va_list vt;
        va_start(vt, message);
        char buffer[128];
        vsnprintf(buffer, sizeof(buffer), message, vt);
        va_end(vt);
        le._message = buffer;

        // 打印日志
        PrintLog(le);
    }
    ~log()
    {
    }

private:
    int _mode;
    std::string _path;
    int _status;
};

// 方便上层调用
log lg;

// ##不传时可忽略参数
#define LOG(level, message, ...)                                          \
    do                                                                    \
    {                                                                     \
        lg.logmessage(level, __FILE__, __LINE__, message, ##__VA_ARGS__); \
    } while (0)

#define SleftScream()          \
    do                         \
    {                          \
        lg.SelectMode(SCREAM); \
    } while (0)
#define SleftFile()          \
    do                       \
    {                        \
        lg.SelectMode(FILE); \
    } while (0)

#define SleftDevelop()            \
    do                            \
    {                             \
        lg.SelectStatus(DEVELOP); \
    } while (0)
#define SleftOperation()            \
    do                              \
    {                               \
        lg.SelectStatus(OPERATION); \
    } while (0)

接下来是服务器的设计 

//UdpServer.hpp
#pragma once
#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <functional>

#include "Nocopy.hpp"
#include "InetAddr.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};

using server_t = std::function<void(int, const std::string &message, InetAddr &who)>; // ?

class UdpServer : public Nocopy
{
public:
    UdpServer(uint16_t localport , server_t fuc,const int &localfd = -1)
        :_localport(localport),
        _fuc(fuc), 
        _localfd(localfd),
        _isrunning(false)
    {}
    void Init()
    {
        // 创建
        _localfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_localfd < 0)
        {
            LOG(FATAL, "create socket fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "Create Socket Sucess,Sockfd: %d\n", _localfd);
        // 绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport); // 统一
        local.sin_addr.s_addr = INADDR_ANY;
        ssize_t n = bind(_localfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "Bind Success\n");
    }
    void Start()
    {
        _isrunning = true;
        char buffer[1024]; // 收到的信息
        while (_isrunning)
        {
            // 接收信息
            struct sockaddr_in perr;
            socklen_t len = sizeof(perr);
            ssize_t n = recvfrom(_localfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&perr, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr ar(perr);
                LOG(DEBUG, "[%s]: %s\n", ar.User().c_str(), buffer);
                // 进行回调(收到的信息发送给所有人)
                _fuc(_localfd, buffer, ar);
            }
            else
            {
                LOG(ERROR, "recvfrom error\n");
            }
        }
        _isrunning = false;
    }
    ~UdpServer()
    {
        if (_localfd < 3)
            close(_localfd);
    }

private:
    int _localfd;
    uint16_t _localport; // 端口
    bool _isrunning;
    server_t _fuc;
};

//Server.cc
#include <memory>

#include "Log.hpp"
#include "UdpServer.hpp"
#include "Route.hpp"

int main(int args, char *argv[])
{
    if (args != 2)
    {
        std::cerr << "User: " << argv[0] << " Port" << std::endl;
    }
    uint16_t Port = std::stoi(argv[1]);

    Route r;
    server_t fuc = std::bind(&Route::Forward, &r,
                             std::placeholders::_1, std::placeholders::_2,
                             std::placeholders::_3);
    std::unique_ptr<UdpServer> user = std::make_unique<UdpServer>(Port,fuc); // c++14

    user->Init();
    user->Start();
    return 0;
}

七TCP网络编程 

1基础版本

 Udp和Tcp网络编程是类似的:不过Tcp在bind后要进行listen监听新连接到来

而listen的返回值我们叫做:sockfd:这与之前socket的返回值一样??

在Tcp中,socket的返回值叫做listensockfd;listen的返回值叫做sockfd;

以一个故事来理清这两者的关系:

在某地有一个鱼店,生意一直很不错,他们的经营模式是:一个服务员在外面拉客;其他服务员(在店内)负责将拉来的客安排到桌子上进行用餐服务;而在外面拉客的服务员可不可能拉客后客人不来他就不拉了??这时不可能的,失败后他会继续等待人进行拉客

所以:这家鱼店的生意可以说大部分是:拉客服务员的功劳

而sockfd就是这个在外面拉客的服务员:listensockfd则是其它服务员辅助sockfd完成连接 

// Server.hpp
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>


#include "InetAddr.hpp"
#include "Log.hpp"
#include "ThreadPool.hpp"

enum
{
    SOCK_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    WAIT_ERROR
};

const static int gsockfd = -1;
const static int gport = 8888;
const static int gbacklog = 8;

using task_t = std::function<void()>;
using namespace threadpool;

class TcpServer
{
public:
    TcpServer(const uint16_t port = gport)
        : _port(port),
          _listensockfd(gsockfd),
          _isrunning(false)
    {
    }

    void Init()
    {
        // 创建
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(FATAL, "socket fail\n");
            exit(SOCK_ERROR);
        }
        LOG(DEBUG, "socket sucess , sockfd: %d\n", _listensockfd);
        // 绑定
        struct sockaddr_in perr;
        memset(&perr, 0, sizeof(perr));
        perr.sin_family = AF_INET;
        perr.sin_port = htons(_port);
        perr.sin_addr.s_addr = INADDR_ANY; // 不推荐绑定固定IP
        if (::bind(_listensockfd, (struct sockaddr *)&perr, sizeof(perr)) < 0)
        {
            LOG(ERROR, "bind fail\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind sucess\n");
        // 监听 Tcp是面向连接的,未来有连接源源不断来 --> 老板模式
        if (::listen(_listensockfd, gbacklog) < 0)
        {
            LOG(ERROR, "listen fail\n");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen sucess\n");
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取新连接 --> 与Udp的recvfrom类似
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 这里的sockfd 与 _listensockfd 有什么区别?
            // _listensockfd 只协助accept 获取新连接(每次不一定有新连接到来(阻塞))
            // sockfd 只提供服务
            int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                LOG(WARNING, "accept fail\n");
                // exit(ACCEPT_ERROR);
                continue; // 没有 继续进行连接
            }
            InetAddr addr(client);
            LOG(DEBUG, "get a line , client: %s\n", addr.User().c_str());

            // 提供服务

            // v0 -- (单进程)只能处理单个客户端
            // Server(sockfd, addr);

            // v1 -- 多进程
            // Server_V1(sockfd, addr);

            // v2 -- 多线程
             Server_V2(sockfd,addr);

            // v3 -- 线程池
            //Server_V3(sockfd,addr);

        }
        _isrunning = false;
    }

    void Server_V3(int sockfd, InetAddr addr)
    {
        task_t t=std::bind(&TcpServer::Server,this,sockfd,addr);
        ThreadPool<task_t>::GetInstance()->Push(t);
    }
    void Server_V2(int sockfd, InetAddr addr)
    {
        pthread_t pid;
        PthreadDate *date = new PthreadDate(sockfd, this, addr);
        pthread_create(&pid, nullptr, Excute, date);
    }
    struct PthreadDate
    {
        int _sockfd;
        TcpServer *_self;
        InetAddr _addr;
        PthreadDate(int sockfd, TcpServer *self, const InetAddr &addr)
            : _sockfd(sockfd),
              _self(self),
              _addr(addr)
        {
        }
    };
    static void *Excute(void *args)
    {
        pthread_detach(pthread_self());

        // static 问题1;无法调用类内方法 2:sockfd变量看不到

        PthreadDate *date = static_cast<PthreadDate *>(args);
        date->_self->Server(date->_sockfd, date->_addr);
        delete date;
        return nullptr;
    }

    void Server_V1(int sockfd, InetAddr addr)
    {
        pid_t n = fork();
        if (n == 0)
        {
            // child
            // 建议:1.防止子进程误操作 2.fd表有上限
            ::close(_listensockfd);

            if (fork() > 0)
                exit(0);

            // grandchild
            Server(sockfd, addr);
            exit(0);
        }
        // father
        ::close(sockfd); // 建议 类似
        pid_t m = ::waitpid(n, nullptr, 0);
        if (m > 0)
        {
            LOG(DEBUG, "wait sucess\n");
        }
    }

    void Server(int sockfd, InetAddr addr)
    {
        while (true)
        {
            // 收消息 -- 类似读写来收发
            char buffer[1024];
            ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // 发
                buffer[n] = 0;

                std::string send = "[server echo]: ";
                send += buffer;
                if (::write(sockfd, send.c_str(), send.size()) < 0)
                {
                    std::cerr << "write fail" << std::endl;
                }
            }
            else if (n == 0)
            {
                LOG(DEBUG, "%s quit\n", addr.User().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read fail\n");
                break;
            }
        }
    }

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

//ServerMian.cc
#include<memory>

#include"Server.hpp"

int main(int args,char* argv[])
{
    if(args!=2)
    {
        std::cerr<<"./Server Localport"<<std::endl;
    }
    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> sr=std::make_unique<TcpServer>(port);
    
    sr->Init();
    sr->Start();
    return 0;
}

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

int main(int args, char *argv[])
{
    if (args != 3)
    {
        std::cerr << "./Server ip sort" << std::endl;
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    // 创建
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket fail" << std::endl;
        exit(2);
    }
    std::cout << "socket sucess" << std::endl;

    // 连接 --> 不需要显示bind 但一定要自己的Ip和port来填充
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    ::inet_pton(AF_INET, ip.c_str(), &server.sin_addr);

    int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect fail" << std::endl;
        exit(2);
    }
    std::cout << "connect success" << std::endl;

    // 操作
    while (true)
    {
        // 发
        std::string message;
        std::cout << "Enter: ";
        getline(std::cin, message);
        if (::write(sockfd, message.c_str(), message.size()) < 0)
        {
            std::cerr << "wirte fail" << std::endl;
        }
        // 收
        char buffer[1024];
        n = ::read(sockfd, buffer, sizeof(buffer)-1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
        else
        {
            break;
        }
    }
    return 0;
}

在设计Server时,共有4中版本:单进程,多进程,多线程,线程池

其中最优的是:多线程的版本~ 

2命令版本 

 实现一个业务:让client通过输入指令,让server端收到并执行该指令

//Server.hpp
#pragma once
#include <iostream>
#include <memory>
#include <cstring>
#include <string>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>


#include "InetAddr.hpp"
#include "Log.hpp"

enum
{
    SOCK_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    WAIT_ERROR
};

const static int gsockfd = -1;
const static int gport = 8888;
const static int gbacklog = 8;

using comm_t = std::function<void(int,InetAddr)>;

class TcpServer
{
public:
    TcpServer(comm_t command,const uint16_t port = gport)
        :_command(command), 
        _port(port),
          _listensockfd(gsockfd),
          _isrunning(false)
    {
    }

    void Init()
    {
        // 创建
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(FATAL, "socket fail\n");
            exit(SOCK_ERROR);
        }
        LOG(DEBUG, "socket sucess , sockfd: %d\n", _listensockfd);
        // 绑定
        struct sockaddr_in perr;
        memset(&perr, 0, sizeof(perr));
        perr.sin_family = AF_INET;
        perr.sin_port = htons(_port);
        perr.sin_addr.s_addr = INADDR_ANY; // 不推荐绑定固定IP
        if (::bind(_listensockfd, (struct sockaddr *)&perr, sizeof(perr)) < 0)
        {
            LOG(ERROR, "bind fail\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind sucess\n");
        // 监听 Tcp是面向连接的,未来有连接源源不断来 --> 老板模式
        if (::listen(_listensockfd, gbacklog) < 0)
        {
            LOG(ERROR, "listen fail\n");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen sucess\n");
    }

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取新连接 --> 与Udp的recvfrom类似
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 这里的sockfd 与 _listensockfd 有什么区别?
            // _listensockfd 只协助accept 获取新连接(每次不一定有新连接到来(阻塞))
            // sockfd 只提供服务
            int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                LOG(WARNING, "accept fail\n");
                // exit(ACCEPT_ERROR);
                continue; // 没有 继续进行连接
            }
            InetAddr addr(client);
            LOG(DEBUG, "get a line , client: %s\n", addr.User().c_str());

            // 提供服务

            // v2 -- 多线程
             Server_V2(sockfd,addr);

        }
        _isrunning = false;
    }
    void Server_V2(int sockfd, InetAddr addr)
    {
        pthread_t pid;
        PthreadDate *date = new PthreadDate(sockfd, this, addr);
        pthread_create(&pid, nullptr, Excute, date);
    }
    struct PthreadDate
    {
        int _sockfd;
        TcpServer *_self;
        InetAddr _addr;
        PthreadDate(int sockfd, TcpServer *self, const InetAddr &addr)
            : _sockfd(sockfd),
              _self(self),
              _addr(addr)
        {
        }
    };
    static void *Excute(void *args)
    {
        pthread_detach(pthread_self());

        // static 问题1;无法调用类内方法 2:sockfd变量看不到

        PthreadDate *date = static_cast<PthreadDate *>(args);
        date->_self->_command(date->_sockfd, date->_addr);
        delete date;
        return nullptr;
    }

private:
    comm_t _command;
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

//Command.hpp
#pragma once

#include <cstdio>
#include "Server.hpp"

class command
{
public:
    command() {}
    ~command() {}
    std::string Excute(const std::string &buffer)
    {
        std::string result;
        // 函数内部创建子进程,将command写进管道内 父进程通过fp读取管道内的数据
        FILE *fp = popen(buffer.c_str(), "r");
        if (fp)
        {
            // 打开成功
            char line[1024];
            while (fgets(line, sizeof(line), fp))
            {
                result += line;
            }
            return result.empty() ? "sucess" : result;//touch 返回无结果
        }
        return "popen fail";
    }
    void servert(int sockfd, InetAddr addr)
    {
        while (true)
        {
            // 收消息 -- 类似读写来收发
            char CommandBuf[1024];
            ssize_t n = ::recv(sockfd, CommandBuf, sizeof(CommandBuf) - 1, 0);
            if (n > 0)
            {
                CommandBuf[n] = 0;
                LOG(DEBUG, "get command from client %s command: %s\n", addr.User().c_str(), CommandBuf);

                std::string result = Excute(CommandBuf);
                ::send(sockfd, result.c_str(), result.size(), 0);
            }
            else if (n == 0)
            {
                LOG(DEBUG, "%s quit\n", addr.User().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read fail\n");
                break;
            }
        }
    }
};

//ServerMain.cc
#include <memory>
#include <functional>
#include "Command.hpp"
int main(int args, char *argv[])
{
    if (args != 2)
    {
        std::cerr << "./Server Localport" << std::endl;
    }
    uint16_t port = std::stoi(argv[1]);

    command c;
    std::unique_ptr<TcpServer> sr = std::make_unique<TcpServer>(std::bind(
    &command::servert,
    &c, std::placeholders::_1, std::placeholders::_2),
    port);

    sr->Init();
    sr->Start();
    return 0;
}

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

int main(int args, char *argv[])
{
    if (args != 3)
    {
        std::cerr << "./Server ip sort" << std::endl;
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    // 创建
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket fail" << std::endl;
        exit(2);
    }
    std::cout << "socket sucess" << std::endl;

    // 连接 --> 不需要显示bind 但一定要自己的Ip和port来填充
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    ::inet_pton(AF_INET, ip.c_str(), &server.sin_addr);

    int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        std::cerr << "connect fail" << std::endl;
        exit(2);
    }
    std::cout << "connect success" << std::endl;

    // 操作
    while (true)
    {
        // 发
        std::string message;
        std::cout << "Enter: ";
        getline(std::cin, message);
        if (::write(sockfd, message.c_str(), message.size()) < 0)
        {
            std::cerr << "wirte fail" << std::endl;
        }
        // 收
        char buffer[1024];
        n = ::read(sockfd, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
        else
        {
            std::cerr << "read fail" << std::endl;
            break;
        }
    }
    ::close(sockfd);
    return 0;
}

更严谨的设计:要把危险的指令,如rm -f /* 等等指令给禁掉(或者添加白名单)

八网络命令

Ping 命令

主要作用:

1、用来检测网络的连通情况和分析网络速度

2、根据域名得到服务器IP

3、根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量

$ ping -c 1 www.qq.com 
PING ins-r23tsuuf.ias.tencent-cloud.net (121.14.77.221) 56(84) bytes of data.
64 bytes from 121.14.77.221 (121.14.77.221): icmp_seq=1 ttl=128 time=44.0 ms

--- ins-r23tsuuf.ias.tencent-cloud.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 44.029/44.029/44.029/0.000 ms

netstat命令 

语法: netstat [选项]

功能: 查看网络状态

常用选项:

• a (all)显示所有选项, 默认不显示 LISTEN 相关

• u (udp)仅显示 udp 相关选项

• p 显示建立相关链接的程序名

• n 拒绝显示别名, 能显示数字的全部转化成数字

• t (tcp)仅显示 tcp 相关选项
• l 仅列出有在 Listen (监听) 的服務状态

• p 显示建立相关链接的程序名

• n 拒绝显示别名, 能显示数字的全部转化成数字

// 每个 1s 执行一次 netstat -tlnp
$ watch -n 1 netstat -tlnp

pidof命令

在查看服务器的进程 id 时非常方便.
语法: pidof [进程名]
功能: 通过进程名, 查看进程 id

$ ps axj | head -1 && ps ajx | grep tcp_server
PPID PID PGID SID TTY TPGID STAT UID TIME
COMMAND
2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002
0:00 ./tcp_server 8888

$ pidof tcp_server
2958285
$ pidof tcp_server | xargs kill -9 //直接kill掉tcp_server

九不同环境下的通信

在进行通信之前:如果是云服务器,一定要在后台开发端口号!

腾讯云:

阿里云:

1Windows作为client访问Linux(UDP)

// UdpServer.cc
#include <iostream>
#include <string>
#include <memory>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

const static uint16_t defaultport = 8888;
const static int defaultfd = -1;
const static int defaultsize = 1024;

enum
{
    Usage_Err = 1,
    Socket_Err,
    Bind_Err
};

class UdpServer
{
public:
    UdpServer(uint16_t port = defaultport)
        : _port(port), _sockfd(defaultfd)
    {
    }
    void Init()
    {
        // 1. 创建socket,就是创建了文件细节
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            exit(Socket_Err);
        }

        // 2. 绑定,指定网络信息
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // memset
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY; // 1. 4字节IP 2. 变成网络序列

        // 结构体填完,设置到内核中了吗??没有
        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n != 0)
        {
            exit(Bind_Err);
        }
    }
    void Start()
    {
        // 服务器永远不退出
        char buffer[defaultsize];
        for (;;)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); // 不能乱写
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                uint16_t clientport = ntohs(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr);
                std::string prefix = clientip + ":" + std::to_string(clientport);
                buffer[n] = 0;
                std::cout << prefix << "# " << buffer << std::endl;


                std::string echo = buffer;
                echo += "[udp server echo message]";
                sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    uint16_t _port;
    int _sockfd;
};

void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_port\n"
              << std::endl;
}

// ./udp_server 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }

    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);
    usvr->Init();
    usvr->Start();

    return 0;
}

//UdpClient.cc
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>

//屏蔽不安全
#pragma warning(disable : 4996)

#pragma comment(lib, "ws2_32.lib")

std::string serverip = "";  // 填写你的云服务器ip
uint16_t serverport = 8888; // 填写你的云服务开放的端口号

int main()
{
    //-------------
    //找库版本
    WSADATA wsd;
    WSAStartup(MAKEWORD(2, 2), &wsd);
    //-------------
    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());

    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == SOCKET_ERROR)
    {
        std::cout << "socker error" << std::endl;
        return 1;
    }
    std::string message;
    char buffer[1024];
    while (true)
    {
        std::cout << "Please Enter@ ";
        std::getline(std::cin, message);
        if(message.empty()) continue;
        sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr *)&server, sizeof(server));
        struct sockaddr_in temp;
        int len = sizeof(temp);
        int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }
    //------------------
    closesocket(sockfd);
    WSACleanup();
    //------------------
    return 0;
}

2Linux作为clinet访问Windows(TCP) 

//TcpServer.cc
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>

const static int default_backlog = 6;

enum
{
    Usage_Err = 1,
    Socket_Err,
    Bind_Err,
    Listen_Err
};

#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)

class TcpServer
{
public:
    TcpServer(uint16_t port) : _port(port), _isrunning(false)
    {
    }
    // 都是固定套路
    void Init()
    {
        // 1. 创建socket, file fd, 本质是文件
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            exit(0);
        }
        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

        // 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 = htonl(INADDR_ANY);

        // 2.1 bind
        if (bind(_listensock, CONV(&local), sizeof(local)) != 0)
        {
            exit(Bind_Err);
        }

        // 3. 设置socket为监听状态,tcp特有的
        if (listen(_listensock, default_backlog) != 0)
        {
            exit(Listen_Err);
        }
    }
    void ProcessConnection(int sockfd, struct sockaddr_in &peer)
    {
        uint16_t clientport = ntohs(peer.sin_port);
        std::string clientip = inet_ntoa(peer.sin_addr);
        std::string prefix = clientip + ":" + std::to_string(clientport);
        std::cout << "get a new connection, info is : " << prefix << std::endl;
        while (true)
        {
            char inbuffer[1024];
            ssize_t s = ::read(sockfd, inbuffer, sizeof(inbuffer)-1);
            if(s > 0)
            {
                inbuffer[s] = 0;
                std::cout << prefix << "# " << inbuffer << std::endl;
                std::string echo = inbuffer;
                echo += "[tcp server echo message]";
                write(sockfd, echo.c_str(), echo.size());
            }
            else
            {
                std::cout << prefix << " client quit" << std::endl;
                break;
            }
        }
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 4. 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensock, CONV(&peer), &len);
            if (sockfd < 0)
            {
                continue;
            }
            ProcessConnection(sockfd, peer);
        }
    }
    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensock; // TODO
    bool _isrunning;
};

using namespace std;

void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_port\n"
              << std::endl;
}
// ./tcp_server 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    uint16_t port = stoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);
    tsvr->Init();
    tsvr->Start();

    return 0;
}

//TcpClient.cc

#include <iostream>
#include <string>
//---------------------------------
//库版本
#include <winsock2.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
//---------------------------------
std::string serverip = "";  // 填写你的云服务器ip
uint16_t serverport = 8888; // 填写你的云服务开放的端口号

int main()
{
    //-----------------------
    //WSADATA: 保存初始化 Winsock 库时返回的信息
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0)
    {
        std::cerr << "WSAStartup failed: " << result << std::endl;
        return 1;
    }
    //-----------------------

    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET)
    {
        std::cerr << "socket failed" << std::endl;
        WSACleanup();
        return 1;
    }

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(serverport);                  // 替换为服务器端口
    serverAddr.sin_addr.s_addr = inet_addr(serverip.c_str()); // 替换为服务器IP地址

    result = connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
    if (result == SOCKET_ERROR)
    {
        std::cerr << "connect failed" << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }
    while (true)
    {
        std::string message;
        std::cout << "Please Enter@ ";
        std::getline(std::cin, message);
        if(message.empty()) continue;
        send(clientSocket, message.c_str(), message.size(), 0);

        char buffer[1024] = {0};
        int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
        if (bytesReceived > 0)
        {
            buffer[bytesReceived] = '\0'; // 确保字符串以 null 结尾
            std::cout << "Received from server: " << buffer << std::endl;
        }
        else
        {
            std::cerr << "recv failed" << std::endl;
        }
    }
    //------------------------
    closesocket(clientSocket);
    WSACleanup();
    //------------------------
    return 0;
}

 十connect的断线重连

设计时要基于状态机来实现逻辑

    void Excute()
    {
        while (true)
        {
            switch (_connect.ConnectStatus())
            {
            case status::NEW: 
                _connect.Connect();
                break;
            case status::CONNECTED:
                _connect.Process();
                break;
            case status::DISCONNECT:
                _connect.Reconnect();
                break;
            case status::CLOSED:
                _connect.Disconnect();
                return; // 退出
            default:
                break;
            }
        }
    }

 根据它来进行设计clinet

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

enum class status
{
    NEW,
    CONNECTING,
    CONNECTED,
    DISCONNECT, // 断线重连
    CLOSED      // 无法连接
};

enum
{
    SOCKETERROR = 1,
    CONNECTERROR,
    ERROR
};

const static int DefaultSockfd = -1;
const static int DefaultRetryInterval = 1;
const static int DefaultMaxRetries = 5;

class Client_Connect
{
public:
    Client_Connect(std::string ip, uint16_t port)
        : _ip(ip), _port(port), _sockfd(DefaultSockfd), _status(status::NEW), _retry_interval(DefaultRetryInterval), _max_retries(DefaultMaxRetries)
    {
    }
    ~Client_Connect()
    {
    }
    status ConnectStatus()
    {
        return _status;
    }
    void Connect() //正常连接
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            std::cerr << "create sockfd fail" << std::endl;
            exit(SOCKETERROR);
        }
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &server.sin_addr);
        int n = connect(_sockfd, (const sockaddr *)&server, sizeof(server));
        if (n < 0)
        {
            // 1.conncet失败,不代表创建socket失败(先关闭sockfd)
            Disconnect();
            // 2.设置状态 -- 没有连接成功(重连)
            _status = status::DISCONNECT;
            // 3.返回
            return;
        }
        _status = status::CONNECTED;
    }

    void Reconnect() //重新连接
    {
        _status = status::CONNECTING;
        int cnt = 0;
        while (true)
        {
            Connect();
            if (_status == status::CONNECTED)
            {
                break;
            }
            cnt++;
            if (cnt > _max_retries)
            {
                // 连接失败进行关闭
                _status = status::CLOSED;
                std::cout << "connect fail" << std::endl;
                break;
            }
            std::cout << "reconnecting cnt:" << cnt << std::endl;
            sleep(_retry_interval);
        }
    }

    void Process() // IO通信
    {
        while (true)
        {
            std::string message = "hello server";
            ssize_t n = send(_sockfd, message.c_str(), message.size(), 0);
            if (n > 0)
            {
                char buffer[1024];
                ssize_t m = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
                if (m > 0)
                {
                    buffer[m] = 0;
                    std::cout << buffer << std::endl;
                }
                else
                {
                    _status = status::DISCONNECT;
                    break;
                }
            }
            else
            {
                std::cerr << "send fail" << std::endl;
                _status = status::DISCONNECT;
                // _status = status::CLOSED;
                break;
            }
            sleep(1);
        }
    }
    
    void Disconnect() //关闭连接 
    {
        if (_sockfd > DefaultSockfd)
        {
            close(_sockfd);
            _sockfd = DefaultSockfd;
            _status = status::CLOSED;
        }
    }

private:
    std::string _ip;
    uint16_t _port;
    int _sockfd;

    status _status;
    int _retry_interval;
    int _max_retries;
};

class TcpClient
{
public:
    TcpClient(std::string ip, uint16_t port)
        : _connect(ip, port)
    {
    }
    ~TcpClient()
    {
    }
    void Excute()
    {
        while (true)
        {
            switch (_connect.ConnectStatus())
            {
            case status::NEW:
                _connect.Connect();
                break;
            case status::CONNECTED:
                _connect.Process();
                break;
            case status::DISCONNECT:
                _connect.Reconnect();
                break;
            case status::CLOSED:
                _connect.Disconnect();
                return; // 退出
            default:
                break;
            }
        }
    }

private:
    Client_Connect _connect;
};

void Usage(std::string proc)
{
    std::cout << "Usage : " << proc << " ip port" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(ERROR);
    }
    std::string Ip = argv[1];
    uint16_t Port = std::stoi(argv[2]);

    // 可以使用多线程来进行重连逻辑
    std::unique_ptr<TcpClient> tsvr = std::make_unique<TcpClient>(Ip, Port);
    tsvr->Excute();
    return 0;
}

服务器可以用上面的不同环境下Linux作为server的测试代码

 现象

以上便是全部内容,有错误欢迎指正,感谢~

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

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

相关文章

【软件设计师真题】下午题第一大题---数据流图设计

解答数据流图的题目关键在于细心。 考试时一定要仔细阅读题目说明和给出的流程图。另外&#xff0c;解题时要懂得将说明和流程图进行对照&#xff0c;将父图和子图进行对照&#xff0c;切忌按照常识来猜测。同时应按照一定顺序考虑问题&#xff0c;以防遗漏&#xff0c;比如可以…

综合案例-数据可视化-地图

一、pyecharts—地图快速入门 假设我们要将6个地区的某种数量在地图上标注出来&#xff0c;首先导入pyecharts包内地图相关模块&#xff0c;然后准备地图数据&#xff08;数据类型是列表&#xff0c;列表的元素类型为元组&#xff09;&#xff0c;然后把准备好的数据添加进地图…

Java笔试面试题AI答之单元测试JUnit(1)

文章目录 1. 什么是JUnit&#xff1f;2. JUnit的重要特征是什么&#xff1f;3. 什么是单元测试案例&#xff1f;单元测试的特点&#xff1a;单元测试的好处&#xff1a;编写单元测试案例的步骤&#xff1a; 4. 何时在开发周期中编写单元测试&#xff1f;5. 为什么不直接使用Sys…

【鸿蒙开发从0到1 day07】

ArkTS-基础语法 一.开发环境1.下载Dev Eco Stdio2.下载chinese插件3.基础入门4.输出语句5.注释 二.3种常见的数据类型1.数据类型2.储存数据(1)变量:专门用来存储数据的容器(可变)(2)常量(3)命名规则 三.数组四.函数1.无参函数2.有参函数3.返回值4.箭头函数 五.对象六.总结 一.开…

HBuilderx中vue页面引用scss样式

scss为css样式的预编译器&#xff0c;引入了变量、嵌入、混合、集成、引入等功能&#xff0c;相对于css样式&#xff0c;实现了样式的编程&#xff0c;具有更灵活的样式编写模式。 那么在HBuilderx中&#xff0c;“.vue”格式页面如何调用scss样式呢&#xff1f;详细如下&#…

每日一练7:简写单词(含链接)

1.链接 简写单词_牛客题霸_牛客网 2.题目 3.代码1&#xff08;错误经验&#xff09; #include <iostream> #include <string> using namespace std;int main() {string s;string ret;int count 0;while(cin >> s)for(auto a : s){if(count 0){if( a <…

字符串操作的高效工具—正则表达式

字符串操作的高效工具—正则表达式 一 . 概述1.1 认识正则表达式1.2 正则表达式体验案例 二 . 使用2.1 字符类2.2 逻辑运算符2.3 预定义字符2.4 数量词2.5 分组括号2.6 字符串中常用含有正则表达式的方法 正则表达式&#xff08;Regular Expression&#xff0c;简称 regex 或 r…

xcode打包报错之 zip fail / copy fail

在xcode中archive打包中&#xff0c;遇到提示zip fail或者copy fail&#xff0c;此时请清理你电脑的缓存文件了&#xff0c;但凡遇到这两种报错&#xff0c;是编译时运行内存不够导致的&#xff0c;请及时查看并清理你的电脑。 如下&#xff1a; 将对应的缓存或者垃圾清理&am…

春日美食汇:基于SpringBoot的订餐平台

2 系统关键技术 2.1JSP技术 JSP(Java脚本页面)是Sun和许多参与建立的公司所提倡的动态web技术。将Java程序添加到传统的web页面HTML文件()。htm,。Html) [1]。 JSP这种能够独立使用的编程语言可以嵌入在html语言里面运行&#xff0c;正因为JSP参照了许多编程语言的特性&#xf…

C++——类与对象(二)

目录 引言 类的默认成员函数 构造函数 1.构造函数的概念 2.注意事项 初始化列表 1.初始化列表的概念 2.注意事项 析构函数 1.析构函数的概念 2.注意事项 拷贝构造函数 1.拷贝构造函数的概念 2.注意事项 运算符重载 1.运算符重载的概念 2.注意事项 赋值运算符…

食家巷桃酥:一口酥脆,回味悠长

在众多的传统糕点中&#xff0c;食家巷桃酥以其独特的魅力脱颖而出&#xff0c;成为了许多人心中的美味记忆。初见食家巷桃酥&#xff0c;那金黄的色泽便让人食欲大增。每一块桃酥都像是一件小小的艺术品&#xff0c;散发着诱人的香气。它的外形饱满&#xff0c;边缘微微隆起&a…

JVM 调优篇1 类的加载器与加载过程

一 基本知识 1.1 JIT&AOT JIT: Just Time compilation 即时编译器 在程序运行时将字节码或中间表示转换为机器代码。 AOT: Ahead of Tmie Compilation &#xff1a; 预编译 在程序运行之前将高级语言代码完全编译成机器代码。 1.2 字面量和符号引用* 字面量&am…

安卓下载工具箱_3.8.1/去浏览器跳转登录就是会员

下载工具箱app是一款支持56种下载协议的安卓万能下载工具,下载工具箱最新版主要提供网络视频解析下载,资源嗅探下载,以及本地视频扫描提取功能,同时提供不常见的视频编辑功能,例如:智能清洗,高帧率/码率等 链接: https://pan.baidu.com/s/1pW0CgmGv9bmzGkBA4rV2oQ?pwdxdby …

群晖NAS安装alist

官方文档&#xff1a;https://alist.nn.ci/zh NAS参考&#xff1a;https://post.smzdm.com/p/an9zpvev/ Windows安装&#xff1a;alist-windows-amd64.zip 手机参考&#xff1a;https://studylessshape.github.io/post/note/alist-install-for-android/ 质感文件可以不用安&…

Science|癌症中三级淋巴结构的免疫调节作用与治疗潜力|顶刊精析·24-09-08

小罗碎碎念 Science文献精析 今天精析的这一篇综述&#xff0c;于2022-01-07发表于Science&#xff0c;主要讨论了癌症中的三级淋巴结构&#xff08;Tertiary Lymphoid Structures, TLS&#xff09;及其在肿瘤免疫反应中的作用。 作者类型作者姓名单位名称&#xff08;中文&…

前端学习笔记-Web APls篇-04

Dom节点&移动端滑动 1.日期对象 日期对象:用来表示时间的对象作用:可以得到当前系统时间 1.1实例化 在代码中发现了 new 关键字时&#xff0c;一般将这个操作称为实例化 创建一个时间对象并获取时间 获得当前时间 获得指定时间 1.2时间对象方法 使用场景&#xff1a…

图的广度优先搜索(BFS)算法与邻接矩阵表示

图的广度优先搜索(BFS)算法与邻接矩阵表示 1. 图的表示2. 广度优先搜索(BFS)BFS 算法步骤:3. 使用邻接矩阵的 BFS 实现4. 运行时间分析时间复杂度:空间复杂度:5. BFS 使用邻接列表与邻接矩阵的比较BFS 在邻接列表上的运行时间:6. 结论在计算机科学中,图是一种重要的数…

Mybatis-设计模式总结

1、Builder模式 Builder模式的定义是“将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。”&#xff0c;它属于创建类模式&#xff0c;一般来说&#xff0c;如果一个对象的构建比较复杂&#xff0c;超出了构造函数所能包含的范围&#x…

Spring05——注解开发定义bean、Spring纯注解开发模式

19-注解开发定义bean Spring的IOC/DI对应的配置开发前面的内容已经讲解完成&#xff0c;但是使用起来相对来说还是比较复杂的&#xff0c;复杂的地方在配置文件。 Spring到底是如何简化代码开发的呢? 要想真正简化开发&#xff0c;就需要用到Spring的注解开发&#xff0c;Spr…

CTFHub技能树-信息泄露-SVN泄漏

目录 Git与SVN的区别 漏洞产生的原因 漏洞危害 修复建议 工具下载 解题过程 当开发人员使用 SVN 进行版本控制&#xff0c;对站点自动部署。如果配置不当,可能会将.svn文件夹直接部署到线上环境。这就引起了 SVN 泄露漏洞。 Git与SVN的区别 Git 是分布式版本控制系统&a…