Linux——TCP协议与相关套接字编程

2023-05-16

一.TCP协议概念

与UDP协议相同,TCP协议也是应用在传输层的协议。虽然都是应用在传输层,但是使用方式和应用场景上大不一样。TCP协议具有:有连接(可靠)面向字节流的特点

(一).有连接

所谓有连接,指的是信息传递是建立在通信双方已经提前“取得了联系”,可以理解为提前创建了一个用于通信的管道。并且TCP的通信具有指向性,发送的信息只能“点对点”式的发送。而UDP协议下发送信息不需要连接,具体可以参考这篇文章:UDP协议与相关套接字编程

因为TCP的通信建立在双方取得联系的基础上,因此不存在数据丢失的问题。如果接收方没有收到信息,那么发送方会继续发送数据直到接收方获取到为止。这也就是为什么TCP协议下的网络通信具有可靠性

(二).面向字节流

和系统输入输出流一样,TCP协议也存在一个缓冲区。当传输数据时,如果数据太短那么会一直保存在缓冲区中,直到数据长度达到发送要求后统一发送给接收方。如果数据太长,那么会进行拆分,一部分一部分的进入缓冲区然后发送。这里需要注意的是因为缓冲区的存在,一份数据可能会拆开来发送,这样接收方一次收到的数据可能就是不完整的,因此需要自定义通信协议(检测数据是否已经完整)确保目标数据完整后再使用,具体方式在套接字编程代码中会说明。而UDP协议基于面向数据报的特点,不存在缓冲区的概念,因此接收数据的完整性得以保障。

二.TCP套接字编程

(一).系统调用接口

①获取套接字/创建套接字结构体/绑定

在UDP套接字编程的博客中已经介绍了相关接口和使用方式,不再赘述:UDP协议与相关套接字编程

TCP的使用方式与UDP基本一致,参数略有不同。

获取套接字:

socket函数第二个参数为套接字类型,TCP为面向字节流,即SOCK_STREAM,需要注意的是,TCP协议有两个套接字文件描述符,其中socket接口返回的叫做监听套接字,用于获取连接;还有一个服务套接字用于和对端网络通信。

int listensocket = -1;
listensocket = socket(AF_INET, SOCK_STREAM, 0);

创建结构体:

与UDP创建结构体方式一致。

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_port);
addr.sin_addr.s_addr = INADDR_ANY;

绑定:

与UDP绑定方式一致

int i = bind(_listensocket, (struct sockaddr *)&addr, sizeof(addr));
if (i < 0) ...

②设置监听状态

因为TCP协议具有连接性的特定,因此需要调用listen接口提前设置一个监听状态。

listen接口第一个参数为监听套接字。

第二个参数为最大连接数量,与TCP建立连接的对端都会记录在一个队列中,这个参数定义的就是该队列最大长度(队列长度为参数值+1)。当队列长度达到最大时,再有对端想要建立连接就会返回一个错误。参考如下:

设置监听成功会返回0,失败返回-1。

int i = listen(listensocket, 20);
if(i < 0) ...

③服务端等待连接

系统接口accept用于与对端获取连接。如果没有主机与本端连接,那么会阻塞在accept函数上。

参数方面,第一个是监听套接字。

第二个和第三个都是输出型参数,和recvfrom接口一样,用于获取对端中记录IP地址和端口号的结构体struct sockaddr。

当对端尝试与本机建立TCP连接后,如果accept成功,那么会返回服务套接字,之后的网络通信行为都使用服务套接字完成。如果accept失败,那么会返回-1,示意如下:

struct sockaddr_in addr;//用于获取对端IP地址&端口号
bzero(&addr, sizeof addr);
socklen_t addr_len;
int servesocket = accept(listensocket, (struct sockaddr*)&addr, &addr_len);
if(servesocket < 0) ...

④客户端连接

服务端使用accept阻塞后,客户端需要调用connect接口主动与服务端建立连接。

需要注意的是,与UDP协议一样,客户端在建立连接前不需要手动绑定自己的端口号和IP地址,采用系统自动绑定的方式让服务端获取。

