Tcp/Udp(网络套接字,服务器) -- Linux/Windows

news2024/11/26 17:41:53

目录

网络传输本质

认识端口号

认识协议

认识TCP协议

认识UDP协议

网络字节序

socket编程接口

socket 常见API

sockaddr结构

sockaddr 结构

sockaddr_in 结构

in_addr结构

编写代码前的小tip(重点)

UDP套接字(网络通信)代码

Linux

Log.hpp

makefile

udpServer.cc

udpClient.cc

Windows

TCP套接字(网络通信)代码

Linux

Log.hpp

makefile

tcpServer.cc

tcpClient.cc

Windows


C语言总结在这常见八大排序在这

作者和朋友建立的社区:非科班转码社区-CSDN社区云💖💛💙

期待hxd的支持哈🎉 🎉 🎉

最后是打鸡血环节:想多了都是问题,做多了都是答案🚀 🚀 🚀

最近作者和好友建立了一个公众号

公众号介绍:

专注于自学编程领域。由USTC、WHU、SDU等高校学生、ACM竞赛选手、CSDN万粉博主、双非上岸BAT学长原创。分享业内资讯、硬核原创资源、职业规划等,和大家一起努力、成长。(二维码在文章底部哈!

网络传输本质

主机间在通信的本质其实是两主机的两个进程在相互通信。

IP地址可以完成主机与主机间的通信,而主机上各自的通信进程,才是发送和接收数据的一方。

我们在网络通信的时候,那便是不能只考虑两台主机交换数据,还要考虑到进程和进程,本质上讲,进行数据交互的时候,是用户和用户在进行交互,用户的身份,通常是程序的体现!(程序一定是在运行中 —— 进程)

那么 IP —— 确保主机的唯一性

Port(端口号) —— 确保该主机上的进程的唯一性

--》IP + Port 标识互联网中唯一的进程!

我们把这种IP+端口(Pork)称为socket(插座,代表的是配套的(插头配插座))(属于POSIX的进程间通信,跨网络的)。网络通信的本质也就是进程间通信。
我们知道进程间通信的本质就是要看到同一份资源,在网络中这份资源就是网络。

认识端口号

端口号 (port) 是传输层协议的内容:
  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

传输层协议 (TCP UDP) 的数据段中有两个端口号, 分别叫做源端口号和目的端口号, 还有两个是源IP和目的IP,源端口和源IP是一对socket,另一个也是一对,这两个socket对一对接我们就知道哪两台主机的哪两个服务要通信就知道了
PS:OS里面有两张重要的哈希表,一个是PID做key的,一个是端口号做key的。

认识协议

认识TCP协议

此处我们先对 TCP(Transmission Control Protocol 传输控制协议 ) 有一个直观的认识 ; 后面我们再详细讨论 TCP 的一些细节问题。
  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

认识UDP协议

此处我们先对 TCP(Transmission Control Protocol 传输控制协议 ) 有一个直观的认识 ; 后面我们再详细讨论 TCP 的一些细节问题。

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

有连接:比如打电话,要拿起手机拨通对方电话号码然后两个人确定对方能听到这些准备工作就是建立连接的过程。
无连接:比如发邮件,我只要知道你的邮箱就可以直接给你发了,并不需要你的许可。
可靠/不可靠传输:
并不是靠不靠谱的意思,更多是指通信特征,没有任何主观色彩。
可靠传输:可靠传输就比如保证按需到达,保证数据丢包的时候重传,比如数据重复时去重等等。
不可靠传输:我直接把数据发给你,你爱收不收,我什么都不管。
所以不要去衡量tcp udp哪一个好了,只有哪一个更合适。就比如tcp要维持可靠传输就肯定要做更多的工作。而udp就是什么都不管,上层把数据丢给我我直接就是丢给下层(添加自己的报头告诉对方谁发的,什么时候发的...),但是就说明我不需要做更多的工作,我一定是足够简单的。这样不同的场景就需要不同的通信方式,就比如支付的时候就一定要有保证(保证可靠传输),就要用tcp。比如看视频,看直播,如果同tcp这样复杂的协议,就会导致直播转播方的压力倍增,如果网络卡顿,那就是直接卡掉线要重新建立连接,所以直接使用udp协议来进行,不用有连接,直接有数据包发给用户,网好就看,网不好就是卡顿。

网络字节序

我们已经知道 , 内存中的多字节数据相对于内存地址有大端和小端之分 , 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分 . 那么如何定义网络数据流的地址呢 ?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出 ;
接收主机把从网络上接到的字节依次保存在接收缓冲区中 , 也是按内存地址从低到高的顺序保存 ;
因此 , 网络数据流的地址应这样规定 : 先发出的数据是低地址 , 后发出的数据是高地址 .
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机 , 都会按照这个 TCP/IP 规定的网络字节序来发送 / 接收数据 ;
如果当前发送主机是小端 , 就需要先将数据转成大端 ; 否则就忽略 , 直接发送即可 ;

在我们之前就学了大小端,但是我们发现一直是没有用上,大小端的问题其实就是在网络里面体现出来的,因为不同主机如果字节序列不同,数据发送过来就有可能将大端机的数据小端机去接受就可能解释数据的时候就解释反了!所以为了处理这个问题,就规定网络数据都是大端序列(默认),也就是说主机发送的数据要去主动或者被动去转换为大端序列。(为什么网络发送数据采用大端序列,有一种猜测是:大端是把高权值数据放在低地址出,而发送数据又是从低地址发的,所以如果发送0x1234,那么拿数据的时候就是先拿高权值的数据1,再234,这样就方便拿数据!
(sum= 1* 10+ 2* 10 + 3* 10 +4)

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。 

socket编程接口

socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
三个参数,一个使用ipv4 第二个是用户数据报通信,第三个0,这样的只有一个就是udp
往后只用这两个套接字类型,域就ipv4(AF_INET),第三个参数为0
// 绑定端口号 (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);
accept 返回一个新的socket套接字
(为什么会有两类套接字)
第二个参数就是让服务器知道是谁连的我,第三个是长度
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockaddr结构

socket API 是一层抽象的网络编程接口 , 适用于各种底层网络协议 , IPv4 IPv6, 以及后面要讲的 UNIX Domain Socket,然而 各种网络协议的地址格式并不相同。

  • IPv4IPv6的地址格式定义在netinet/in.h,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32IP地址;
  • IPv4IPv6地址类型分别定义为常数AF_INETAF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容;
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

我们的套接字通信(API),设计的时候除了要支持网络通信还要支持本地通信,所以网络套接字在网络中划分为两类,一类是网络通信的我们称为跨网络套接字,一般也就是套接字(默认就是网络通信)。还有一种是域间套接字(代表的就是在本地直接通信),也称为双向管道(之前文件的管道是单向的)(目前不讲,因为相比于网络的API没有端口号没有ip的概念会更简单,而且又是管道)。但是设计者为了把这两种通信方式统一起来并且以文件的方案让我们去使用,但是因为本地通信和网络通信要的数据是不一样的(本地不要ip,端口,只是看到同一份资源就可以了),所以设计者就设计了一种抽象结构,in-->inet 表示网络,表示ipv4 ipv6这样的协议,un-->unix简写,代表我们的域间套接字。为了统一可以调用两种结构,就肯定不能是他们两个,所以就有了 struct sockaddr,这个类型里其他有效字段基本没有,但是他的第一个字段,前16位都是一样的,当你传后两种的时候,要把他们两个的类型强转为 struct sockaddr,然后 struct sockaddr 并不知道你传的是哪个,但是没关系,直接拿着前165位去匹配,如果他是AF_INET,那么就可以把他当作(内部强转为struct sockaddr_in,再去用这个结构体去访问内部数据,域间通信一样)是网络通信(就提端口号和ip),是AF_UNIX就是域间通信(就提路径),这就像是多态。

sockaddr 结构

sockaddr_in 结构

 

虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时 , 使用的数据结构是 sockaddr_in; 这个结构里主要有三部分信息: 地址类型 , 端口号 , IP 地址。

in_addr结构

in_addr用来表示一个IPv4IP地址. 其实就是一个32位的整数。

编写代码前的小tip(重点)

1. ifconfig,如果使用云服务器,那么看见的是内网ip,如果是虚拟机就是公网ip,绑定这个就可以。 

2. 关于端口号,不要绑定0-1023以前的,服务器中的端口号分配是有他自己的规则的,比如http是80,https是43,Mysql对应的网络服务是3306等。

3. netstat 查看网络服务 

允许远端的任何主机的任何端口发消息 (0.0.0.0)

4. recvfrom(sendto)(udp特有的)(定长数据) 最后两个参数是输出型参数,是谁(客户端)给你发的消息和长度,会把客户端信息保存到RmtPort(远端)中 。

5. 127.0.0.1:本地环回

客户端发的消息经过网络协议栈,但是不往网络里发,直接到了网络协议栈最底部,然后再由最底部向上交付,再交付给另一个进程他对应的缓冲区里面,让他读到消息。所以一般(127.0.0.1)就代表本主机。

(PS:如果要把写的客户端发给别人就makefile里面静态编译)   

6. sz name 可以把文件发到桌面

7. rz -e 可以把桌面的文件放到linux中

8. (这点可后面再看)直接客户端用的 server_ip 是云服务器的就可以直接向server发消息

9. (这点可后面再看)作为一个udpserver,那么他是一个网络服务,那么我们是不是应该让linux作为服务端,windows作为客户端。也就是说把我们的客户端写在windows下,让我们的linux和windows直接通信。其实,windows和linux的套接字接口是一样的!只有开头一点点不一样!

首先加上这个库

然后初始化启动信息和创建套接字
直接调用WSAStarup,这就是相当于初始化我们的套接字资源,只不过是库去初始化的,然后传入套接字的版本信息,然后初始话的信息写道上面的date。

创建套接字就是把原来代码的这个地方改了就可以了 

然后就是windows下没有命令行就把之前要用的port和ip直接定义出来 

最后就是注意加一个这个 

然后就可以了!然后就可以直接把.exe文件发给别人了!

然后是一些零零散散的函数

UDP套接字(网络通信)代码

Linux

Log.hpp

#pragma once
#include <iostream>
#include <cstdio>
#include <string>

void LogMessage(std::string ip, __uint16_t port, std::string message = "nullptr")
{
    std::cout << "[ip:" << ip << "][port:" << port << "]"
              << ": " << message << std::endl;
}

makefile

.PHONY:all
all:udpClient udpServer
	
udpClient:udpClient.cc
	g++ -o $@ $^ -std=c++11  -lpthread
udpServer:udpServer.cc
	g++ -o $@ $^ -std=c++11  -lpthread

.PHONY:clean
clean:
	rm -rf udpClient udpServer

udpServer.cc


#include "Log.hpp"
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>  //
#include <sys/socket.h> //
#include <netinet/in.h> //  !  这两货从哪来 tip:inet_addr
#include <arpa/inet.h>  //  !
#include <cassert>
#include <unistd.h>
#include <ctype.h>
#include <pthread.h>

#define BUFFER_SIZE 1024

class UdpServer; //先声明

static void Usage(const std::string porc)
{
    std::cout << "Usage:\n\t" << porc << " port [ip]" << std::endl;
}

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    UdpServer *this_;

public:
    ThreadData(uint16_t port, std::string ip, int sock, UdpServer *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {
    }
};

// ./UdeServer port [ip]
class UdpServer
{
public:
    UdpServer(int port, std::string ip = "")
        : _port((uint16_t)port),
          _ip(ip),
          _socket(-1)
    {
    }
    ~UdpServer()
    {
    }

public:
    //初始化服务器
    void init()
    {
        // 1. 创建 soket 套接字 : domain type protocol
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            std::cerr << __LINE__ << ":socket err" << std::endl;
            exit(1);
        }
        // 2. 绑定网络信息,指名ip+port
        // 2.1首先填充基本信息到 struct sockaddr_in
        struct sockaddr_in local;
        bzero(&local, 0);
        //填充协议家族 域(网络通信还是本地通信)
        local.sin_family = AF_INET; // PF_INET一样的 本地通信还是网络通信
        //填充服务器对应的端口号信息,一定是会发送给对方的,_port一定会到网络中,所以htons
        local.sin_port = htons(_port);
        //服务器都有ip地址,"15,23,45,888",字符串风格的点分十进制 -> 因为每小段都是0-255所以每小段都是一个字节就可以了
        //所以要转化为 四字节ip -> uint32_t ip
        // INADDR_ANY(本质是0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法
        // inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n
        // 云服务器有一些特殊情况:
        // 禁止你bind云服务器上的任何确定IP, 只能使用INADDR_ANY,如果你是虚拟机,随意
        local.sin_addr.s_addr = _ip.empty() ? htonl(INADDR_ANY) : inet_addr(_ip.c_str());
        // 2.2bind 网络信息 因为之前的local是在用户栈创建的,是临时变量,现在要把他写入内核中
        if (bind(_socket, (sockaddr *)&local, sizeof(local)) < 0)
        {
            std::cerr << __LINE__ << "bind err" << std::endl;
            exit(2);
        }
        std::cout << "socket bind success : " << _socket << std::endl;
        std::cout << "creat server success !" << std::endl;
    }

    // 大小写转化服务
    // TCP && UDP: 支持全双工
    void transService(std::string &inbuffer,int s)
    {
        std::cout << "into transService!" << std::endl;
        // char inbuffer[BUFFER_SIZE];
        std::cout << "Uper before:" << inbuffer << std::endl;
        for (int i = 0; i < s; i++)
        {
            if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                inbuffer[i] = toupper(inbuffer[i]);
        }
        std::cout << "Uper after:" << inbuffer << std::endl;
    }

    // static void *threadRoutine(void *args)
    // {
    //     std::cout << "into thread" << std::endl;
    //     pthread_detach(pthread_self()); //设置线程分离
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     // ThreadData* td=(ThreadData*)args;
    //     //然后是线程去执行服务
    //     td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
    //     delete td;
    //     return nullptr;
    // }

    //启动服务器
    void start()
    {
        // 服务器设计的时候,服务器都是死循环
        std::cout << "开始启动服务器" << std::endl;
        std::string inbuffer;
        inbuffer.resize(1024);

        // char inbuffer[1024];
        // char outbuffer[1024];
        while (true)
        {
            struct sockaddr_in RmtPort; // RmtPort 远端
            socklen_t len = sizeof(RmtPort);
            //  UDP无连接的
            //  对方给你发了消息,你想不想给对方回消息?要的!后面的两个参数是输出型参数,代表谁给你发的
            ssize_t s = recvfrom(_socket, (void *)inbuffer.c_str(), sizeof(inbuffer) - 1, 0, //阻塞等待
                                 (struct sockaddr *)&RmtPort, &len);
            if (s > 0)
            {
                std::cout << "s>0" << std::endl;
                inbuffer[s] = '\0'; //当作字符串,因为上面-1了的就是给'\0'预留位置
            }
            else if (s == -1)
            {
                std::cout << __LINE__ << " : recvfrom warning" << std::endl;
                std::cout << strerror(errno) << std::endl; // pos
                continue;                                  //因为服务器不能退出
            }

            //到这就读取成功了
            std::string RmtPortIp = inet_ntoa(RmtPort.sin_addr);
            uint16_t RmtPortPort = RmtPort.sin_port;
            LogMessage(RmtPortIp, RmtPortPort, "server read client success");
            std::cout << "[ip:" << RmtPortIp << "][port:" << RmtPortPort << "]"
                      << ": " << inbuffer << std::endl;

            // //开始提供服务
            // ThreadData *td = new ThreadData(RmtPortPort, RmtPortIp, _socket, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, threadRoutine, (void *)td);
            // std::cout << "endl...." << std::endl;

            transService(inbuffer,s);

            sendto(_socket, inbuffer.c_str(), s, 0, (struct sockaddr *)&RmtPort, len);
            std::cout << "send to cliet[ip:" << RmtPortIp << "]"<< "[port:" << RmtPortPort << "]" << std::endl;
        }
    }

private:
    //服务器必须要有端口号信息
    uint16_t _port;
    //服务器必须要有ip地址
    std::string _ip;
    //服务器的 socket fd 信息
    int _socket;
};

int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3) //反面:argc == 2 || argc == 3
    {
        Usage(argv[0]);
        exit(3);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    UdpServer udpser(port, ip);
    udpser.init();
    udpser.start();
    return 0;
}

