【Linux】socket编程2

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析

在这里插入图片描述


目录

  • 👉🏻客户端代码
    • Makefile(生成目标文件)
    • UdpClient.cc(客户端代码)
    • 服务端代码部分优化1(接受客户端时显示客户端地址信息)
    • InetAddrtoLocal.hpp(将网络地址信息转为主机地址信息)
      • inet_ntoa
    • 服务端代码部分优化2(不给服务器绑定具体IP地址)
    • Udpserver.hpp(去除了ip成员)
    • Main.cc
  • 👉🏻windows充当client给服务端发消息
  • 👉🏻实现处理命令
    • Udpserver.hpp
    • Main.cc
    • popen函数
    • fgets函数

👉🏻客户端代码

Makefile(生成目标文件)

UdpClient.cc(客户端代码)

#include<iostream>
#include"Log.hpp"
#include"ComErr.hpp"
#include<unistd.h>
#include<cstring>//包含strerror函数
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>

using namespace std;
void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << " local_ip local_port\n" << std::endl;
}
int main(int argc,char* argv[3])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(Usage_Err);//unistd.h
    }
    //客户端输入的ip地址和端口号,是要发送对象的ip地址和端口号,这里我们从命令行记录一下
    string ip = argv[1];
    uint16_t port = stoi(argv[2]);

    //给client创建套接字对象
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd==-1)
    {
        lg.LogMessage(Fatal,"socket err : %d %s\n",errno,strerror(errno));//cerrno
        exit(Socket_Err);
    }
    //client不需要手动绑定唯一网络信息,具体解释后面给出

    //接下来就是发送消息给服务端并接收服务端消息
    //但是发送数据前我们需要准备好报头,即目标对象的地址信息
    struct sockaddr_in dest;
    memset(&dest,0,sizeof(dest));
    dest.sin_family = AF_INET;//协议族
    dest.sin_port = htons(port);//将主机号变为网络序列
    dest.sin_addr.s_addr = inet_addr(ip.c_str());//ip地址转4字节且是网络序列

    socklen_t len = sizeof(dest);
    while(true)
    {
        //1.发送消息
       
        string inbuffer;
        cout<<"Please enter :";
        getline(cin,inbuffer);//string
        ssize_t n = sendto(sockfd,inbuffer.c_str(),inbuffer.size(),0,(struct sockaddr*)&dest,len);
        if(n!=-1)
        {
            //发送成功
            //2.接收服务端消息
            int defaultbuffer = 1024;
            char buffer[defaultbuffer];
            struct sockaddr_in temp;//存储服务端地址信息
            socklen_t templen = sizeof(temp);
            ssize_t ret = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&templen);
            if(ret!=0)
            {
                //接收到服务端消息
                buffer[ret] = '\0';
                cout<<"Server say :"<<buffer;
            }
            else
                {
                    cout<<"服务端并未回应...\n";
                }
        }
        else
            {
                cout<<"send fail\n";
                break;
            }
    }

    close(sockfd);
    return 0;

}

至此我们实现了客户端代码,现在让我们将其和服务端进行联动吧😄

在这里插入图片描述
效果还是不错的。

这里要解释几个要点:
🍎1.为什么client不能绑定唯一端口而是随机绑定?
因为会有非常多的客户端,比如我今天打开qq,qq客户端绑定了一个唯一端口号999,然后我又打开了王者荣耀,此时王者荣耀也想绑定999的端口号,此时就会起冲突,导致客户端打开错误了!
所以client需要Bind,但是不需要显示bind,让本地OS自动随机bind,选择随机端口号

什么时候bind? 在client首次发送数据的时候,进行由内核随机Bind

🍎2.ip地址127.0.0.1
IP地址127.0.0.1是IPv4地址中的一个特殊地址,它被保留用于本地环回测试。当你在计算机上访问127.0.0.1时,实际上是在访问你自己的计算机。这个地址通常被用来进行网络客户端/服务器测试,因为它允许在没有真实网络连接的情况下模拟网络通信。