接口参数方面,第一个参数为本端(客户端)套接字文件描述符(socket函数返回值)。

第二个参数是struct sockaddr类型,记录的是服务端IP地址和端口号,用于确定连接哪个服务器。

第三个参数记录的是第二个参数对象的长度。

连接成功返回0, 失败返回-1。

int sock = socket(...);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);//记录服务端的端口号
addr.sin_addr.s_addr = inet_addr(ip);//记录服务端的IP地址
int i = connect(sock, (struct sockaddr*)&addr, sizeof addr);
if(i < 0) ...

⑤发送数据

调用write/send系统接口发送数据。

因为服务套接字本质就是一个文件描述符,因此可以直接使用write接口发送数据,具体方式与普通文件别无二致。

send接口和write接口使用上差别不大,前三个参数含义相同,最后一个参数代表发送策略,一般填0。

但有一点需要强调,write和send的第一个参数在服务端是服务套接字,因为每一个客户端(对端)的服务套接字各不相同,使用服务套接字可以标定唯一的客户端。在客户端就采用socket接口返回的套接字文件描述符即可。

char buf[1024];
...//生产网络数据
//**********服务端
int i = send(servesocket, buf, strlen(buf), 0);
//or:   write(servesocket, buf, strlen(buf));

//**********客户端
int i = send(sock, buf, strlen(buf), 0);
//or:   write(sock, buf, strlen(buf));
if(i < 0) ...

⑥获取数据

调用recv/read接口获取数据。

道理同发送数据,不再赘述,看完示例就明白了:

char buf[1024];
memset(buf, 0, sizeof buf);
//***********服务端
int i = recv(servesocket, buf, sizeof buf, 0);
//or:   read(servesocket, buf, sizeof buf);
//***********客户端
int i = recv(sock, buf, sizeof buf, 0);
//or:   read(sock, buf, sizeof buf);
if(i <= 0) ...

但这里有一个疑问:

首先我们知道UDP协议通信中,接收数据时规定接收长度最大为buf大小-1。这是防止接收数据超过buf大小,导致不能设置最后一位为'\0':

int i = recvfrom(sockfd, buf, sizeof buf - 1, ...);
buf[i] = 0;

那为什么采用TCP协议接收数据时不再规定接收长度为size - 1呢?

这是因为TCP协议具有面向字节流的特点,也就是第一部分介绍面向字节流时说的一次传输数据可能不完整,需要多次传输。举个例子,假如客户端发送了"abcdefg"这个字符串,但是TCP的缓冲区太小,只存放了efg,那么服务端第一次接受的就是abcd,第二次接收时才能收到efg。因此接收长度应该是buf大小。

那么怎么判断何时数据全部接收了呢?需要自定义通信协议,在下一节会说明。

(二).模拟TCP服务端与客户端

①定制协议

基于上文,我们知道TCP协议的通信中一份数据可能是经过多次分批发送的。因此需要根据数据内容判断哪次接收后一份数据传输完毕。

怎么判断呢?我们可以这样:在一份数据的开头添加"数据长度"\r\n",结尾添加"\r\n"。

例:5\r\n abcde\r\n

接收数据时,首先收到数据长度和\r\n,知道有一份新的数据过来了且知道了数据长度。当再次收到\r\n,一份数据就完整传输完毕。当然这样的方式也可以理解为给数据添加了报头和报尾。

#define SEP "\r\n"
#define SEP_LEN strlen(SEP)
string Decode(string *str) // 解除自定义协议(获取数据)
{
    int pos = str->find(SEP);
    if (pos == string::npos)
        return "";
    int len = atoi(str->substr(0, pos).c_str());
    int size = str->size() - pos - SEP_LEN * 2;
    if (size >= len)
    {
        str->erase(0, pos + SEP_LEN);
        string ret = str->substr(0, len);
        str->erase(0, len + SEP_LEN);
        return ret;
    }
    else
        return "";
}
bool Encode(string *buf) // 添加自定义协议 length\r\nxxxxxxx\r\n
{
    string ret = std::to_string(buf->size());
    ret += SEP;
    ret += *buf;
    ret += SEP;
    *buf = ret;
    return true;
}

