【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现)

这篇具有很好参考价值的文章主要介绍了【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现),Linux,网络,linux,tcp/ip

引言

在前一篇文章中,我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。本文将延续上文内容,重点讨论简单的TCP网络程序模拟实现。通过本文的学习,读者将能够深入了解TCP协议的实际应用,并掌握如何编写简单的TCP网络程序。让我们一起深入探讨TCP网络程序的实现细节,为网络编程的学习之旅添上一份精彩的实践经验。

一、TCP协议

TCP(Transmission Control Protocol)是一种面向连接的通信协议,它要求在数据传输前先建立连接,以确保数据的可靠传输。TCP通过序号、确认和重传等机制来保证数据的完整性和可靠性,同时还实现了拥塞控制和流量控制,以适应不同网络环境下的数据传输需求。由于TCP的可靠性和稳定性,它被广泛应用于网络通信中,包括网页浏览、文件传输、电子邮件等各种应用场景,成为互联网协议套件中的重要组成部分。详介绍可以看上一篇文章:UDP协议介绍 | TCP协议介绍 | UDP 和 TCP 的异同

二、TCP网络程序模拟实现

接下来,我们打算运用线程池技术,模拟实现一个简单的TCP网络程序。通过充分利用线程池,我们能够更有效地管理并发连接,从而提高程序的性能和稳定性。这一实践将有助于加深我们对网络编程关键概念和技术的理解和掌握。在前文中已经提到了线程池,这里就不再赘述其原理和作用。详细可以点击传送门:🚩 线程池

1. 预备代码

⭕ThreadPool.hpp(线程池)

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

// 线程信息结构体
struct ThreadInfo
{
    pthread_t tid;  // 线程ID
    std::string name;  // 线程名称
};

static const int defalutnum = 10;  // 默认线程池大小为10

template <class T>
class ThreadPool
{
public:
    void Lock() // 加锁
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock() // 解锁
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup() // 唤醒等待中的线程
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep() // 线程休眠
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty() // 判断任务队列是否为空
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid) // 获取线程名称
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args) // 线程任务处理函数
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty()) // 若任务队列为空,则线程等待
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop(); // 从任务队列中取出任务
            tp->Unlock();

            t(); // 执行任务
        }
    }
    void Start() // 启动线程池
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this); // 创建线程
        }
    }
    T Pop() // 从任务队列中取出任务
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t) // 将任务推入任务队列
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance() // 获取线程池实例
    {
        if (nullptr == tp_) // 若线程池实例为空
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_) // 双重检查锁
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>(); // 创建线程池实例
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num) // 构造函数
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool() // 析构函数
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete; // 禁用拷贝构造函数
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // 禁用赋值操作符,避免 a=b=c 的写法
private:
    std::vector<ThreadInfo> threads_; // 线程信息数组
    std::queue<T> tasks_; // 任务队列

    pthread_mutex_t mutex_; // 互斥锁
    pthread_cond_t cond_; // 条件变量

    static ThreadPool<T> *tp_; // 线程池实例指针
    static pthread_mutex_t lock_; // 静态互斥锁
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr; // 初始化线程池实例指针为nullptr

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER; // 初始化静态互斥锁

以上代码实现了一个简单的线程池模板类 ThreadPool,其中包含了线程池的基本功能和操作。

  1. 首先定义了一个线程信息结构体 ThreadInfo,用来保存线程的ID和名称。

  2. 然后定义了一个模板类 ThreadPool,其中包含了线程池的各种操作和属性:

    • Lock()Unlock() 分别用于加锁和解锁。
    • Wakeup() 用于唤醒等待中的线程。
    • ThreadSleep() 用于使线程进入休眠状态。
    • IsQueueEmpty() 判断任务队列是否为空。
    • GetThreadName() 根据线程ID获取线程名称。
  3. 定义了静态成员函数 HandlerTask,作为线程的任务处理函数。在该函数中,线程会不断地从任务队列中取出任务并执行。

  4. Start() 函数用于启动线程池,创建指定数量的线程,并将线程的任务处理函数设置为 HandlerTask

  5. Pop() 函数用于从任务队列中取出任务。

  6. Push() 函数用于将任务推入任务队列。

  7. GetInstance() 函数用于获取线程池的实例,采用了双重检查锁(Double-Checked Locking)实现单例模式。

  8. 线程池的构造函数和析构函数分别用于初始化和销毁互斥锁和条件变量。

  9. 最后使用静态成员变量初始化了线程池实例指针和静态互斥锁。

