当前位置: 首页 > news >正文

Linux编程: 10、线程池与初识网络编程

今天我计划通过一个小型项目,系统讲解线程池与网络编程的核心原理及实践。项目将围绕 “利用线程池实现高并发网络通信” 这一核心需求展开,具体设计如下:

  • 为保证线程安全,线程池采用单例模式设计,确保全局唯一实例避免资源竞争;
  • 技术栈采用 C/C++ 混合编程:网络通信、线程管理、同步锁等底层操作直接调用 Linux 系统接口(贴合 Linux 编程的 C 语言风格),其他业务逻辑及封装层则使用 C++ 实现,兼顾底层效率与代码的可维护性。

一、什么是线程池

线程池是一种线程管理机制,简单来说,它是一组预先创建好的线程的集合,这些线程在程序启动时或需要时被初始化并待命,当有任务到来时,线程池会分配一个空闲线程来处理任务,任务完成后线程不会被销毁,而是回到线程池等待下一个任务。

它的核心作用是避免频繁创建和销毁线程带来的性能开销。因为线程的创建、切换、销毁都需要操作系统内核进行资源调度,在高并发场景下(比如大量网络请求、频繁的任务处理),如果每次处理任务都新建线程,会导致系统资源(CPU、内存)被大量消耗,甚至引发系统不稳定。

线程池的主要组成部分通常包括:

  • 线程队列:存储所有等待或正在运行的线程
  • 任务队列:存放待处理的任务,当没有空闲线程时,新任务会暂时进入队列等待
  • 管理机制:负责线程的创建、回收、任务分配以及线程池大小的动态调整(可选)

在实际应用中,线程池可以高效处理大量并发连接:当新的网络请求到达时,无需为每个请求单独创建线程,而是直接交给线程池中的空闲线程处理,既保证了响应速度,又避免了资源浪费。

 

1. 线程池基础配置

图中 “线程池” 里标注了线程数量:3,意思是线程池预先创建并维护着 3 个空闲线程,随时准备处理任务。

2. 任务提交与分配

右侧的任务 1、任务 2、任务 3,代表 3 个待处理的任务。因为线程池有 3 个空闲线程,所以这 3 个任务会直接分配给线程池里的空闲线程,同时开始执行。

3. 任务排队等待

任务 4到来时,线程池里的 3 个线程已经被前面的任务占用了(都在忙)。此时,任务 4 会进入阻塞状态,被放进线程池的 “任务队列” 里,等待有线程处理完任务后,再重新变成空闲线程来执行它


二、项目开始前的配置文件

我这里用的编辑器是vscode,调试器是lldb,lsp是clangd,操作系统是centos10,也用了cmake,当然,你们也可以用其它的编译环境

1、launch.json

{"version": "0.2.0","configurations": [{"type": "lldb","request": "launch","name": "Debug","program": "${workspaceFolder}/bin/server",  "args": [],"cwd": "${workspaceFolder}","internalConsoleOptions": "neverOpen","console": "integratedTerminal"}]
}

2、.clang-format

BasedOnStyle: LLVM
AccessModifierOffset: -2       # 访问修饰符(public/private)缩进减少 2 格
AlignAfterOpenBracket: Align   # 开括号后的内容对齐
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AllowShortFunctionsOnASingleLine: Empty  # 空函数允许单行
BraceWrapping:AfterClass: trueAfterControlStatement: falseAfterEnum: trueAfterFunction: trueAfterNamespace: falseAfterStruct: trueAfterUnion: trueBeforeCatch: trueBeforeElse: trueIndentBraces: falseSplitEmptyFunction: trueSplitEmptyRecord: trueSplitEmptyNamespace: true
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: true
Cpp11BracedListStyle: true
DerivePointerAlignment: false
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
SpaceBeforeAssignmentOperators: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
Standard: Cpp11
TabWidth: 4
UseTab: Never

3、CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(server)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)# 头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)file(GLOB_RECURSE SOURCES "${PROJECT_SOURCE_DIR}/src/*.*")# 可执行文件
add_executable(server ${SOURCES} main.cpp)# 链接线程库(必须!)
target_link_libraries(server pthread)

4、项目结构


三、实现单例线程池

1、任务的基类:Task.h

#ifndef TASK_H
#define TASK_Hclass Task
{
public:virtual ~Task() = default;virtual void run() = 0;
};#endif  // !TASK_H

2、线程池的相关接口:ThreadPool.h