udpClient.cc

#include <iostream>
#include <string>
#include <strings.h>
#include <cstdio>
#include <cstdlib>
#include <cassert>
//网络四必须包
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//
#include <pthread.h>
#include <sys/socket.h>

using std::cout;
using std::endl;

void Usage(std::string name)
{
    cout << "Usage:\n\t" << name << " ip port" << endl;
}

void *recverServer(void *arg)
{
    cout<<"into thread"<<endl;
    while (true)
    {
        char buffer[1024];
        int sockfd = *(int *)arg;
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp) - 1;
        ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer),
                             0, (struct sockaddr *)&tmp, &len);
        if (s > 0)
        {
            cout << "server echo#" << buffer << endl;
        }
    }
    return nullptr;
}

// ./udpClient server_ip server_port
// 如果一个客户端要连接server必须知道server对应的ip和port
int main(int args, char *argv[])
{
    if (args != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 1. 根据传入的参数,得到要访问服务器数据
    std::string severr_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    // 2. 创建客户端
    // 2.1创建Socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);
    // 2.2 client 需不需要bind??? 需要bind,但是不需要用户自己bind,而是os自动给你bind
    // 所谓的"不需要",指的是: 不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
    // 如果我非要自己bind呢?可以!严重不推荐!
    // 所有的客户端软件 <-> 服务器 通信的时候,必须得有 client[ip:port] <-> server[ip:port]
    // 为什么呢??client很多,不能给客户端bind指定的port,port可能被别的client使用了,你的client就无法启动了
    // 那么server凭什么要bind呢??server提供的服务,必须被所有人知道!server不能随便改变!(网站名不能随意改变)
    // 2.2 填写服务器对应的信息
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(severr_ip.c_str());

    //创建线程去接受服务器的消息
    pthread_t t;
    pthread_create(&t, nullptr, recverServer, (void *)&sockfd);

    // 3. cilent 向 server 通信
    std::string buffer;
    while (true)
    {
        cout << "Please Entet: ";
        getline(std::cin, buffer);
        //发送消息给服务器
        sendto(sockfd,buffer.c_str(),buffer.size(),0,
        (const struct sockaddr*)&server,sizeof(server));
    }
    return 0;
}

Windows

额..........网上是有的,上面也已经提到了,我相信以大家的实力也不用我贴代码了吧,虽然我开始是想写的,但是我懒...或者说写这篇文章快把我整郁闷了(bug真的多),等之后再有想法了我再回来补上吧=。=。

TCP套接字(网络通信)代码

1. 创建套接字,只要是网络的,流式的,就默认是tcp
面向什么就是说要做什么之前,要先有面向的对象。比如面向连接就是做其他事情之前要先有连接。(tcp是面向连接的)

2. 然后读取和写入因为都是流式的所有就可以用write和read(tcp)

对应recvfrom和sendto是不能用的,因为这个是专门对于udp发送固定大小的报文

3. 文件描述符打开的个数是有上限的。每个进程都对应有一个文件描述符表,这个文件描述符表默认的在云服务器,非生产环境的这种机器中一般一个进程能打开的文件描述符数一般是32/64,而云服务器他的一个进程能打开的文件描述符数是10w/65535/65536,所以如果打开了不归还,就是文件描述符泄漏。(满了就无法建立新连接,这个服务器就无法对外提供服务了)

4. 我们一直是对于同一个sock进行write和read(udp也是),证明了 TCP:UDP支持全双工。

5. 如何让自己的程序只启动一次,bind端口号就是一种方法(因为你占用了别人就不能再去启动了,因为端口没有了) 。

6. 正常退出后client再连,然后ctrl+c程序异常退出,server端也可以知道client退出了,然后关闭文件描述符。

7. 注意getline结尾不会读到‘\n’。 

注意:

当运行自己服务器的时候

这个是公网ip,是云服务上的,并不是我们自己的,所以不能用这个ip去做服务器,但是别人访问我们的时候,是要用这个公网ip来访问我们,他是会转换为我们linux下的自己的内网ip,这个才是我们自己的。

所以,我们自己运行服务器的时候,也要用自己的内网ip。 

Linux

Log.hpp

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cassert>
#include <cerrno>
#include <ctime>

using std::cout;
using std::endl;

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};
//   logMessage(DEBUG, "exaple xxx", strerror(errno));
void logMessage(int level, std::string message = "unmessage")
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    FILE *out = (level == FATAL ? stderr : stdout);
    std::string errmsg = "unerr";
    if (errno != 0)
    {
        errmsg = strerror(errno);
    }
    fprintf(out, "%s | %u | line: %d | %s | %s \n",
            log_level[level], (unsigned int)time(NULL), __LINE__, message.c_str(), errmsg.c_str());
}
//#pragma once