⭕makefile文件

.PHONY:all
all:tcpserverd tcpclient

tcpserverd:Main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcpclient:TcpClient.cc
	g++ -o $@ $^ -std=c++11


.PHONY:clean
clean:
	rm -f tcpserverd tcpclient

这段代码是一个简单的 Makefile 文件,用于编译生成两个可执行文件 tcpserverdtcpclient

  1. .PHONY: all:声明 all 为一个伪目标,表示 all 不是一个实际的文件名,而是一个指定的操作。

  2. all: tcpserverd tcpclient:定义了 all 目标,它依赖于 tcpserverdtcpclient 目标。当执行 make all 时,会先编译 tcpserverdtcpclient

  3. tcpserverd: Main.cc:定义了生成 tcpserverd 可执行文件的规则,依赖于 Main.cc 源文件。使用 g++ 编译器进行编译,指定输出文件名为 tcpserverd,使用 C++11 标准,并链接 pthread 库。

  4. tcpclient: TcpClient.cc:定义了生成 tcpclient 可执行文件的规则,依赖于 TcpClient.cc 源文件。同样使用 g++ 编译器进行编译,指定输出文件名为 tcpclient,使用 C++11 标准。

  5. .PHONY: clean:声明 clean 为一个伪目标。

  6. clean: rm -f tcpserverd tcpclient:定义了 clean 目标,用于清理生成的可执行文件。执行 make clean 时将删除 tcpserverdtcpclient 可执行文件。

⭕打印日志文件

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen; // 默认打印方式为屏幕输出
        path = "./log/"; // 默认日志文件路径为当前目录下的"log/"目录
    }

    void Enable(int method)
    {
        printMethod = method; // 设置打印方式
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl; // 在屏幕上输出日志信息
            break;
        case Onefile:
            printOneFile(LogFile, logtxt); // 将日志信息写入单个文件中
            break;
        case Classfile:
            printClassFile(level, logtxt); // 根据日志级别将日志信息写入不同的文件中
            break;
        default:
            break;
        }
    }

    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname; // 拼接日志文件路径
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // 打开或创建一个文件,以追加方式写入
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size()); // 将日志信息写入文件
        close(fd); // 关闭文件
    }

    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // 生成日志文件名,例如"log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt); // 将日志信息写入对应级别的文件中
    }

    ~Log()
    {
    }

    // 重载()运算符,用于输出日志信息
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t); // 获取当前时间

        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec); // 格式化左侧部分,包括日志级别和时间信息

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // 格式化右侧部分,即用户自定义的日志内容
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer); // 拼接左右两侧的日志内容
        printLog(level, logtxt); // 打印日志信息
    }

private:
    int printMethod; // 打印方式
    std::string path; // 日志文件路径
};

这段代码是一个简单的日志记录类 Log,它提供了几种不同的日志输出方式和日志级别。

  • #pragma once: 使用编译器指令,确保头文件只被编译一次。

  • 定义了一些常量:

    • SIZE: 缓冲区大小为 1024。
    • 日志级别常量:Info, Debug, Warning, Error, Fatal
    • 打印方式常量:Screen, Onefile, Classfile
    • 日志文件名常量:LogFile
  • Log 类包含以下成员函数和变量:

    • printMethod: 记录当前的打印方式,默认为屏幕输出。
    • path: 日志文件路径,默认为"./log/"。
  • 构造函数 Log() 初始化 printMethodpath

  • Enable(int method): 设置日志的打印方式。

  • levelToString(int level): 将日志级别转换为对应的字符串。

  • printLog(int level, const std::string &logtxt): 根据打印方式输出日志信息。

  • printOneFile(const std::string &logname, const std::string &logtxt): 将日志信息写入单个文件中。

  • printClassFile(int level, const std::string &logtxt): 根据日志级别将日志信息写入不同的文件中。

  • 析构函数 ~Log()

  • 重载的函数调用运算符 operator(): 接受日志级别和格式化字符串,格式化输出日志信息到不同的输出位置。

⭕将当前进程转变为守护进程

#pragma once

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string nullfile = "/dev/null"; // 定义空设备文件路径