②序列化与反序列化

当然上文有一个疑点,如果数据内容不是字符串呢,比如结构体,那么协议该怎么定制呢。对于这个问题答案很简单,通常情况下在网络通信中,把要传入网络的数据全部转成字符串的形式即可。通俗来讲,比如结构体就把每一个数据成员转成一个字符串,这个过程就叫做序列化。将网络中的字符串型数据转成主机中目标类型(比如字符串转成结构体)的过程就叫做反序列化

图示如下:

③服务端

服务端的任务主要有三个:建立TCP通信,接收数据,发送数据。

当然,TCP的服务端处除了需要建立监听外,在连接一个客户端后,需要创建一个子进程负责和这个客户端通信,父进程则继续循环等待新的客户端来连接。除了上述方式完成多客户端连接,也可以使用多线程的方式。如果是采用多进程版的还需要注意一点,子进程需要关闭监听套接字因为子进程不用于获取连接;父进程要关闭服务套接字,因为父进程只用于建立与客户端的连接,如果不关闭的话随着连接客户端数量的增加会导致出现文件描述符不够的问题。

当接收到数据后,先将网络中的数据去掉自定义协议即去掉报头,之后将字符串类型的数据反序列化成主机对象类型。当处理完数据向客户端回传时,先将数据序列化成字符串类型便于网络传输,之后添加自定义协议(报头)。当然通信时采用accept返回的服务套接字。

伪代码如下:

class Server
{
    static void serveFunc(int sock, string ip, uint16_t port) // 用于子进程和客户端通信
    {
        ...//接收数据(去除协议+反序列化)、处理数据、发送数据(添加协议+序列化)
    }

public:
    ...
    void initServer()
    {
        ...//获取监听套接字、创建结构体、绑定
        listen(_listensocket, 20);//监听
    }
    void startServer()
    {
        signal(SIGCHLD, SIG_IGN);//分离父子进程,使子进程可以自动回收而不是变成僵尸进程
        while (true)//父进程循环,等待多个客户端连接
        {
            ...//创建接收客户端的结构体
            int servesocket = accept(_listensocket, (struct sockaddr *)&addr, &len);//连接客户端,获取服务套接字
            if (fork() == 0)//子进程负责和特定客户端通信
            {
                close(_listensocket);//子进程通信不需要监听套接字
                serveFunc(servesocket, ip, port);//通信函数
                exit(0);//子进程退出
            }
            close(servesocket);//父进程不需要服务套接字
        }
    }

private:
    string _IP;
    uint16_t _port;
    int _listensocket;
};

④客户端

客户端有三个任务:主动与服务端建立连接、发送数据、获取数据。

与服务端建立连接就采用connect接口,与UDP通信的客户端一样不需要自己绑定IP和端口号,而让操作系统自动绑定(避免一个主机上多个服务端同时绑定同一个IP地址和端口号)。

发送数据和获取数据的思路与服务端一致,发送数据前先序列化后添加协议,获取数据后先去除协议再反序列化。通信时用socket函数返回值即可。

伪代码如下:

int main(int argc, char* argv[])
 {
    ...//获取套接字、根据服务端IP和端口号创建结构体(用于connect)
    //无需bind    
    //建立与服务端连接
    int i = connect(sock, (struct sockaddr*)&addr, sizeof addr);
    ...//与服务端通信,发送数据、接收数据
    close(sock);
    return 0;
 }