#ifndef THREAD_POOL_H
#define THREAD_POOL_H#include "Task.h"
#include <atomic>
#include <cstddef>
#include <cstdio>
#include <pthread.h>
#include <queue>
#include <vector>class ThreadPool
{public:ThreadPool(const ThreadPool&)           = delete;ThreadPool operator=(const ThreadPool&) = delete;~ThreadPool();static ThreadPool& getInstance();// 添加任务到线程池void addTask(Task* task);private:ThreadPool(size_t pool_size = 3);// 工作线程函数static void* worker(void* arg);// 工作循环函数void work();// 开始\停止void start();void stop();private:size_t                 _pool_size;  // 线程池数量std::vector<pthread_t> _threads;    // 线程数组std::queue<Task*>      _tasks;      // 任务队列pthread_cond_t         _cond;       // 条件变量pthread_mutex_t        _mutex;      // 锁std::atomic<bool>      _is_stop;    // 是否暂停
};#endif  // !THREAD_POOL_H

3、构造函数与获取单例对象

ThreadPool::ThreadPool(size_t pool_size): _pool_size(pool_size), _is_stop(true)
{// 初始化互斥量和条件变量pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);// 线程池开始运行start();
}ThreadPool& ThreadPool::getInstance() {static ThreadPool instance;return instance;
}
  1. 构造函数 ThreadPool::ThreadPool(size_t pool_size)

    • 初始化列表中设置了线程池大小_pool_size和停止标志_is_stop(初始为true表示未运行)
    • 调用pthread_mutex_initpthread_cond_init初始化了互斥锁和条件变量(线程同步的核心工具)
    • 最后调用start()方法启动线程池(实际创建并启动工作线程)
  2. 单例获取函数 ThreadPool::getInstance()

    • 使用 C++11 的static局部变量特性实现单例模式
    • 局部静态变量instance会在第一次调用时初始化,且保证线程安全
    • 每次调用都返回同一个实例的引用,确保整个程序中只有一个线程池实例

4、工作线程函数与工作循环函数