当你将127.0.0.1作为目标IP地址时,你的计算机会尝试与自己进行通信。这对于测试网络应用程序、调试网络问题或者简单地检查网络服务是否正在正常运行非常有用。例如,当你在本地运行一个Web服务器时,可以通过在浏览器中输入"http://127.0.0.1"来访问它,而无需连接到真实的互联网。

服务端代码部分优化1(接受客户端时显示客户端地址信息)

这里我们专门写一个将网络信息转换为主机信息的类

InetAddrtoLocal.hpp(将网络地址信息转为主机地址信息)

#pragma once

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

class InetAddrtoLocal
{
public:
    InetAddrtoLocal(){}
    InetAddrtoLocal(struct sockaddr_in& addr)
    :_addr(addr)
    {
        _port= ntohs(_addr.sin_port);//将16位无符号短整数从网络字节序(大端序)转换为主机字节序。
        _ip = inet_ntoa(_addr.sin_addr);
    }
    string Ip(){return _ip;}

    uint16_t Port(){return _port;}

    string Info()
    {
        string info = _ip;
        info+=":";
        info+=to_string(_port);
        return info;
    }
private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

而后服务端那边部分代码稍微改下就行
服务端部分代码:👇🏻

  if(n!=-1)
            {
                //说明接受信息成功
                //接下来我们将打印发送方的信息
                buffer[n] = '\0';
                InetAddrtoLocal sender(peer);
                printf("Client[%s] : %s\n",(sender.Info()).c_str(),buffer);
                //cout<<"Client say : "<<buffer<<endl;
                //接下来也发送一些信息回应客户端
                char retstr[] = "Got it,processing...\n";
                sendto(_sockfd,retstr,sizeof(retstr),0,(struct sockaddr*)&peer,len);
            }

效果如下:
在这里插入图片描述

inet_ntoa

inet_ntoa函数是一个C语言中用于将32位IPv4地址转换为点分十进制字符串表示的函数。在头文件<arpa/inet.h>中声明。它接受一个struct in_addr类型的IPv4地址作为参数,并返回一个表示该地址的字符串。

以下是inet_ntoa函数的原型:

char *inet_ntoa(struct in_addr in);

其中in是一个struct in_addr类型的IPv4地址结构体。

inet_ntoa函数将32位的IPv4地址从二进制格式转换为点分十进制格式,并将结果存储在一个静态分配的缓冲区中,然后返回该缓冲区的指针。请注意,由于使用了静态缓冲区,因此每次调用inet_ntoa函数时,都会覆盖上一次调用的结果。因此,如果需要保存多个IPv4地址的字符串表示,应该将结果复制到另一个缓冲区中,以免被覆盖。

以下是一个示例,展示了如何使用inet_ntoa函数将IPv4地址转换为点分十进制字符串:

#include <stdio.h>
#include <arpa/inet.h>

int main() {
    struct in_addr addr;
    addr.s_addr = inet_addr("192.0.2.1");
    char *ip_str = inet_ntoa(addr);
    printf("IPv4 Address: %s\n", ip_str);
    return 0;
}

在这个示例中,首先将IPv4地址字符串"192.0.2.1"转换为struct in_addr类型的结构体,然后使用inet_ntoa函数将其转换为点分十进制字符串,并最终打印出来。

服务端代码部分优化2(不给服务器绑定具体IP地址)

在网络编程中,通常更推荐将服务器绑定到本地的随机IP地址,而不是固定的IP地址。这种方式通常被称为"本地绑定随机IP"或"通配绑定"。

以下是几个原因:

  1. 灵活性和可移植性: 如果服务器绑定了一个固定的IP地址,那么当需要将服务器迁移到另一个网络环境时,可能需要重新配置服务器以适应新的网络环境。而本地绑定随机IP的方式可以使得服务器更加灵活和可移植,因为它会自动选择可用的本地IP地址进行绑定,无需手动配置