// #include <cstdio>
// #include <ctime>
// #include <cstdarg>
// #include <cassert>
// #include <cstring>
// #include <cerrno>
// #include <stdlib.h>

// #define DEBUG 0
// #define NOTICE 1
// #define WARINING 2
// #define FATAL 3

// const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

// //logMessage(DEBUG, "exaple xxx", strerror(errno));
// void logMessage(int level, const char *format, ...)
// {
//     assert(level >= DEBUG);
//     assert(level <= FATAL);

//     char *name = getenv("USER");

//     char logInfo[1024];
//     va_list ap; // ap -> char*
//     va_start(ap, format);

//     vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);

//     va_end(ap); // ap = NULL

//     FILE *out = (level == FATAL) ? stderr : stdout;

//     fprintf(out, "%s | %u | %s | %s\n",
//             log_level[level],
//             (unsigned int)time(nullptr),
//             name == nullptr ? "unknow" : name,
//             logInfo);

//     // char *s = format;
//     // while(s){
//     //     case '%':
//     //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
//     //     break;
//     // }
// }

makefile

.PHONY:all
all:tcpClient tcpServer
	
tcpClient:tcpClient.cc
	g++ -o $@ $^ -std=c++11  
tcpServer:tcpServer.cc
	g++ -o $@ $^ -std=c++11  -lpthread