void* ThreadPool::worker(void* arg) {auto* pool = static_cast<ThreadPool*>(arg);pool->work(); return nullptr;
}void ThreadPool::work()
{while(!_is_stop) {// 加锁pthread_mutex_lock(&_mutex);// 只有运行时且任务队列为空时才会等待while(!_is_stop && _tasks.empty()) {pthread_cond_wait(&_cond, &_mutex);}// 运行时若停止了,则退出if(_is_stop) {pthread_mutex_unlock(&_mutex);break;}auto* task = _tasks.front();_tasks.pop();pthread_mutex_unlock(&_mutex);if(task) {task->run();delete task;}}
}
  1. worker 函数
    这是线程的入口函数(符合 pthread 库对线程函数的要求),作用是:

    • 通过static_cast将传入的void*参数转换为线程池实例指针
    • 调用线程池的work()方法,让线程进入实际的任务处理循环

    它相当于一个适配层,将 pthread 库的 C 风格函数接口与 C++ 的类方法衔接起来。

  2. work 函数
    这是线程的核心工作循环,实现了 "等待 - 取任务 - 执行" 的逻辑:

    • 循环判断:通过!_is_stop控制线程是否退出
    • 加锁保护:操作任务队列前先加互斥锁_mutex,保证线程安全
    • 等待机制:当任务队列为空且线程池未停止时,通过pthread_cond_wait让线程进入等待状态(释放锁并阻塞,直到被唤醒)
    • 退出检查:被唤醒后先判断是否需要停止,若需停止则解锁并退出循环
    • 处理任务:从队列取第一个任务,解锁后执行任务的run()方法,完成后释放任务对象
    • 线程复用:任务执行完后回到循环开头,继续等待新任务,实现线程的复用

5、线程池开启函数

void ThreadPool::start()
{// 如果已经是启动状态]if(!_is_stop) {return;}_is_stop = false;_threads.reserve(_pool_size);for(size_t i = 0; i < _pool_size; i++) {pthread_t pth;if(pthread_create(&pth, nullptr,worker, this) != 0){perror("create thread failed");_is_stop = true;return;}_threads.push_back(pth);}
}
  1. 启动状态检查
    首先判断_is_stop标志,如果线程池已经处于运行状态(_is_stopfalse),则直接返回,避免重复启动。

  2. 初始化准备
    _is_stop设为false(标记线程池进入运行状态),并通过_threads.reserve(_pool_size)预先为存储线程 ID 的容器分配内存,提升后续插入效率。

  3. 创建工作线程
    循环 _pool_size 次(线程池预设的线程数量),每次调用pthread_create创建一个线程:

    • 线程入口函数为worker(之前实现的线程工作函数)
    • 传入this指针作为当前线程池实例指针,让工作线程能访问线程池的任务队列等资源
  4. 错误处理
    若线程创建失败(pthread_create返回非 0),则通过perror打印错误信息,将_is_stop重置为true(标记线程池停止),并退出函数。

  5. 记录线程 ID
    成功创建的线程 ID(pth)会被存入_threads容器,便于后续管理(如停止线程池时回收线程)。

6、线程池停止函数

void ThreadPool::stop()
{// 如果已停止if(_is_stop) return;_is_stop = true;pthread_cond_broadcast(&_cond);// 等待所有线程结束for(pthread_t& pth : _threads) {pthread_join(pth, nullptr);}_threads.clear();// 清空任务队列pthread_mutex_lock(&_mutex);while (!_tasks.empty()){auto* task = _tasks.front();_tasks.pop();if(task) {delete task;}}pthread_mutex_unlock(&_mutex);
}
  1. 停止状态检查
    首先判断_is_stop标志,如果线程池已经处于停止状态,则直接返回,避免重复停止操作。

  2. 触发停止机制

    • _is_stop设为true(标记线程池进入停止状态)
    • 调用pthread_cond_broadcast(&_cond)唤醒所有等待在条件变量上的工作线程(避免线程一直阻塞在等待任务的状态)
  3. 回收工作线程
    遍历存储线程 ID 的_threads容器,通过pthread_join等待每个工作线程执行完毕并回收资源,最后清空容器。这一步确保所有线程都正常退出,避免僵尸线程。

  4. 清理任务队列

    • 加锁保护任务队列操作
    • 循环清空队列中剩余的未执行任务,逐个释放任务对象的内存
    • 解锁完成清理

7、添加任务进任务队列

void ThreadPool::addTask(Task* task)
{if(_is_stop || !task) return;pthread_mutex_lock(&_mutex);_tasks.push(task);pthread_cond_signal(&_cond);        // 唤醒一个线程pthread_mutex_unlock(&_mutex);
}
  1. 参数与状态检查
    先判断线程池是否已停止(_is_stoptrue)或任务指针为空,若满足任一条件则直接返回,避免向已停止的线程池添加任务或添加无效任务。

  2. 线程安全的任务入队

    • 加锁(pthread_mutex_lock):确保多线程同时添加任务时,对任务队列_tasks的操作是线程安全的
    • 入队(_tasks.push(task)):将新任务添加到任务队列尾部
    • 唤醒线程(pthread_cond_signal):发送信号唤醒一个正在等待的工作线程(之前在work方法中通过pthread_cond_wait等待的线程),通知有新任务可处理
    • 解锁(pthread_mutex_unlock):释放锁,允许其他线程操作任务队列

8、析构函数

ThreadPool::~ThreadPool()
{stop();pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);
}
  1. 调用 stop () 方法
    首先调用stop(),确保线程池在销毁前已经停止运行:包括唤醒所有工作线程、回收线程资源、清理未执行的任务等(这些逻辑已在stop()中实现)。这一步是为了避免线程池对象销毁后,仍有线程在后台运行或资源未释放的情况。

  2. 销毁同步机制

    • 调用pthread_mutex_destroy(&_mutex)销毁互斥锁,释放其占用的系统资源
    • 调用pthread_cond_destroy(&_cond)销毁条件变量,同样释放相关系统资源

四、服务器和客户端的通信流程

1、服务器端(像收件邮箱服务器)

  1. 新建 socket → 架起 “邮件接收系统”,准备收邮件
  2. bind 绑定 → 确定自己叫 xxx@qq.com ,让别人能找到
  3. listen 监听 → 开通 “同时收多封邮件” 功能,别一来就挤崩
  4. accept 等待 → 守着等你点 “发送”,接住你的邮件请求
  5. read/write 收发 → 收你发的邮件内容,还能回 “已收到” 提示
  6. close 关闭 → 这次发信结束,等下次你再发

2、客户端(像你用邮箱发信)

  1. 新建 socket → 打开手机邮箱 App,准备发邮件
  2. connect 连接 → 填对方邮箱点 “发送”,主动找服务器
  3. read/write 收发 → 写邮件、发出去,还能收到 “发送成功”
  4. close 关闭 → 发完关 App,结束这次发信