// 将当前进程变为守护进程的函数
void Daemon(const std::string &cwd = "")
{
    // 1. 忽略一些异常信号,以避免对守护进程造成影响
    signal(SIGCLD, SIG_IGN); // 忽略子进程结束信号
    signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
    signal(SIGSTOP, SIG_IGN); // 忽略终止信号

    // 2. 创建一个子进程并使父进程退出,确保守护进程不是进程组组长,创建一个新的会话
    if (fork() > 0)
        exit(0); // 父进程退出
    setsid(); // 创建新的会话,并成为该会话的首进程

    // 3. 更改当前调用进程的工作目录,如果指定了工作目录则切换到相应目录
    if (!cwd.empty())
        chdir(cwd.c_str()); // 切换工作目录到指定路径

    // 4. 将标准输入,标准输出,标准错误重定向至/dev/null,关闭不需要的文件描述符
    int fd = open(nullfile.c_str(), O_RDWR); // 打开空设备文件
    if (fd > 0)
    {
        dup2(fd, 0); // 标准输入重定向至空设备
        dup2(fd, 1); // 标准输出重定向至空设备
        dup2(fd, 2); // 标准错误重定向至空设备
        close(fd); // 关闭打开的文件描述符
    }
}

这段代码实现了将当前进程转变为守护进程的函数 Daemon

  1. 忽略一些异常信号,避免对守护进程产生影响。
  2. 创建一个子进程并使父进程退出,确保守护进程不是进程组组长,创建一个新的会话。
  3. 更改当前调用进程的工作目录,如果指定了工作目录,则切换到相应目录。
  4. 将标准输入、标准输出和标准错误重定向至 /dev/null,即空设备文件,关闭不需要的文件描述符,确保守护进程不产生输出和错误信息。

2. TCP 服务器端实现(TcpServer.hpp)

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 最大连接请求队列长度

extern Log lg;

enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

class TcpServer;

// 线程数据结构,用于传递给线程处理函数
class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

// TCP服务器类
class TcpServer
{
public:
    // 构造函数,初始化端口和IP地址
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
    {
    }

    // 初始化服务器
    void InitServer()
    {
        // 创建套接字
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            lg(Fatal, "create socket, errno: %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensock_: %d", listensock_);

        // 设置套接字选项,允许地址重用
        int opt = 1;
        setsockopt(listensock_, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));

        // 绑定本地地址
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));

        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }

        lg(Info, "bind socket success, listensock_: %d");

        // 监听套接字,开始接受连接请求
        if (listen(listensock_, backlog) < 0)
        {
            lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }

        lg(Info, "listen socket success, listensock_: %d");
    }

    // 启动服务器
    void Start()
    {
        // 将当前进程变为守护进程
        Daemon();

        // 启动线程池
        ThreadPool<Task>::GetInstance()->Start();

        lg(Info, "tcpServer is running....");
        
        // 循环接受客户端连接并处理
        while(true)
        {
            // 获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); 
                continue;
            }

            // 获取客户端IP和端口
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            // 打印客户端连接信息
            lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);

            // 创建任务对象并加入线程池处理
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    // 析构函数
    ~TcpServer() {}

private:
    int listensock_;  // 监听套接字
    uint16_t port_;   // 端口号
    std::string ip_;  // IP地址
};

这段代码是一个简单的TCP服务器的实现,包括了创建套接字、绑定地址、监听连接、接受客户端连接等基本操作。

3. TCP 客户端实现(main函数)

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

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    // 检查命令行参数是否正确
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 设置服务器地址信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    while (true)
    {
        int cnt = 5; // 连接重试次数
        int isreconnect = false; // 是否需要重连
        int sockfd = 0;

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

        do
        {
            // 尝试连接服务器
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2); // 等待一段时间后重连
            }
            else
            {
                break;
            }
        } while (cnt && isreconnect);

        if (cnt == 0)
        {
            std::cerr << "user offline..." << std::endl;
            break;
        }

        // 与服务器建立连接后进行通信
        while (true)
        {
            std::string message;
            std::cout << "Please Enter# ";
            std::getline(std::cin, message);

            // 向服务器发送消息
            int n = write(sockfd, message.c_str(), message.size());
            if (n < 0)
            {
                std::cerr << "write error..." << std::endl;
            }

            // 从服务器接收消息并显示
            char inbuffer[4096];
            n = read(sockfd, inbuffer, sizeof(inbuffer));
            if (n > 0)
            {
                inbuffer[n] = 0;
                std::cout << inbuffer << std::endl;
            }
        }

        // 关闭套接字
        close(sockfd);
    }

    return 0;
}

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现),Linux,网络,linux,tcp/ip文章来源地址https://www.toymoban.com/news/detail-850501.html