.PHONY:clean
clean:
	rm -rf tcpClient tcpServer

tcpServer.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
//
#include <sys/socket.h>
#include <sys/types.h> // 咋找这个头文件!
#include <netinet/in.h>
#include <arpa/inet.h>
//
#include "Log.hpp"
#include <pthread.h>

using std::cout;
using std::endl;

class ServerTcp;

struct ThreadDate
{
public:
    ThreadDate(int sock, std::string ip, uint16_t port, ServerTcp *portthis)
        : listensocket_(sock),
          PortIp_(ip),
          PortPt_(port),
          this_(portthis)
    {}
    int listensocket_;
    std::string PortIp_;
    uint16_t PortPt_;
    ServerTcp *this_;
};

// ./ServerTcp local_port [local_ip]
class ServerTcp
{
public:
    ServerTcp(uint16_t port, std::string ip = "")
        : listensocket_(-1),
          port_(port),
          ip_(ip)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listensocket_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listensocket_ < 0)
        {
            logMessage(FATAL, "server socket err");
            exit(-1);
        }
        logMessage(DEBUG, "server socket success!");
        // 2. bind绑定  int bind(int socket, const struct sockaddr *address,socklen_t address_len);
        // 2.1 填充服务器信息
        struct sockaddr_in local;
        bzero(&local, sizeof(local));  // 初始化
        local.sin_family = PF_INET;    // 网络通信
        local.sin_port = htons(port_); // 端口号
        local.sin_addr.s_addr = (ip_.empty() ? INADDR_ANY : local.sin_addr.s_addr = inet_addr(ip_.c_str())); // IP
        
        socklen_t len = sizeof(local);
        // 2.2 本地socket信息,写入sock_对应的 内核 区域
        int ret = bind(listensocket_, (const struct sockaddr *)&local, len);
        if (ret < 0)
        {
            logMessage(FATAL, "server bind err");
            exit(-1);
        }
        logMessage(DEBUG, "server bind success!");
        // 3. 监听socket,为何要监听呢?因为tcp是面向连接的!
        ret = listen(listensocket_, 5 /*这个后面会讲是什么*/);
        if (ret < 0)
        {
            logMessage(FATAL, "server listen err");
            exit(-1);
        }
        logMessage(DEBUG, "server listen success!");
    }

    void start()
    {
        while (true)
        {
            logMessage(DEBUG, "The server is running properly");
            // 4. 获取连接, accept 的返回值是一个新的socket fd?!!!
            // 这个fd是专门给Clien去提供服务的
            // init里面的是监听socket,是专门去招Client的
            struct sockaddr_in remotP; // 远端/客户端
            socklen_t len = sizeof(remotP);
            int serviceSock  = accept(listensocket_, (struct sockaddr *)&remotP, &len);
            if (serviceSock  < 0)
            {
                logMessage(FATAL, "server accept err");
                exit(-1);
            }
            logMessage(DEBUG, "server accept success!");
            // 4.1 获取客户端基本信息
            uint16_t ClientPort = ntohs(remotP.sin_port);
            std::string ClientIp = inet_ntoa(remotP.sin_addr);
            cout << "ClientPort: " << ClientPort << " | ClientIp: " << ClientIp << endl;
            // 5. 提供服务, echo -> 小写 -> 大写(多个版本)
            ThreadDate *td = new ThreadDate(serviceSock , ClientIp, ClientPort, this);
            pthread_t tid;
            pthread_create(&tid, NULL, threadRoutine, td);
        }
    }

    static void *threadRoutine(void *argv)
    {
        pthread_detach(pthread_self()); // 设置线程分离
        cout<<"pthread detach"<<endl;
        ThreadDate *td = static_cast<ThreadDate *>(argv);
        td->this_->transService(td->listensocket_,td->PortIp_,td->PortPt_);
        return NULL;
    }