3、总结

网络通信操作邮箱发信类比通俗理解
socket打开邮箱 App / 搭建邮箱系统准备通信工具 / 服务
bind确定邮箱域名(如 qq.com )给服务定地址,让人找得到
listen开通 “同时收信” 队列限制并发,避免系统被挤爆
accept邮箱服务器 “接住” 你的邮件服务器受理客户端的连接请求
connect你点 “发送”,发起发信客户端主动连服务器
read/write写邮件、发邮件、收提示双方互相收发数据内容
close发完信关 App / 退出页面结束本次通信,释放资源

五、实现简易服务器

1、服务器相关接口:Server.h

#ifndef SERVER_H
#define SERVER_H#include <atomic>
class Server
{
public:Server(int port): _port(port), _is_stop(true) {}bool start();void stop();private:int _server_sock;int _port;std::atomic<bool> _is_stop;
};#endif // !SERVER_H

2、服务器开始运行

bool Server::start()
{// 1. 创建服务器套接字// AF_INET: 使用IPv4协议// SOCK_STREAM: 使用TCP协议(面向连接的流式传输)// 0: 使用默认协议(TCP)_server_sock = socket(AF_INET, SOCK_STREAM, 0);if(_server_sock  == -1) {  // 套接字创建失败std::cerr << "create socket failed" << std::endl;return false;}// 2. 设置套接字选项:允许端口重用// 解决服务器重启时"地址已在使用"的问题(端口未完全释放)int opt = 1;  // 选项值:1表示启用该选项// SOL_SOCKET: 通用套接字级别// SO_REUSEADDR: 允许端口重用选项if (setsockopt(_server_sock, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt)) == -1){std::cerr << "Failed to set socket options" << std::endl;close(_server_sock);  // 失败时关闭已创建的套接字return false;}// 3. 绑定套接字到指定地址和端口sockaddr_in server_addr;          // 存储服务器地址信息的结构体server_addr.sin_family = AF_INET; // 使用IPv4协议// 监听所有可用网络接口(服务器可能有多个网卡)server_addr.sin_addr.s_addr = INADDR_ANY;// 将端口号从主机字节序转换为网络字节序(大端序)server_addr.sin_port = htons(_port);// 绑定操作:将套接字与地址信息关联if(bind(_server_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) {std::cerr << "Failed to bind socket" << std::endl;close(_server_sock);  // 失败时释放资源return false;}// 4. 开始监听连接请求// 第二个参数5: 最大等待连接队列长度(超过的连接会被拒绝)if(listen(_server_sock, 5) == -1) {std::cerr << "Failed to listen on socket" << std::endl;close(_server_sock);  // 失败时释放资源return false;}// 5. 进入主循环,持续接受客户端连接_is_stop = false;  // 重置停止标志,开始运行while (!_is_stop) {sockaddr_in client_addr;      // 存储客户端地址信息socklen_t client_len = sizeof(client_addr);  // 地址结构体长度// 阻塞等待客户端连接,成功后返回客户端专属套接字int client_sock = accept(_server_sock, (sockaddr*)&client_addr, &client_len);if(client_sock == -1) {  // 接受连接失败if(!_is_stop) {  // 非主动停止时才打印错误std::cerr << "Failed to accept connection" << std::endl;}continue;  // 继续等待下一个连接}// 打印新连接的客户端IP地址std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr)<< std::endl;// 6. 将客户端连接交给线程池处理// 创建网络任务(封装客户端套接字),添加到线程池任务队列// 线程池会自动分配空闲线程处理该连接,实现并发处理ThreadPool::getInstance().addTask(new NetworkTask(client_sock));}return true;
}
  1. 创建服务器套接字
    通过socket(AF_INET, SOCK_STREAM, 0)创建 TCP 套接字(SOCK_STREAM表示流式协议,即 TCP),失败则返回错误。

  2. 设置套接字选项
    调用setsockopt设置SO_REUSEADDR选项,允许端口在服务器重启后快速重用(避免因端口未完全释放导致的启动失败)。

  3. 绑定地址和端口

    • 初始化server_addr结构体,指定 IPv4 协议(AF_INET)、监听所有网卡(INADDR_ANY)和端口(_port,通过htons转换为网络字节序)
    • 调用bind将套接字与地址端口绑定,失败则关闭套接字并返回。
  4. 开始监听连接
    listen(_server_sock, 5)启动监听,设置等待连接的队列长度为 5(最多同时有 5 个客户端在队列中等待处理)。

  5. 循环接受客户端连接

    • 进入while(!_is_stop)循环,持续等待客户端连接
    • accept函数阻塞等待新连接,成功后返回客户端套接字client_sock和客户端地址信息
    • 打印客户端 IP 地址,标识新连接建立
  6. 用线程池处理连接
    将客户端套接字封装成NetworkTask任务,通过线程池的addTask方法提交给线程池处理,实现高并发(避免为每个连接单独创建线程)。

