【Linux】Socket编程—TCP
文章目录
- 1. TCP socket API 详解
- socket()
- bind()
- listen()
- accept()
- connect()
- 2. Echo Server
- TCP服务器
- 多进程版本
- 多线程版本
- 线程池版本
- TCP客户端
1. TCP socket API 详解
下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中。
socket()

- 作用:打开一个网络通讯端口,如果成功的话,就像
open()一样返回一个文件描述符; 应用程序可以像读写文件一样用 read/write 在网络上收发数据; - 返回值:如果 socket()调用出错则返回-1;
- 参数:对于 IPv4, family 参数指定为
AF_INET; 对于 TCP 协议,type 参数指定为SOCK_STREAM, 表示面向流的传输协议; protocol 参数的介绍从略,指定为 0 即可。
bind()

-
介绍:服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号;
-
返回值: bind()成功返回 0,失败返回-1。
-
作用:将参数
sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号; -
参数: 前面讲过,
struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;我们的程序中对 myaddr 参数是这样初始化的:

1. 将整个结构体清零;
2. 设置地址类型为AF_INET;
3. 网络地址为INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有
多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址;
4. 端口号为 SERV_PORT, 我们定义为 8080;
listen()

- 介绍:listen()声明
sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接
等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5); - 返回值:listen()成功返回 0,失败返回-1;
accept()

- 介绍:三次握手完成后, 服务器调用 accept()接受连接; 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
- 参数:
addr是一个传出参数,accept()返回时传出客户端的地址和端口号; 如果给addr参数传NULL,表示不关心客户端的地址;addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
我们的服务器程序结构是这样的:

- 返回值:sockfd用来进行通信
connect()

- 介绍:客户端需要调用 connect()连接服务器;
- 参数:
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而
connect的参数是对方的地址; - 返回值: connect()成功返回 0,出错返回-1;
2. Echo Server
有了上面的接口,我们就可以实现以TCP为基础的简单消息回显服务器了,运行结果应该如下图所示:

代码如下:
TCP服务器
#pragma once#include <iostream>
#include <string.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"#define BACKLOG 8
using namespace InetAddrModule;
using namespace LogModule;
static const uint16_t defaultport = 8888;class TcpServer
{
public:TcpServer(uint16_t port = defaultport) : _port(port), _listensockfd(-1), _isruning(false){}void InitServer(){// 1.创建Tcp套接字_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(LogLevel::ERROR) << "InitServer socket fail ...";Die(SOCKET_ERR);}// 填充信息struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = ::htons(_port);//aaa注意要转网络!!!!!!!!!!serveraddr.sin_addr.s_addr = INADDR_ANY; // 表示可以接收任意地址的信息// 2. bind;int n = ::bind(_listensockfd, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "InitServer bind fail ...";Die(BIND_ERR);}// 3.监听int m = ::listen(_listensockfd, BACKLOG);if (m < 0){LOG(LogLevel::ERROR) << "InitServer listen fail ...";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "ServerInit success...";}void handler(int sockfd){char buffer[4096];while (true){ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;LOG(LogLevel::INFO) << buffer;std::string echo_string = "server echo# ";echo_string += buffer;::write(sockfd, echo_string.c_str(),echo_string.size());}else if (n == 0) // client 退出{LOG(LogLevel::INFO) << "client quit: " << sockfd;break;}else{// 读取失败break;}}::close(sockfd); // fd泄漏问题!}void Start(){_isruning = true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO)<<"ServerStart success...";// 连接成功后就可以通信handler(sockfd);}_isruning = false;}~TcpServer(){}private:uint16_t _port;int _listensockfd;bool _isruning;
};
与Udp服务器不同的是,Tcp服务要求我们先调用
listen接口监听,然后在通过accept和客户端使用connet建立连接后才可以进行通信;所以如果仅仅使用单进程是无法满足同时接收多个客户端的消息,下面将会给出多进程、多线程以及基于线程池实现的Tcp服务。
多进程版本
//其他的不变
void Start(){_isruning = true;while(_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO)<<"ServerStart success...";// 连接成功后就可以通信//version1: 多进程pid_t id = ::fork();if(id == 0)//子进程{::close(_listensockfd);//要关掉不需要的文件描述符,避免fd泄露问题if(fork())//子进程再创建孙子进程::exit(0);//让子进程退出,孙子进程成为孤儿进程,这样就不用父进程回收//孙子进程处理,结束后由操作系统回收handler(sockfd);::exit(0);}::close(sockfd);//子进程退出后,父进程就不会阻塞在这里,继续接收其他客户端连接int rid = ::waitpid(id, nullptr, 0);if(rid < 0)LOG(LogLevel::WARNING) << "ServerStart waitpid error...";}_isruning = false;}
对于多进程,首先每个进程都有自己的文件描述符表,所以父子进程都需要关闭自己不需要的文件描述符;
其次父进程需要等待回收子进程,此时父进程会阻塞直到子进程完成通信,这样和之前单进程通信效果一样,所以为了不让父进程阻塞,子进程需要再创建子进程,用它来完成通信,此时父进程就可以直接回收子进程,孙子进程就成为孤儿进程进行通信,结束后由操作系统回收。
多线程版本
struct ThreadData{int sockfd;TcpServer *self;};static void *ThreadEntry(void *args){pthread_detach(pthread_self()); // 线程分离,线程执行结束后自动被系统回收ThreadData *data = (ThreadData *)args;data->self->handler(data->sockfd);return nullptr;}void Start(){_isruning = true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO) << "ServerStart success...";// 连接成功后就可以通信// version2: 多线程// 主线程和新线程共享一张文件描述符表pthread_t tid;ThreadData *data = new ThreadData;data->sockfd = sockfd;data->self = this;pthread_create(&tid, nullptr, ThreadEntry, data);
}_isruning = false;}
设置线程分离这样线程执行完毕后就可以自动被系统回收
线程池版本
using task_t = std::function<void()>;void Start(){_isruning = true;while (_isruning){struct sockaddr_in peer;socklen_t peerlen = sizeof(peer); // 这个地方一定要注意,要不然,会有问题!LOG(LogLevel::DEBUG) << "accepting ...";// 我们要获取client的信息:数据(sockfd)+client socket信息(accept || recvfrom)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::ERROR) << "StartServer accept fail ...";continue; // 继续接收}LOG(LogLevel::INFO) << "ServerStart success...";// version-3:线程池版本 比较适合处理短任务,或者是用户量少的情况ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd](){ this->handler(sockfd); });}_isruning = false;}
引入之前实现的线程池,并使用单例模式
使用服务器代码如下:
#include "TcpServer.hpp"int main()
{std::unique_ptr<TcpServer> tcpserver = std::make_unique<TcpServer>();tcpserver->InitServer();tcpserver->Start();return 0;
}
TCP客户端
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;
using namespace InetAddrModule;int sockfd = -1;//./udp_client server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){LOG(LogLevel::ERROR) << "Usage:" << argv[0] << " serverip serverport";Die(ARGV_ERR);}// 1.创建sockfdsockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){LOG(LogLevel::WARNING) << "client sockfd fail...";Die(SOCKET_ERR);}// 2.填充服务器信息std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// InetAddr serveraddr(serverip, serverport); struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(serverport);serveraddr.sin_addr.s_addr = inet_addr(serverip.c_str());// 3.与服务器建立连接int n = ::connect(sockfd, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "ClientConnet fail...";Die(CONNET_ERR);}// 4. 发送请求给服务器while (true){// 4.1获取信息std::cout << "Please Enter# ";std::string message;std::getline(std::cin, message);// 4.2发送信息给服务器ssize_t n = ::sendto(sockfd, message.c_str(), sizeof(message), 0, CONV(&serveraddr), sizeof(serveraddr));if (n < 0){LOG(LogLevel::ERROR) << "client sendto fail...";continue;}// 4.3从服务器接收信息char buffer[1024];struct sockaddr_in tmp;socklen_t len = sizeof(tmp);ssize_t m = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&tmp), &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{LOG(LogLevel::ERROR) << "client recvfrom fail...";}}::close(sockfd);return 0;
}
与UDP客户端相比,TCP客户端需要与服务器通过
connet连接后才能通信。
相关文章:
【Linux】Socket编程—TCP
🔥 个人主页:大耳朵土土垚 🔥 所属专栏:Linux系统编程 这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉 文章目…...
c++11 for auto不定参数
数量不定的模板参数。参数分为一个和一包两部分。 冒号的左边声明一个变量。右手边必须是一个容器。从容器(某种数据结构)中找出每一个元素设置到左边这个变量。11之前可以用容器的迭代器去取数据。或者标准库里的foreach...
C#+redis实现消息队列的发布订阅功能
代码 参考c#redis stream实现消息队列以及ack机制文章的思路,实现 SubscribeAttribute.cs using System;namespace DotnetQueue.Attributes {/// <summary>/// 订阅特性/// </summary>[AttributeUsage(AttributeTargets.Method, Inherited false)]pu…...
Docker容器基本操作
容器的基本操作 操作命令(全)命令(简)容器的创建docker container run <image name>docker run <image name>容器的列出(up)docker container lsdocker ps容器的列出(up和exit&…...
从无序到有序:上北智信通过深度数据分析改善会议室资源配置
当前企业普遍面临会议室资源管理难题,预约机制不完善和临时会议多导致资源调度不合理,既有空置又有过度拥挤现象。 针对上述问题,上北智信采用了专业数据分析手段,巧妙融合楼层平面图、环形图、折线图和柱形图等多种可视化工具&a…...
总结:使用JDK原生HttpsURLConnection,封装HttpsUtil工具类,加载自定义证书验证,忽略ssl证书验证
总结:使用JDK原生HttpsURLConnection,封装HttpsUtil工具类,加载自定义证书验证,忽略ssl证书验证 一HttpsUtil工具类二SSLUtil工具类 一HttpsUtil工具类 package com.example.util;import javax.net.ssl.HttpsURLConnection; impo…...
重新定义人机关系边界,Soul以AI社交构建多元社交元宇宙
近年来,AI Native应用的兴起已逐渐成为大众关注的焦点。在此背景下,Soul App的首席技术官陶明在极客公园IF2025创新大会上,发表了一场主题为“人机关系的新边界,Soul如何定义AI社交未来”的演讲。他分享了Soul在人工智能领域内的最新技术进展和战略规划,同时也将Soul社交元宇宙…...
HTTP 参数污染(HPP)详解
1. 什么是 HTTP 参数污染(HPP)? HTTP 参数污染(HTTP Parameter Pollution,简称 HPP)是一种 Web 应用攻击技术,攻击者通过在 HTTP 请求中注入多个相同的参数来绕过安全控制或篡改应用逻辑&#…...
阿里云轻量服务器docker部署nginx
拉取nginx docker镜像 sudo docker pull nginx创建以下挂载目录及文件 用户目录下:conf html logs conf: conf.d nginx.conf html: index.html conf.d: default.confnginx.conf添加文件内容 events {worker_connections 1024; }http {include /etc/ngi…...
(萌新入门)如何从起步阶段开始学习STM32 —— 我应该学习HAL库还是寄存器库?
概念 笔者下面需要介绍的是库寄存器和HAL库两个重要的概念,在各位看完之后,需要决定自己的学习路线到底是学习HAL呢?还是寄存器呢?还是两者都学习呢? 库寄存器 库寄存器就是简单的封装了我们对寄存器的操作…...
Windchill开发-电子仓相关对象信息查询SQL
电子仓相关对象信息查询SQL 一、说明二、数据表信息三、数据表字段说明3.1 HOLDERTOCONTENT3.1.1 对象类型3.1.2 存储类型 3.2 APPLICATIONDATA3.2.1 类别3.2.2 与对象的角色关系3.2.3 存储方式3.2.4 其他字段 3.3 URLDATA3.4 STREAMDATA3.5 FVITEM3.6 FVMOUNT3.6.1 安装状态3.…...
MySQL 数据库定时任务及进阶学习
一、引言 在当今数字化时代,数据管理的高效性和自动化至关重要。MySQL 作为一款广泛应用的开源关系型数据库管理系统,提供了强大的功能来满足各种数据处理需求。其中,定时任务执行功能对于自动化数据操作、维护数据完整性以及优化系统性能具…...
DeepSeek教unity------MessagePack-01
中文:GitCode - 全球开发者的开源社区,开源代码托管平台 MessagePack是C# 的极速 MessagePack 序列化器。它比 MsgPack-Cli 快 10 倍,并且性能超过其他 C# 序列化器。MessagePack for C# 还内置支持 LZ4 压缩——一种极其快速的压缩算法。性能在诸如游戏…...
知识拓展:Python序列化模块 marshal 模块详解
Python marshal 模块学习笔记 1. 简介 marshal 是 Python 的内部序列化格式,主要用于序列化和反序列化 Python 对象。它是 Python 字节码(.pyc文件)使用的序列化格式,比 pickle 更原始和受限,但也更快速和安全。 http…...
leetcode 2684. 矩阵中移动的最大次数
题目如下 数据范围 本题使用常规动态规划就行,不过要注意由于有三个转移的方向,所以我们对dp数组的遍历应该是从上到下 从左到右即按列优先遍历。通过代码 class Solution { public:int maxMoves(vector<vector<int>>& grid) {int …...
机械学习基础-6.更多分类-数据建模与机械智能课程自留
data modeling and machine intelligence - FURTHER CLASSIFICATION 混淆矩阵评估指标:灵敏度和特异度ROC 曲线文字说明部分 AUC:ROC曲线下面积 支持向量机思路补充背景知识点积超平面(HYPERPLANES超平面的法向量到超平面的最小距离数据集与超…...
自动化测试实战
http://8.137.19.140:9090/blog_login.htm 账号: lisi 密码: 123456 上面是系统链接 1. 自动化测试的步骤 1.1 编写Web测试用例 1.2 创建空项目添加依赖 然后我们创建一个新的java项目(使用maven管理),然后引入我们的配置文件:屏幕截图,驱动管理,selenium库 <dependency…...
qt QPlainTextEdit总结
QPlainTextEdit 概述 用途:专为处理纯文本设计,适合大文本编辑和简单文本显示(如日志、代码编辑器)。 特点:相比QTextEdit,轻量高效,支持快速加载和滚动大文件,默认不支持富文本。 …...
AWS SES 邮件服务退信/投诉处理与最佳实践指南
在使用 AWS SES 发送邮件时,合理处理退信和投诉是维护发送声誉的关键。本文将详细介绍 SES 中的退信/投诉处理机制以及相关最佳实践。 一、退信处理机制 © ivwdcwso (ID: u012172506) 1.1 退信类型 在 SES 中,退信分为两种类型: 硬退信(Hard Bounce) 永久性错误,如无效…...
理解WebGPU 中的 GPUAdapter :连接浏览器与 GPU 的桥梁
在 WebGPU 开发中, GPUAdapter 是一个至关重要的对象,它作为浏览器与 GPU 之间的桥梁,为开发者提供了请求 GPU 设备、查询 GPU 特性以及获取适配器信息的能力。本文将详细介绍 GPUAdapter 的核心属性和方法,并通过实际代码…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