#define BUFFER_SIZE 1024
    void transService(int sock, const std::string &clientIp, uint16_t clientPort)
    {
        std::cout << "into transService!" << std::endl;
        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); // 我们认为我们读到的都是字符串
            if (s > 0)
            {
                // read success
                inbuffer[s] = '\0';
                if (strcasecmp(inbuffer, "quit") == 0)
                {
                    logMessage(DEBUG, "client quit");
                    break;
                }
                std::cout << "Uper before:" << inbuffer << std::endl;
                // 可以进行大小写转化了
                for (int i = 0; i < s; i++)
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                        inbuffer[i] = toupper(inbuffer[i]);
                }
                std::cout << "Uper after:" << inbuffer << std::endl;
                write(sock, inbuffer, strlen(inbuffer));
            }
            else if (s == 0)
            {
                // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
                // s == 0: 代表对方关闭,client 退出
                logMessage(DEBUG, "client quit");
                break;
            }
            else
            {
                logMessage(FATAL, "read err");
                break;
            }
        }
    }

private:
    // 服务器 sockt
    int listensocket_;
    // 服务器 port
    uint16_t port_;
    // 服务器 ip
    std::string ip_;
};

void Usage(char *name)
{
    cout << name << " port [ip]" << endl;
}

// int main()
// {
// 	short port = htons(8080);
// 	std::string ip = "0.0.0.0";
	