3、服务器停止运行

void Server::stop()
{_is_stop = true;close(_server_sock);
}
  1. 设置停止标志
    _is_stop设为true,用于终止start()方法中接受连接的循环(while(!_is_stop)),让服务器退出等待新连接的状态。

  2. 关闭服务器套接字
    调用close(_server_sock)关闭服务器监听套接字,这会导致阻塞在accept()函数上的服务器线程被唤醒并退出,使服务器无法再接受新连接。

4、相关的接口函数

#include <sys/socket.h>int socket(int domain, int type, int protocol);功能:创建一个套接字参数1AF_INET  网络套接字(不同主机通过通络进行通信)AF_UNIX  文件系统套接字(本机内多进程之间通信)参数2
指定套接字的特性:当参数1是AF_INET是,参数可以选择:SOCK_STREAM 数据流服务,是面向连接的,更可靠的,使用TCP协议SOCK_DGRAM 数据报服务,使用UDP协议参数3
0: 表示使用默认的协议参数2为SOCK_STREAM的默认协议就是TCP参数3为SOCK_DGRAM的默认协议就是UDP返回值:成功,返回套接字对应的文件描述符;失败,返回-1
#include <sys/socket.h>int bind(int socket,const struct sockaddr *address,socklen_t address_len);功能:把套接字和地址绑定
参数1:服务器套接字
参数2:服务器的地址
参数3:参数2的长度
返回值:成功,返回0;失败,返回-1
#include <sys/socket.h>int listen(int socket, int backlog);功能:创建套接字队列服务器正在处理一个客户端的请求时,后续的客户请求就被放入队列等待处理。如果队列中等待处理的请求数超过参数 2,连接请求就会被拒绝。返回值:成功,返回 0;失败,返回-1
#include <sys/socket.h>int accept(int socket,struct sockaddr *restrict address,socklen_t *restrict address_len);功能:等待客户端的请求,直到有客户端接入。
参数1:服务器套接字
参数2:被接入服务器的客户端的地址
参数3:客户端地址的长度(注意,是一个指针)
返回值:成功,返回一个对应的客户端套接字失败,返回-1
#include <sys/socket.h>int connect(int socket,const struct sockaddr *address,socklen_t address_len);功能:客户端向指定的服务器发起连接请求。
参数1:套接字
参数2:服务器地址
参数3:地址的长度
返回值:成功,返回 0;失败,返回-1
#include <sys/socket.h>int setsockopt(int socket, int level, int option_name,const void *option_value, socklen_t option_len);功能:设置套接字的属性选项
参数1:要设置的套接字
参数2:选项级别(如SOL_SOCKET表示通用套接字选项)
参数3:具体选项(如SO_REUSEADDR表示允许端口重用)
参数4:选项值的指针
参数5:选项值的长度
返回值:成功返回0;失败返回-1

六、实现网络工作类

1、网络工作类的接口:NetworkTask.h

#ifndef NETWORK_TASK_H
#define NETWORK_TASK_H
#include "Task.h"class NetworkTask: public Task
{
public:NetworkTask(int sock) :_client_sock(sock) {}void run() override;private:int _client_sock;
};#endif // !NETWORK_TASK_H

2、实现run函数