  2. 多网卡支持: 如果服务器有多个网络接口或多个IP地址,本地绑定随机IP的方式可以更好地利用这些资源,使得服务器能够在多个网络接口或多个IP地址上同时监听连接。

  3. 容错性: 本地绑定随机IP的方式可以提高服务器的容错性。当服务器监听的IP地址不可用时(例如,由于网络故障或IP地址冲突),服务器可以尝试绑定到另一个可用的本地IP地址,从而避免服务中断。

  4. 安全性: 通过本地绑定随机IP的方式,可以减少服务器暴露在公共网络上的风险。固定IP地址可能会成为黑客攻击的目标,而使用随机IP地址可以增加攻击者对服务器的识别和定位的难度。

在网络编程中,经常会使用INADDR_ANY来赋值给IP地址。这个常量表示"任意"或"所有"的IP地址,通常用于服务器端绑定监听所有可用的网络接口,而不关心具体是哪个网络接口。具体来说,将IP地址设置为INADDR_ANY可以使服务器监听所有可用的网络接口上的连接请求,而不必关心具体是哪个网卡或IP地址

所以,客户端代码要改的话,就是命令行参数我们不需要指定具体的ip了,而是让内核自定义随机可用的ip地址。

Udpserver.hpp(去除了ip成员)

#include<iostream>
#include "ComErr.hpp"
#include"Log.hpp"
#include <cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"InetAddrtoLocal.hpp"

using namespace std;

const string defaultip = "127.0.0.0";
const uint16_t defaultport = 8888;
class Udpsever
{
public:
    Udpsever(Udpsever& udp)=delete;//防止拷贝
    Udpsever(const Udpsever& udp)=delete;
    const Udpsever& operator=(Udpsever& udp)=delete;

    Udpsever(const uint16_t& port = defaultport)
    :_port(port)
    {}

    void Init()
    {
        //1.创建套接字对象
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建一个套接字对象,协议族为IPv4,套接字类型是数据报套接字
        if(_sockfd<0)
        {
            //创建失败则打印错误日志
            lg.LogMessage(Fatal,"socket err,%d : %s\n",errno,strerror(errno));//strerror需要包含头文件cstring
            exit(Socket_Err);
        }
        //成功则打印返回的套接字描述符
        lg.LogMessage(Info,"sockfd : %d\n",_sockfd);

        //上述我们只是创建了一个套接字对象,即我们本地的ip和端口号已经有了
        //但是接下来我们要与网络信息绑定,才能实现在网络中进行通信
        //我们要用到socket编程中的bind函数:用于将套接字与特定的地址(IP地址和端口号)绑定在一起,以便其他计算机可以找到并与之通信
        //2.bind并指定网络信息
        //2.1指定网络地址信息
        
        struct sockaddr_in local;//创建一个网络地址信息结构体,接下里初始化地址信息
        bzero(&local,sizeof(local));//相当于memset
        local.sin_family = AF_INET;//1.初始化协议族
        local.sin_port = htons(_port);//2.将16位无符号短整数从主机字节序转换为网络字节序(大端序)
        local.sin_addr.s_addr = INADDR_ANY;//3.初始化ip地址,这里我们想让_ip变为4字节且是网络字节序,所以使用转换函数inet_addr

        //2.2将网络地址信息与socket文件对象进行绑定
        //bind函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        int bd = bind(_sockfd,(const struct sockaddr *)&local,sizeof(local));
        if(bd!=0)
        {
            lg.LogMessage(Fatal,"bind err :%d %s\n",errno,strerror(errno));
            exit(Bind_Err);
        }
        //至此socket初始化完成

        

    }
    void Start()
    {
        int defaultbuffer = 1024;
        char buffer[defaultbuffer];
        while(true)//服务器永远不退出
        {
            //接受信息
            struct sockaddr_in peer;//用来存储发送方的地址信息
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1/*再减1是给\0留位置*/,0,(struct sockaddr*)&peer,&len);
            if(n!=-1)
            {
                //说明接受信息成功
                //接下来我们将打印发送方的信息
                buffer[n] = '\0';
                InetAddrtoLocal sender(peer);
                printf("Client[%s] : %s\n",(sender.Info()).c_str(),buffer);
                //cout<<"Client say : "<<buffer<<endl;
                //接下来也发送一些信息回应客户端
                char retstr[] = "Got it,processing...\n";
                sendto(_sockfd,retstr,sizeof(retstr),0,(struct sockaddr*)&peer,len);
            }
        }
    }
private:
    uint16_t _port;//端口号
    int _sockfd;//套接字描述符
};