//     	ServerTcp server(port, ip);
//     	server.init();
//     	server.start();
// 	return 0;
// }



//./ServerTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(-1);
    }
    uint16_t port = atoi(argv[1]); // 注意转一下
    std::string ip;
    if (argc == 3)
        ip = argv[2];
    ServerTcp server(port, ip);
    server.init();
    server.start();
    return 0;
}

tcpClient.cc

#include "Log.hpp"
#include <iostream>
#include <string>
#include <cstdlib>
//
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
//
#include <unistd.h>

using std::cout;
using std::endl;

bool quit = false;

class TcpClient
{
public:
    TcpClient(std::string serverIp, uint16_t serverPort)
        : socket_(-1),
          serverIp_(serverIp),
          serverPort_(serverPort)
    {
    }
    ~TcpClient()
    {
    }

public:
    void init()
    {
        // 1. 创建socket SOCK_STREAM
        socket_ = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_ < 0)
        {
            logMessage(FATAL, "client socket err");
            exit(-1);
        }
        logMessage(DEBUG, "cliennt socket success!");
        // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
        // 2.1 先填充需要连接的远端主机的基本信息            输入进来的数据是 h -》n
        struct sockaddr_in RmtPort;
        bzero(&RmtPort, sizeof(RmtPort));
        RmtPort.sin_family = AF_INET; //网络通信
        inet_aton(serverIp_.c_str(), &RmtPort.sin_addr);// hton ip
        RmtPort.sin_port = htons(serverPort_);//hton port
        // 2.2 发起请求,connect 会自动帮我们进行bind!
        int ret = connect(socket_, (const struct sockaddr *)&RmtPort, sizeof(RmtPort));
        if (ret != 0)
        {
            logMessage(FATAL, "client connect err");
            exit(-1);
        }
        logMessage(DEBUG, "cliennt connect success!");
    }

    #define READ_SIZE 1024
    void recverServer()
    {
        cout << "client start recvfrom server message !" << endl;
        std::string message;
        while (!quit)
        {
            cout << "please echo your massege # ";
            getline(std::cin, message);
            if (strcasecmp(message.c_str(), "quit") == 0)//strcasecmp 忽略大小写匹配
                quit = true;
            ssize_t s = write(socket_,message.c_str(),message.size());//把消息写给服务器
            if(s>0)//写入成功接收服务器的消息
            {
                message.resize(1024);
                ssize_t s = read(socket_,(char*)message.c_str(),READ_SIZE);//读写不是同一个缓冲区,不用担心自己写了自己读
                if(s>0)
                {
                    message[s]='\0';
                }
                cout<<"Server return# "<<message<<endl;
            }
            else
            {
                logMessage(FATAL, "client read err");
            }
        }
        close(socket_);//用完一次就关一次
    }