到了这里,关于【探索Linux】P.29(网络编程套接字 —— 简单的TCP网络程序模拟实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux网络编程】网络编程套接字二

    喜欢的点赞,收藏,关注一下把! TCP和UDP在编程接口上是非常像的,前面我们说过TCP是面向连接的,UDP我们上篇博客也写过了,我们发现UDP服务端客户端写好启动直接就发消息了没有建立连接。TCP是建立连接的,注定在写的时候肯定有写不一样的地方。具体怎么不一样,我们

    2024年04月15日
    浏览(39)
  • linux【网络编程】之网络套接字预备

    在【网络基础】中我们提到了IP地址,接下来了解一下网络通信中其他方面的知识 端口号是一个2字节16位的整数; 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理; 一个端口号只能被一个进程占用 通信原理 (公网)IP唯一标识一台主机,这样两台

    2024年02月05日
    浏览(33)
  • 【Linux网络】网络编程套接字(TCP)

    目录 地址转换函数 字符串IP转整数IP 整数IP转字符串IP 关于inet_ntoa 简单的单执行流TCP网络程序 TCP socket API 详解及封装TCP socket  服务端创建套接字  服务端绑定  服务端监听  服务端获取连接  服务端处理请求 客户端创建套接字 客户端连接服务器 客户端发起请求 服务器测试

    2024年03月21日
    浏览(44)
  • 【Linux】网络编程套接字一

    上篇博客由唐僧的例子我们知道: 在IP数据包头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。 思考一下: 不考虑中间的一系列步骤,两台主机我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子,有了IP地址能够把消息发送到对方的机器上。 但是我们把

    2024年03月26日
    浏览(80)
  • Linux网络编程(二-套接字)

    目录 一、背景知识 1.1 端口号 1.2 网络字节序 1.3 地址转换函数  二、Socket简介 三、套接字相关的函数  3.1 socket() 3.2 bind() 3.3 connect() 3.4 listen() 3.5 accept()  3.6 read()/recv()/recvfrom() 3.7 send()/sendto()  3.8 close()  四、UPD客服/服务端实验  1.1 端口号 端口号是访问服务器的标识 ,就好像

    2024年01月22日
    浏览(30)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(43)
  • Linux网络编程——tcp套接字

    本章Gitee仓库:tcp套接字 客户端: 客户端: 关于构造和初始化,可以直接在构造的时候,将服务器初始化,那为什么还要写到 init 初始化函数里面呢? 构造尽量简单一点,不要做一些“有风险”的操作。 tcp 是面向连接的,通信之前要建立连接,服务器处于等待连接到来的

    2024年02月20日
    浏览(32)
  • 【Linux】网络基础+UDP网络套接字编程

    只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事。 1. 首先计算机是人类设计出来提高生产力的工具,而人类的文明绵延至今一定离不开人类之间互相的协作,既然人类需要协作以完成更为复杂的工作和难题,所以计算机作为人类的工具自然也一定需要

    2024年02月08日
    浏览(39)
  • 【Linux】网络---->套接字编程(TCP)

    TCP的编程流程:大致可以分为五个过程,分别是准备过程、连接建立过程、获取新连接过程、消息收发过程和断开过程。 1.准备过程:服务端和客户端需要创建各自的套接字,除此之外服务端还需要绑定自己的地址信息和进行监听。注意:服务端调用listen函数后,处理监听状

    2024年02月04日
    浏览(39)
  • 【Linux网络】网络编程套接字(预备知识+UDP)

    目录 预备知识 1. 理解源IP地址和目的IP地址 2. 理解源MAC地址和目的MAC地址 3. 认识端口号  4. 理解源端口号和目的端口号 5. 端口号(port) vs 进程pid 6. 认识TCP协议和认识UDP协议 7. 网络字节序 socket编程接口  1. socket 常见API 2. sockaddr结构  简单的UDP网络程序  1. 服务端创建udp

    2024年02月19日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包