Main.cc

#include "Udpserver.hpp"
#include "ComErr.hpp"
#include <memory>

void Usage(std::string proc)
{
    std::cout << "Usage : \n\t" << proc << "local_port\n" << std::endl;
}
int main(int argc,char* argv[2])
{
    if(argc!=2)//如果命令行指令数没有两个
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    //string ip = argv[1];//获取ip
    uint16_t port = stoi(argv[1]);//获取端口号

    //接下来创建Udpserver的指针

    unique_ptr<Udpsever> usvr = std::make_unique<Udpsever>(port);//unique_ptr不支持拷贝构造和赋值操作,因此确保了资源的独占性
    //使用make_unique记得添加memory头文件
    usvr->Init();
    usvr->Start();

    return 0;
}

效果如下: 👇🏻
在这里插入图片描述

👉🏻windows充当client给服务端发消息

#include <WinSock2.h>
#include <Windows.h>
#include <iostream>
#include <string>

#pragma comment(lib,"ws2_32.lib")//引入动态库 

using namespace std;

uint16_t serverport = 8081;
string serverip = "110.40.135.148";

int main()
{
    WSADATA wsd;
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)//给动态库初始化 
    {
        cout << "WSAStartup failed\n";
        return 1;
    }

    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == INVALID_SOCKET)
    {
        cout << "socket create failed\n";
        WSACleanup();
        return 1;
    }

    struct sockaddr_in dest;
    memset(&dest, 0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(serverport);
    dest.sin_addr.s_addr = inet_addr(serverip.c_str());

    int len = sizeof(dest);

    while (true)
    {
        string inbuffer;
        cout << "Please enter :";
        getline(cin, inbuffer);

        int n = sendto(sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&dest, len);
        if (n != SOCKET_ERROR)
        {
            cout << "send success" << endl;
            const int defaultbuffer = 1024;
            char buffer[defaultbuffer];
            struct sockaddr_in temp;
            int templen = sizeof(temp);
            int ret = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &templen);
            if (ret != SOCKET_ERROR)
            {
                buffer[ret] = '\0';
                cout << "Server say: " << buffer << endl;
            }
            else
            {
                cout << "recvfrom failed: " << WSAGetLastError() << endl;
            }
        }
        else
        {
            cout << "sendto failed: " << WSAGetLastError() << endl;
        }

        // Add a condition to exit the loop
        if (inbuffer == "exit")
        {
            break;
        }
    }

    closesocket(sockfd);
    WSACleanup();//清理ws2库 
    return 0;
}

经过我的测试
说实话代码没有问题,数据也能发送成功。
但是不知道是不是因为网络防火墙的原因,服务端那边一直阻塞在recvfrom函数那里,导致数据一直不能被读出,也是很尴尬了(后续我再看看有没有什么办法)

👉🏻实现处理命令

这里我只给出修改过代码的源文件

Udpserver.hpp

#include<iostream>
#include "ComErr.hpp"
#include"Log.hpp"
#include <cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"InetAddrtoLocal.hpp"

#include<functional>
using namespace std;

const string defaultip = "127.0.0.0";
const uint16_t defaultport = 8888;
const int defaultbuffer = 1024;

using func_t = function<string(string)>;//定义函数类型