private:
    // client socket
    int socket_;
    // server ip
    std::string serverIp_;
    // server port
    uint16_t serverPort_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}

// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    TcpClient client(serverIp, serverPort);
    client.init();
    client.recverServer();
    return 0;
}

Windows

额..........网上是有的,上面也已经提到了,我相信以大家的实力也不用我贴代码了吧,虽然我开始是想写的,但是我懒...或者说写这篇文章快把我整郁闷了(bug真的多),等之后再有想法了我再回来补上吧=。=。

最后的最后,创作不易,希望读者三连支持💖

赠人玫瑰,手有余香💖

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

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

相关文章

算法竞赛入门【码蹄集进阶塔335题】(MT2101-2125)

算法竞赛入门【码蹄集进阶塔335题】(MT2201-2225&#xff09; 文章目录算法竞赛入门【码蹄集进阶塔335题】(MT2201-2225&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f;目录1. MT2101 竹鼠发瓜子2. MT2102 竹鼠发瓜子&#xff08;二…

Raft协议

Raft协议先行了解 总体过程速览 假设我们只使用一个节点&#xff0c;可以很容易的达成协议或者共识。 但是现在我们思考&#xff0c;假如有多个节点呢&#xff1f; 多个节点之间达成协议或者共识就叫做分布式共识。 而Raft就是一个实现分布式共识的协议。 一个节点可以有3…

数字化转型的十大好处

前言&#xff1a; 在过去的几年中&#xff0c;“适者生存”对企业来说是至关重要的。不能适应环境变化的企业&#xff0c;也将会加速被淘汰的进程。只有从数字化转型中受益的企业才能更好的参与管理和快速调整&#xff0c;这样一来&#xff0c;员工便能够在更高效、更安全的状…

黑盒测试用例设计 - 判定表法

什么是判定表&#xff1f; 判定表法也叫判定驱动法&#xff0c;是分析和表达多逻辑条件下执行不同操作的情况的工作。 应用场合&#xff1a;只要适用于多条件的内容组合与结果分析 它由以下几个内容组成&#xff1a; 条件桩&#xff08;condition stub&#xff09;&#xff1…

LwIP带操作系统的移植

目录 LwIP移植前期准备 LwIP移植流程 修改lwipopts.h 修改lwip_comm.c文件 修改ethernetif.c/h文件 修改ethernetif_input函数 修改ethernet.c文件 添加应用程序 LwIP是支持操作系统的&#xff0c;在操作系统的支持下我们可以使用LwIP提供的另外两种API编程接口编程。没…

使用动态代理+Netty+Zookeeper+Protobuff手撸一个RPC框架

RPC是什么 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;一种计算机之间的远程调用技术&#xff0c;客户端能够在不知道服务器底层的通信架构的情况下调用服务器端的方法&#xff0c;就像调用自身的方法一样。 举个例子&#xff1a; 老婆自己去…

Uni-app 实现md5加密

写下这篇文章&#xff0c;记录自己走过的坑 第一次尝试&#xff1a;参照博客uniapp使用md5_清雨小竹的博客-CSDN博客_uniapp md5 引入md5.js后&#xff0c;在main.js中import后&#xff0c;无法使用md5.hex_md5("需要加密的字符串")&#xff0c;vue页面无法打开&…

【捕风捉影】Vue项目报错,点击浏览器报错信息定位不到报错代码,该如何优雅地调试代码?

【捕风捉影】Vue项目如何优雅地调试代码一、背景二、调试时开启productionSourceMap三、devtool几种模式一、背景 通过vue-cli服务运行项目&#xff0c;项目运行一切正常。但打包后&#xff0c;通过nginx部署运行&#xff0c;大屏展示模块报echarts typeError 错误。但是点击浏…

如何使用Docker创建自定义网络

目录 网络模式 1.bridge模式(默认模式--桥接模式) 初识网络模式 查看桥接模式的特点 2.host模式&#xff08;仅主机模式&#xff09; 使用守护进程的方式创建并启动且进入容器 查看仅主机模式下的网络配置 端口映射 &#xff1a;​ 3.如何创建自定义网络 网络模式 Docker…

启发式算法 之 模拟退火原理及实践

一、初窥其貌 1.1 启发式算法和元启发式算法 启发式算法是求解优化问题的一类方法&#xff0c;因为经典优化方法存在局限性&#xff0c;有时无法得到最优解&#xff0c;只能得到一个可以接受的近似最优解&#xff0c;启发式算法就适合求解这类问题。启发式算法就是专家的推测…

Redis框架(七):大众点评项目 缓存穿透、缓存击穿、缓存雪崩

大众点评项目 缓存穿透、缓存击穿、缓存雪崩需求&#xff1a;缓存穿透、缓存击穿、缓存雪崩处理策略缓存穿透处理缓存雪崩缓存击穿总结SpringCloud章节复习已经过去&#xff0c;新的章节Redis开始了&#xff0c;这个章节中将会回顾Redis实战项目 大众点评 主要依照以下几个原则…

吉时利Keithley静电计程控上位机软件-摩擦纳米发电机测试软件NS-EM

1、产品简介 NS-EM 静电计程控系统可实现对吉时利静电计的程控&#xff0c;通过此系统软件您可以单独程控静电计进行数据的采集的同时还可以利用告诉信号采集卡对测试获取的电压、电流等信号进行高频率采样并实时显示采集信号的波形图。 2、产品特点 ◆可远程进行仪器控制&am…

QF state machine 介绍

转型Qt小半年了&#xff0c;看到项目组用的Qt state machine signal和匿名函数满天飞&#xff0c;就想之前用的C#里的QF state machine 能不能做转到Qt平台。这样可以省去使用Qt状态机的信号&#xff0c;在这过程中学习借鉴了QF state machine 的鼻祖 QP框架&#xff0c;不知道…

编译原理笔记

第一课&#xff1a; 《编译原理求语法树的短语和直接短语等等》 二义性是什么&#xff1f; 如果最左推导和最右推导的结果不一致&#xff0c;那么说明文法有二义性 短语是什么&#xff1f; 找短语就是找能长叶子的结点&#xff0c;有五个如图圆圈标号1 2 3 4 5 直接短语&#x…

c语言:联合体—union

联合体一.基本认识1.一个联合体的基本样式2.内部成员的访问3.具体的内存分配二.大小端对联合体的影响三.一个问题一.基本认识 1.一个联合体的基本样式 看得出来其实跟我们定义结构体是一样的&#xff08;如果还不大了解结构体的可以看看这篇博客什么是结构体&#xff09;&…

[附源码]计算机毕业设计港口集团仓库管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

MoveIT1 Assistant 总结

文章目录环境步骤备注故障问题解决参考接上一篇&#xff0c;生成URDF后&#xff0c;在MoveIT Assistant生成配置用于运动规划。https://blog.csdn.net/woshigaowei5146/article/details/128237105?spm1001.2014.3001.5501 环境 Ubuntu20.04&#xff1b;ROS1 Noetic;VMware 步…

生成模型(一):GAN

生成对抗网络 (GAN)在许多生成任务中显示出很好的结果&#xff0c;以复制真实世界的丰富内容&#xff0c;例如图像、文字和语音。它受到博弈论的启发&#xff1a;一个生成器和一个判别器&#xff0c;在互相竞争的同时让彼此变得更强大。然而&#xff0c;训练 GAN 模型相当具有挑…

一篇解析Linux paging_init

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 介绍 从详细讲解Linux物理内存初始化中&#xff0c;可知在paging_init调用之前&#xff0c;存放Kernel Image和…

java计算机毕业设计ssm幼儿英语学习平台的设计与实现yofnu(附源码、数据库)

java计算机毕业设计ssm幼儿英语学习平台的设计与实现yofnu&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支…