如有错误,敬请斧正

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux——TCP协议与相关套接字编程 的相关文章

  • 【Unity编辑器】拓展Project视图

    目录 1 拓展右键菜单 2 创建一个菜单 3 拓展布局 4 监听事件 首先创建一个Editor文件夹 xff0c 此文件夹可以作为多个目录的子文件夹存在 xff0c 这样开发者就可以按照功能来划分 xff0c 将不同功能的编辑代码放在不同的
  • 【数据结构】详解顺序表

    目录 一 线性表 二 顺序表 1 顺序表的概念 2 顺序表的分类 1 静态顺序表 2 动态顺序表 3 顺序表的接口实现 三 顺序表的应用 四 总结 一 线性表 线性表 xff08 linear list xff09 是n个具有相同特性的数据
  • Windos10下Anaconda中jupyter notebook内核错误kernel error的一种解决方法

    问题描述 xff1a 在装anaconda3后 xff0c 打开jupyter notebook新建点击python3时总是显示kernel error试了几次都这样 相信很多人都有这样的困扰 xff0c 不管是卸载重装anaconda还是
  • #shell 使用shell编写一个自动备份数据库的脚本

    前言 这是一个基于shell编写能定期对数据库进行备份的bash脚本 xff0c 在系统实际运行中 xff0c 总会有各种意想不到的可能使系统宕机 xff0c 对数据库进行定期的备份可以最大程度的减少宕机造成的损失 xff0c 保障数据 环
  • 计蒜客 T1068 救援

    include lt stdio h gt include lt math h gt int main int i 61 0 int n 61 0 float x 61 0 y 61 0 int r 61 0 float k 61 0 fl
  • 计蒜客 T1079 开关灯

    include lt stdio h gt int a 5001 int main int m n k 61 1 scanf 34 d d 34 amp n amp m for int i 61 2 i lt 61 m i 43 43 fo
  • 7-15 找出最大和最小值

    从键盘输入n个整数 xff0c 找出其中的最大值和最小值 xff0c 并输出 输入格式 第1行为n xff0c 表示整数个数 xff1b 第2行为n个整数 xff0c 分别以空格隔开 输出格式 最大值和最小值 xff0c 以换行符分隔 输入
  • 如何在Debian 11上安装Docker Swarm集群

    Docker Swarm是一个用于管理 Docker 主机的工具 我们可以构建一个高可用性 高性能的Docker集群 xff0c 其中应用部署在许多服务器上 Docker swarm由管理主机和工作节点构建 您可以在Docker Swarm
  • javascript 数组方法 slice() 的使用说明

    slice 的英译为 切片的意思 xff0c 就是截取一个片段 javascript的数组对象有一个方法slice xff0c 通过索引位置从原数组中截取一个片段构成新的数组 xff0c 该方法不会修改原数组 xff0c 只是返回一个新的子
  • 洛谷p2078

    分析 xff1a 啊这 xff0c 居然让我这个单身汉来配情侣 xff0c 我可以配 1对吗 认真分析 xff1a 把小明的朋友和他朋友的朋友和他朋友的朋友的 的朋友的父节点都变成小明 xff0c 然后统计小明这个集合有多少元素 xff0c
  • 22. Spring Boot 整合 MyBatis-Plus

    文章目录 22 1 官方文档22 2 基本介绍22 3 整合 MyBatis Plus 实例22 3 1 需求说明 图解22 3 2 代码实现22 3 2 1 创建数据库和表22 3 2 2 创建 springboot mybatisplu
  • openjudge 1.4.11 晶晶赴约会

    OpenJudge 11 晶晶赴约会 解题思路 xff1a 1 判断晶晶是否可以去约会 xff0c 那么只需要判断输入的日期是否为1 3 5中的任意一个数字即可 2 定义整型变量a并输入 3 If else条件判断 xff0c 并输出对应内
  • HTML制作个人名片

    题目 xff1a 完成以下效果图 xff0c 上传代码和效果图 xff08 具体样式提示如下 xff1a 整个大盒子尺寸为 150 278 效果图居中对齐 xff0c 左右外边距50px 字体 xff1a 楷体 xff1b 2 段落设置 x
  • 原码,反码,补码的概念

    计算机里都是以补码 的形式存储数据 xff0c 电脑只能识别二进制的0和1 xff0c 一个字节 xff08 8位 xff09 为例 原码 xff1a 最高位符号位 xff0c 0代表正数 xff0c 1代表负数 xff0c 非符号位为该数
  • cmake编译opencv开源项目报错问题

    最近使用cmake在编译一个配置了opencv环境的c 43 43 开源项目时遇到如下问题 CMake Warning at D opencv4 OpenCVConfig cmake 176 message Found OpenCV Win
  • 第一个SpringBoot项目的创建

    目录 一 SpringBoot是什么 xff1f 初识springboot springboot的优点 二 SpringBoot项目的创建与简单运行 x1f351 使用idea创建springboot项目 x1f351 Spring Boo
  • 解决在vue3中使用reactive响应式,赋值后造成页面不改变的问题?

    文章目录 场景原因一 例子二 解决方法1 使用ref存储响应式数据2 在reactive中使用对象包裹要改变的数据3 for of循环push到reactive数据中 xff0c 不破坏数据结构 总结 场景原因 我们需要在vue3中使用服务
  • ubuntu交叉编译工具arm-linux-gcc安装

    1 安装交叉编译工具 xff1a arm linux gcc 安装包4 4 6 TQ210 release 20120720 tar bz2 环境 xff1a ubuntu 20 版 xff0c 已换清华源 1 1解压文件 提取解压1 1
  • MYSQL(1)----初阶

    一 新增 insert into 表名 values 列的值 xff0c 列的值 xff0c 列的值 xff0c 列的值 insert into 表名 字段1 xff0c 字段2 values 列的值 xff0c 列的值 列的值 xff0c
  • GitLab -- 创建新用户

    登录GitLab xff0c 点击最上面的扳手图标 xff0c 进入 管理区域 页面 xff0c 点击 新建用户 进入新建用户页面 xff0c 输入对应信息 信息确认后 xff0c 点击 创建用户 用户创建成功后 xff0c 点击 编辑 按