class Udpsever
{
public:
    Udpsever(Udpsever& udp)=delete;//防止拷贝
    Udpsever(const Udpsever& udp)=delete;
    const Udpsever& operator=(Udpsever& udp)=delete;

    Udpsever(func_t OnMessage,const uint16_t& port = defaultport)
    :_port(port),_OnMessage(OnMessage)
    {}

    void Init()
    {
        //1.创建套接字对象
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);//创建一个套接字对象,协议族为IPv4,套接字类型是数据报套接字
        if(_sockfd<0)
        {
            //创建失败则打印错误日志
            lg.LogMessage(Fatal,"socket err,%d : %s\n",errno,strerror(errno));//strerror需要包含头文件cstring
            exit(Socket_Err);
        }
        //成功则打印返回的套接字描述符
        lg.LogMessage(Info,"sockfd : %d\n",_sockfd);

        //上述我们只是创建了一个套接字对象,即我们本地的ip和端口号已经有了
        //但是接下来我们要与网络信息绑定,才能实现在网络中进行通信
        //我们要用到socket编程中的bind函数:用于将套接字与特定的地址(IP地址和端口号)绑定在一起,以便其他计算机可以找到并与之通信
        //2.bind并指定网络信息
        //2.1指定网络地址信息
        
        struct sockaddr_in local;//创建一个网络地址信息结构体,接下里初始化地址信息
        bzero(&local,sizeof(local));//相当于memset
        local.sin_family = AF_INET;//1.初始化协议族
        local.sin_port = htons(_port);//2.将16位无符号短整数从主机字节序转换为网络字节序(大端序)
        local.sin_addr.s_addr = INADDR_ANY;//3.初始化ip地址,这里我们想让_ip变为4字节且是网络字节序,所以使用转换函数inet_addr

        //2.2将网络地址信息与socket文件对象进行绑定
        //bind函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        int bd = bind(_sockfd,(const struct sockaddr *)&local,sizeof(local));
        if(bd!=0)
        {
            lg.LogMessage(Fatal,"bind err :%d %s\n",errno,strerror(errno));
            exit(Bind_Err);
        }
        //至此socket初始化完成

        

    }
    void Start()
    {
        cout<<"start"<<endl;
        char buffer[defaultbuffer];
        while(true)//服务器永远不退出
        {
            cout<<"running..."<<endl;
            //接受信息
            struct sockaddr_in peer;//用来存储发送方的地址信息
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1/*再减1是给\0留位置*/,0,(struct sockaddr*)&peer,&len);
            
            if(n!=-1)
            {
                cout<<"收到\n";
                //说明接受信息成功
                //接下来我们将打印发送方的信息
                buffer[n] = '\0';

                //处理消息
                string response = _OnMessage(buffer);

                // InetAddrtoLocal sender(peer);
                // printf("Client[%s] : %s\n",(sender.Info()).c_str(),buffer);
                // //cout<<"Client say : "<<buffer<<endl;
                // //接下来也发送一些信息回应客户端
                // char retstr[] = "Got it,processing...\n";
                sendto(_sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&peer,len);
            }
            else
            {
                cout<<"没收到\n";
                strerror(errno);

            }
        }
    }
private:
    uint16_t _port;//端口号
    int _sockfd;//套接字描述符

    func_t _OnMessage;//回调函数
};

Main.cc

#include "Udpserver.hpp"
#include "ComErr.hpp"
#include <memory>
#include<cstdio>
#include<vector>

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

string OnMessageDefault(string request)
{
    return request + "[got message]\n";
}