#include "NetworkTask.h"
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>void NetworkTask::run() {char buff[BUFSIZ] = {0};while (1) {size_t read_bytes = read(_client_sock, buff, BUFSIZ - 1);if (read_bytes > 0) {std::cout << "接收: " << buff << std::endl;std::string response ="接收到" + std::to_string(strlen(buff)) + "个字符";int ret = write(_client_sock, response.data(), response.size());if (ret == -1) {perror("Error writing to socket");break;}memset(buff, 0, BUFSIZ);  // 清空缓冲区,准备下次读取}else if (read_bytes == 0) {// 客户端正常关闭连接std::cout << "客户端主动关闭连接" << std::endl;break;}else {// 读取错误perror("Error reading from socket");break;}}// 循环结束后关闭close(_client_sock);
}
  1. 初始化缓冲区
    创建BUFSIZ大小的字符数组buff(系统默认缓冲区大小,通常为 8192 字节),并初始化为 0,用于临时存储从客户端读取的数据。

  2. 进入通信循环
    通过while(1)开启无限循环,持续处理与客户端的交互:

    • 读取数据:调用read(_client_sock, buff, BUFSIZ-1)从客户端套接字读取数据,最多读取BUFSIZ-1字节(预留 1 字节给字符串结束符)。
    • 处理有效数据:若read_bytes>0(成功读取到数据):
      • 打印接收的内容到服务器控制台。
      • 构造响应字符串(格式为 “接收到 X 个字符”),通过write函数发送给客户端。
      • 若写入失败(ret==-1),打印错误并退出循环。
      • memset清空缓冲区,为下一次读取做准备。
    • 客户端断开连接:若read_bytes==0(客户端主动关闭连接),打印提示信息并退出循环。
    • 读取错误:若read_bytes<0(读取失败,如网络异常),用perror输出错误详情并退出循环。
  3. 清理资源
    循环结束后,调用close(_client_sock)关闭客户端套接字,释放该连接占用的资源。


 七、main函数与测试文件

1、主函数入口: main.cpp

#include "Server.h"
#include <cstdlib>
#include <iostream>int main()
{try{Server s(8000);std::cout << "Starting server..." << std::endl;if (!s.start()) {std::cerr << "Failed to start server" << std::endl;exit(1);}std::cout << "Server is running. Press Enter to stop..." << std::endl;std::cin.get();// 停止服务器和线程池s.stop();std::cout << "Server stopped successfully" << std::endl;}catch (const std::exception& e){std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}

2、测试用的客户端: test.c

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>int main()
{// 创建客户端套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址struct sockaddr_in address;address.sin_family      = AF_INET;address.sin_addr.s_addr = inet_addr("192.168.1.10");address.sin_port        = htons(8000);int ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));if (ret == -1) {perror("connect failed.");exit(1);}// 接收用户输入char buff[BUFSIZ];printf("Please input: ");fgets(buff, sizeof(buff), stdin);buff[strlen(buff) - 1] = 0;// 向服务器发送数据write(sockfd, buff, strlen(buff) + 1);// 读取服务器发回的数据read(sockfd, buff, sizeof(buff));printf("Received: %s\n", buff);// 关闭套接字close(sockfd);return 0;
}

3、运行结果:

// cmake编译后可调试的命令
cmake -DCMAKE_BUILD_TYPE=Debug ..

http://www.dtcms.com/a/310282.html

相关文章:

  • 通用障碍物调研
  • Java 大视界 -- Java 大数据机器学习模型在电商产品定价策略优化与市场竞争力提升中的应用(375)
  • 阿里云oss上传文件 普通上传和分片上传方法封装
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(7)
  • 小杰数据结构(four day)——藏器于身,待时而动。
  • PNP机器人机器人学术年会展示灵巧手动作捕捉方案。
  • 【高等数学】第七章 微分方程——第六节 高阶线性微分方程
  • C# StringBuilder类及其使用方法
  • 【LeetCode 热题 100】394. 字符串解码
  • 合并对象 递归注意对象的合并时机
  • 20257月29日-8月2日训练日志
  • Codeforces Round 1040 (Div. 2)(补题)
  • Java函数式编程之【基本数据类型流】
  • Thymeleaf 模板引擎原理
  • 删除MicroGame
  • 设计模式之职责链模式
  • Android 中 Intent 的显式和隐式使用方式
  • Alpine Linux 设置镜像的时区
  • ONLYOFFICE 深度解锁系列.14-如何在ONLYOFFICE表格中调用异步API,集成外部数据源
  • R语言基础图像及部分调用函数
  • MyEclipse启动OutOfMemoryError内存溢出
  • 笔试——Day25
  • 【数据结构入门】顺序表
  • linux81 shell通配符:[list],‘‘ ``““
  • AI数字人:会“呼吸”的虚拟人如何改变我们的世界?
  • 倒计时!2025国自然放榜时间锁定
  • DreamBoards 借助 DreamHAT+ 雷达插件为 Raspberry Pi 提供 60GHz 毫米波雷达
  • 使用Excel解析从OData API获取到的JSON数据
  • AR智能巡检系统:制造业设备管理的效率革新
  • 【难点】敬语