随机推荐

  • debian技能大赛 笔记 (一)静态IP 网卡重启

    一 Debian静态IP 1 使用vim打开 etc network interfaces配置文件 vim etc network interfaces 2 在配置文件里添加 auto 网卡号 allow hotplug 网卡号 iface
  • Debian技能大赛笔记(二)service 、systemctl等命令消失不见解决办法

    二 service systemctl等命令消失不见解决办法 1 使用命令进入root模式 wt 64 Rserver su root Password root 64 Rserver home wt 2 使用vim 使用vi或者nano都
  • debian技能大赛笔记(八)创建DISK RAID5 自动挂载

    一 问题 1 在虚拟机上添加四个1g的硬盘 2 创建raid5 其中一个作为热备盘 设备名为 md0 3 将md0设置为LVM 设备名为md0 4 格式化为ext4 文件系统 5 开机自动挂载到 data目录 添加硬盘 添加完几个硬盘虚拟机
  • Debian大赛笔记(十)修改系统语言

    1 在root用户下输入 dpkg reconfigure locales 选择ZH CN xff08 简体中文 xff09 按空格键选择 xff0c 选择成功后重启系统语言便会修改成功
  • Debian技能大赛笔记(11)欢迎信息内容配置 删除欢迎信息

    技能大赛里都有一个差不多的题就是做一个欢迎信息 大致就像下图 那怎么配置呢 1 进入配置文件 vim etc update motd d 10 uname 在配置文件里加入 uname snrvm printf n printf 2s Ch
  • 上班摸鱼看小说的最佳软件

    这个软件几乎满足了我对上班摸鱼的所有担忧 xff0c 比如上班的时候打开网页看下说 xff0c 那太明显了 xff0c 不说摄像头 xff0c 后台看一下浏览器历史记录就暴露的特别明显 xff0c 这合适吗 xff1f 老板来到你身后远远的
  • iOS-UI-导航控制器-导航栏

    文章目录 导航控制器导航控制器的基本创建方法导航控制器的基本框架如下图所示添加单个按钮 x1f518 添加多个按钮 x1f518 创建按钮数组导航控制器效果 导航控制器的切换VcRoot界面切换事件函数 导航控制器 导航控制器 xff1a
  • 【iOS开发】-UIViewController加载过程和生命周期

    文章目录 前言ViewController执行过程的探讨ViewControllerOne 函数介绍顺序引入ViewControllerSecond引入 ViewControllerOne点击执行到ViewControllerSecond的
  • 【C语言】魔方阵的实现(最全)

    魔方阵的实现 xff08 最全 xff09 一 什么是魔方阵 xff1f 魔方矩阵 xff0c 又称幻方 xff0c 是具有相同的行数和列数 xff0c 并在每行每列 对角线上的和都相等的矩阵 N阶幻方 xff0c 即将自然数1到排成N行N
  • 四种求最大公约数的算法 C / C++

    文章目录 前言一 辗转相除法1 算法简介2 算法描述3 代码及复杂度 二 穷举法 xff08 枚举法 xff09 1 算法简介2 算法描述3 代码及复杂度 三 更相减损法1 算法简介2 算法描述3 代码及复杂度 四 Stein算法 xff0
  • 安装使用supervisor来启动服务

    supervisor 使用方法 supervisor 官网 是一个unix的系统进程管理软件 xff0c 可以用它来管理apache nginx等服务 xff0c 若服务挂了可以让它们自动重启 当然也可以用来实现golang的守护进程 学完
  • 超详细讲解实现拓扑排序、关键路径

    今天 xff0c 小编带着大家来学习图中非常重要的一环 xff0c 拓扑排序和关键路径 xff01 目录 一 绪论 实际应用 二 拓扑排序 xff08 一 xff09 含义 xff08 二 xff09 实现原理 xff08 三 xff09
  • getline函数介绍

    今天 xff0c 小编将为大家讲解有关getline函数的相关知识 目录 一 cin getline char s streamsize n char delim 二 getline istream amp is string amp st
  • C++语法——详解运算符重载

    运算符重载是C 43 43 的一个重要特性 有了运算符重载 xff0c 在代码编写时能更好的实现封装 目录 一 运算符重载介绍 二 运算符重载形式 xff08 一 xff09 参数 xff08 二 xff09 返回值 xff08 三 xff
  • linux—常用gdb调试命令汇总

    目录 一 准备工作 二 调试命令 xff08 一 xff09 查看代码内容 xff08 l xff08 二 xff09 开始调试 xff08 r xff09 xff08 三 xff09 查看当前调试位置 xff08 where xff09
  • Linux——详细模拟实现shell(进程控制综合运用)

    在运行linux时 xff0c 我们总免不了需要输入各种指令让shell进行解析 xff0c 从而与系统进行交互 那么我们有没有可能自己自制一个简易的shell呢 xff1f 答案是当然没问题 目录 一 大体思路 二 具体实现 xff08
  • C++语法——详解虚继承

    目录 一 什么是虚继承 二 虚继承原理 三 虚继承使用注意事项 一 什么是虚继承 所谓虚继承 xff08 virtual xff09 就是子类中只有一份间接父类的数据 该技术用于解决多继承中的父类为非虚基类时出现的数据冗余问题 xff0c
  • Qt——基本介绍、详解对象树

    目录 一 基本介绍 二 对象树 一 基本介绍 创建qt项目是 xff0c 如果选择空窗口QWidget xff0c 那么mian函数中会有如下代码 xff1a include 34 myWindow h 34 include lt QApp
  • 项目:手把手实现高并发内存池

    一 前言 xff08 一 xff09 项目简介 高并发内存池 xff08 ConCurrentMemoryPool xff09 xff0c 其原型是google的开源项目tcmalloc 全称是t hread c ache malloc x
  • Linux——TCP协议与相关套接字编程

    一 TCP协议概念 与UDP协议相同 xff0c TCP协议也是应用在传输层 的协议 虽然都是应用在传输层 xff0c 但是使用方式和应用场景上大不一样 TCP协议具有 xff1a 有连接 xff08 可靠 xff09 面向字节流的特点 x