std::vector<std::string> black_words = {//黑名单
    "rm",
    "unlink",
    "cp",
    "mv",
    "chmod",
    "exit",
    "reboot",
    "halt",
    "shutdown",
    "top",
    "kill",
    "dd",
    "vim",
    "vi",
    "nano",
    "man"
};
bool SafeCheck(std::string command)
{
    for(auto &k : black_words)
    {
        std::size_t pos = command.find(k);
        if(pos != std::string::npos) return false;
    }

    return true;
}
string ExcuteCommand(string command)
{
    if(!SafeCheck(command)) return "bad man!!\n";
    cout<<"get a message: "<< command<<endl;
    FILE* fp = popen(command.c_str(),"r");
    if(fp == nullptr)
    {
        return "excute error,reason is unknown\n";
    }

    string response;
    char buffer[1024];
    while(true)
    {
        char* s = fgets(buffer,sizeof(buffer),fp);
        if(!s) break;
        else response+=buffer;
    }
    pclose(fp);
    return response.empty()?"Nothing excute\n":response;//将处理结果输出到屏幕上
}
int main(int argc,char* argv[2])
{
    if(argc!=2)//如果命令行指令数没有三个
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    //string ip = argv[1];//获取ip
    uint16_t port = stoi(argv[1]);//获取端口号

    //接下来创建Udpserver的指针

    unique_ptr<Udpsever> usvr = std::make_unique<Udpsever>(ExcuteCommand,port);//unique_ptr不支持拷贝构造和赋值操作,因此确保了资源的独占性
    //使用make_unique记得添加memory头文件
    usvr->Init();
    usvr->Start();

    return 0;
}

popen函数

popen 函数是一个 C 标准库中的函数,用于创建一个进程,并建立一个管道连接到该进程的标准输入或标准输出。它通常用于创建子进程,并与其进行双向通信。

popen 函数的原型如下:

FILE *popen(const char *command, const char *mode);

其中,command 参数是一个字符串,表示要执行的命令,mode 参数是一个字符串,指定打开的管道是用于读取还是写入。mode 参数可以是 "r"(读取)或 "w"(写入)。

popen 函数返回一个 FILE 指针,可以像使用 fopen 返回的文件指针一样来操作管道。如果 popen 调用失败,它将返回 NULL

下面是一个简单的示例,演示如何使用 popen 函数来执行一个命令并读取其输出:

#include <stdio.h>

int main() {
    FILE *pipe;
    char buffer[128];

    // 执行命令并读取其输出
    pipe = popen("ls -l", "r");
    if (pipe == NULL) {
        perror("popen");
        return -1;
    }

    // 读取管道的输出并打印到标准输出
    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
        printf("%s", buffer);
    }

    // 关闭管道
    pclose(pipe);

    return 0;
}

在这个示例中,我们使用 popen 执行了 ls -l 命令,并从其输出中读取文件列表,并将其打印到标准输出。最后,使用 pclose 函数关闭了管道。

需要注意的是,popen 函数在 Windows 平台上不是标准 C 库的一部分,并且可能不可用。在 Windows 上,可以考虑使用 _popen_pclose 函数来实现类似的功能。

fgets函数

fgets 函数是 C 标准库中用于从文件流中读取一行数据的函数。它的原型如下:

char *fgets(char *str, int n, FILE *stream);

其中:

  • str 是一个指向字符数组的指针,用于存储读取的字符串。
  • n 是一个整数,表示要读取的最大字符数,包括最后的空字符 ‘\0’。
  • stream 是一个指向文件的指针,指定要读取的文件流。

fgets 函数会从指定的文件流中读取字符,直到遇到换行符 ‘\n’、文件结束符 EOF 或者读取了 n-1 个字符为止(包括换行符)。它会将读取的字符存储到 str 指向的字符数组中,并在末尾添加一个空字符 ‘\0’,以表示字符串的结束。

fgets 函数的返回值是一个指向目标字符数组的指针,即参数 str 的值,或者在发生错误或到达文件末尾时返回 NULL

下面是一个简单的示例,演示如何使用 fgets 函数从文件中读取一行数据:

#include <stdio.h>

int main() {
    FILE *file;
    char buffer[128];

    // 打开文件以供读取
    file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("fopen");
        return -1;
    }

    // 从文件中读取一行数据
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }

    // 关闭文件
    fclose(file);

    return 0;
}

在这个示例中,我们打开一个名为 example.txt 的文件以供读取,然后使用 fgets 函数从文件中逐行读取数据,并将其打印到标准输出。最后,使用 fclose 函数关闭文件。


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机低中高音7键电子琴音乐播放器

基于51单片机电子琴音乐播放器 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.可以使用按键切换音乐播放模式和弹奏模式&#xff1b; 2.LED灯显示在使用哪种模式&#xff1b; 3.音乐…

Redis部署之主从

使用两台云服务器&#xff0c;在 Docker 下部署。 Redis版本为&#xff1a;7.2.4 下载并配置redis 配置文件 下载 wget -c http://download.redis.io/redis-stable/redis.conf配置 master节点配置 bind 0.0.0.0 # 使得Redis服务器可以跨网络访问,生产环境请考虑…

第十四届蓝桥杯C/C++大学B组题解(二)

6、岛屿个数 #include <bits/stdc.h> using namespace std; const int M51; int T,m,n; int vis[M][M],used[M][M]; int dx[]{1,-1,0,0,1,1,-1,-1}; int dy[]{0,0,1,-1,1,-1,1,-1}; string mp[M]; struct node{//记录一点坐标 int x,y; }; void bfs_col(int x,int y){ qu…

C++ | Leetcode C++题解之第14题最长公共前缀

题目&#xff1a; 题解&#xff1a; class Solution { public:string longestCommonPrefix(vector<string>& strs) {if (!strs.size()) {return "";}int minLength min_element(strs.begin(), strs.end(), [](const string& s, const string& t)…

同步压缩理论

参考 在频率方向进行能量重新分配&#xff08;分配到中心&#xff09; 时频重排

实验4 DHCP基础配置

实验4 DHCP基础配置 一、 原理描述二、 实验目的三、 实验内容四、 实验配置五、 实验步骤1.基本配置2.配置DHCPServer功能3.配置DHCP Client 一、 原理描述 动态主机配置协议 DHCP是一个局域网的网络协议&#xff0c;使用UDP协议工作&#xff0c;主要有两个用途&#xff1a;用…

大话设计模式——16.命令模式(Command Pattern)

简介 请求以命令的形式包裹在对象中&#xff0c;并传给调用对象。调用对象寻找可以处理该命令的对象进行执行。命令模式是一种特殊的策略模式&#xff0c;体现多个策略执行的问题&#xff0c;而不是选择的问题 UML图 应用场景 界面选择、键盘、按钮、事件操作都类似命令模式 …

前端工程化理解 (2024 面试题)

最好介绍远古世界最好随性一点&#xff0c;不要太刻板 &#xff0c;不然像背书 什么是前端工程化&#xff1f; - 知乎 前端工程化的历史 互联网初期&#xff0c;09 年以前&#xff0c;页面只需要展示一些列表、表格、文章内容以及简单图片即可&#xff0c;其目的是为了传送信…

身份证正面打印、反面打印与打印在同一页

1 打印身份证正面 将身份证正面朝下放置&#xff0c;按打印机键盘上的数字【3】&#xff0c;再按【Start】键&#xff0c;选择A4大小打印&#xff0c;如图(1)所示&#xff1a; 图(1) A4纸复印 2 打印身份证反面 将身份证反面朝下放置&#xff0c;按打印机键盘上的数字【3】&…

KVM 高级功能部署

目录 一、案例分析 1.1、案例概述 1.2、案例前置知识点 1&#xff09;KVM 虚拟机迁移 2&#xff09;KSM 内核同页合并 1.3、案例环境 1&#xff09;本案例环境 2&#xff09;案例需求 3&#xff09;案例实现思路 二、案例实施 2.1、静态迁移 1&#xff09;在…

springboot+vue药店药品进销存采购管理系统0z10z

本系统采用intellij idea支持eclipse 项目架构&#xff1a;B/S架构web 开发语言&#xff1a;java 前端技术&#xff1a;vue.jsElementUi 后端框架&#xff1a;django、mybatis、Springmvc 运行环境&#xff1a;win10/win11、jdk1.8 可行性论证 社会可行性 开发本系统&#xff…

XC6206稳压芯片

mark 662k XC6206 的基本特性。这是一个 SOT23封装的 3.3V 稳压器。它输出最大工作电流为 100mA 最大特点便宜 参考链接 XC6206稳压芯片 (qq.com)https://mp.weixin.qq.com/s?__bizMzA5NjQyNjc2NQ&mid2452268489&idx1&sn90e920c596e3c2a382f81929c6313977&c…

Linux--进程的概念(一)

目录 一、冯诺依曼体系结构二、操作系统2.1 什么是操作系统2.2 操作系统的意义 三、进程3.1 进程的基本概念3.2 描述进程——PCB3.3 进程和程序的区别3.4 task_struct-PCB的一种3.5 task_struct的内容分类 四、如何查看进程4.1 通过系统文件查看进程4.2 通过ps指令查看进程 五、…

《公安机关互联网安全监督检查规定》系列之“解决方案”

随着中国互联网和信息网络飞速发展&#xff0c;无线网络普及到国内各个家庭和公共场所&#xff0c;成为人们日常办公和生活娱乐不可或缺的一部分。无线网络在创造商业价值、带来工作和生活便捷的同时&#xff0c;也同样让犯罪份子有了可乘之机&#xff0c;越来越多的网络违法活…

如何通过数据验证防止 Web API 攻击 - Web API 安全指南

充分的数据保护和用户保密是网页开发者的主要责任。因此&#xff0c;在构建 API 终端时&#xff0c;确保最高可能的安全性至关重要。 应用程序安全是客户端和服务器开发者共同的责任&#xff0c;一方的疏忽可能会造成灾难性后果。统计数据显示&#xff0c;2023 年的数据泄露导…

windows安装Redis,Mongo,ES并快速基本掌握开发流程

前言 这里只是一些安装后的基础操作&#xff0c;后期会学习更加深入的操作 基础操作 前言RedisRedis启动idea集成Redisjedis技术 Mongodbwindows版Mongodb的安装idea整合Mongodb ES(Elasticsearch)ESwindows下载ES文档操作idea整合ES低级别ES整合高级别ES整合 Redis Redis是…

HarmonyOS 开发-Grid和List内拖拽交换子组件位置

介绍 本示例分别通过onItemDrop()和onDrop()回调&#xff0c;实现子组件在Grid和List中的子组件位置交换。 效果图预览 使用说明&#xff1a; 拖拽Grid中子组件&#xff0c;到目标Grid子组件位置&#xff0c;进行两者位置互换。拖拽List中子组件&#xff0c;到目标List子组件…

【进阶六】Python实现SDVRPTW常见求解算法——自适应大邻域算法(ALNS)

基于python语言&#xff0c;采用经典自适应大邻域算法&#xff08;ALNS&#xff09;对 带硬时间窗的需求拆分车辆路径规划问题&#xff08;SDVRPTW&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整2.1 需求拆分2.2 需求拆分后的服务时长取值问题 3. 求解结果4…

CADMap3D2024 2023下载地址及安装教程

CAD Map 3D是由Autodesk开发的一款专业的地图制作和GIS&#xff08;地理信息系统&#xff09;软件。它是AutoCAD系列软件的一个扩展&#xff0c;提供了一系列特定于地理数据的工具和功能。 CAD Map 3D主要用于处理和管理与地理空间相关的数据&#xff0c;在地图制作、城市规划…

洗地机哪个品牌质量好?四大高口碑优质款式直入

如今&#xff0c;保持家居地面清洁整洁已成为生活中的重要任务。在这方面&#xff0c;洗地机作为一种高效的清洁工具备受青睐。然而&#xff0c;市场上的洗地机种类繁多&#xff0c;选择起来常常让人头疼。所以&#xff0c;哪个品牌的洗地机质量更好呢&#xff1f;以下是几款备…