单线程 TCP/IP 服务器和客户端的实现

devtools/2024/9/20 4:00:48/ 标签: tcp/ip, 服务器, c++, 网络, 网络协议

单线程 TCP/IP 服务器和客户端的实现

文章目录

  • 单线程 TCP/IP 服务器和客户端的实现
    • 通信流程
      • 服务端
      • 客户端
    • 代码实现
      • 服务端
      • 客户端
    • 运行结果

通信流程

服务端

  1. socket:创建监听的文件描述符(socket) fd;
  2. bind:fd 和自身的 ip 和端口绑定;
  3. listen:为当前文件描述符设置监听,主要是设置客户端连接数量;
  4. accept:阻塞直到客户端请求连接;
    • 若收到客户端请求,那么将会创建一个新的 socket,即用作通信的文件描述符 cfd;此后即可使用 cfd 进行通信;
  5. recv:接收来自客户端的数据;
  6. send:向客户端发生数据;
  7. 结束连接;

客户端

  1. socket:建立通信的文件描述符 fd;注意此处的 fd 和服务器是不同的,该处的 fd 直接用于通信,而服务器中的第一个是用于监听;
  2. connect:设置好服务器的 ip 和端口后使用 connect 建立连接;
  3. send:将数据发送到服务器
  4. recv:收到服务器发送的数据;
  5. close:结束连接;

在这里插入图片描述

代码实现

服务端

  • socket:创建监听的文件描述符(socket) fd;
// 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCP
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {perror("socket fail...!\n");exit(0);
}
  • bind:fd 和自身的 ip 和端口绑定;

此处首先使用 sockaddr_in 设置 ip 和端口。需要指定协议簇为 IPv4(AF_INET),端口为 9898,注意此处的 9898 在主机上的存储方式为小端,需要将其转化为网络中的存储方式即大端。因此使用 htons 将主机上的端口转化为网络中的端口;

bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr)); 是将监听的文件描述符 fd,和本机的 ip 和端口进行绑定;

// 2. socket 和本机ip 端口绑定
struct sockaddr_in in_addr;
in_addr.sin_family = AF_INET;
// 端口号,短整型,主机转网络
in_addr.sin_port = htons(9898);
// 获取本机 ip 地址
in_addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));
if (ret == -1) {perror("bind fail...\n");exit(0);
}
  • listen:为当前文件描述符设置监听,主要是设置客户端连接数量;
// 3. 监听客户端
ret = listen(fd, 128);
if (ret == -1) {perror("listen fail...\n");exit(0);
}
  • accept:阻塞直到客户端请求连接;
    • 若收到客户端请求,那么将会创建一个新的 socket,即用作通信的文件描述符 cfd;此后即可使用 cfd 进行通信;

int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len); 使用 fd 监听客户端请求,若收到请求,将客户端的 ip 和端口信息写入 cli_addr,并创建一个用于通信的文件描述符(socket) cfd;在收到后,打印客户端信息;

此处涉及到 ip 地址的转换;ip 地址的转换有两种需求:

  • 主机给定一个 ip 字符串转换为网络字节序;这是在客户端代码中使用,在后续代码中使用;将字符串 src 转为字节序的 ip 地址存入 dst 中;
int inet_pton(int af, const char *src, void *dst); 
  • 网络中的字节序转换为字符串;即在本处使用;将字节序的 ip 地址转换为字符串存入 dst 中;
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
// 4. 阻塞等待客户端连接
struct sockaddr_in cli_addr;
int cli_len = sizeof(cli_addr);
int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len);
if (cfd == -1) {perror("accept fail...\n");exit(0);
}
// 打印客户端信息
char ip[20] = {0};
printf("客户端 IP 地址为:%s,端口号为 %d\n",inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, sizeof(ip)),ntohs(cli_addr.sin_port));
  • recv:接收来自客户端的数据;

  • send:向客户端发生数据;

定义缓冲区 buff 用于存放接收到的数据和放置需要发送的数据;注意此处是使用通信文件描述符 cfd 用于通信;recv(cfd, buff, sizeof(buff), 0) 使用套接字 cfd 进行通信,将接收到的数据放入 buff 中;

send(cfd, buff, size, 0); 是将需要发送的数据放入 buff 中进行发送;

// 接收数据的缓冲区
char buff[1024];
memset(buff, 0, sizeof(buff));
int size = recv(cfd, buff, sizeof(buff), 0);
if (size > 0) {printf("client say: %s\n", buff);send(cfd, buff, size, 0);
} else if (size == 0) { printf("客户端断开了连接...\n");break;
} else {perror("read fail...\n");break;
}
  • 结束连接;
close(fd);
close(cfd);

整体代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main () {// 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCPint fd = socket(AF_INET, SOCK_STREAM, 0);if (fd == -1) {perror("socket fail...!\n");exit(0);}// 2. socket 和本机ip 端口绑定struct sockaddr_in in_addr;in_addr.sin_family = AF_INET;// 端口号,短整型,主机转网络in_addr.sin_port = htons(9898);// 获取本机 ip 地址in_addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));if (ret == -1) {perror("bind fail...\n");exit(0);}// 3. 监听客户端ret = listen(fd, 128);if (ret == -1) {perror("listen fail...\n");exit(0);}// 4. 阻塞等待客户端连接struct sockaddr_in cli_addr;int cli_len = sizeof(cli_addr);int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len);if (cfd == -1) {perror("accept fail...\n");exit(0);}// 打印客户端信息char ip[20] = {0};printf("客户端 IP 地址为:%s,端口号为 %d\n",inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(cli_addr.sin_port));// 5. 和客户端通信while (1) {// 接收数据的缓冲区char buff[1024];memset(buff, 0, sizeof(buff));int size = recv(cfd, buff, sizeof(buff), 0);if (size > 0) {printf("client say: %s\n", buff);send(cfd, buff, size, 0);} else if (size == 0) { printf("客户端断开了连接...\n");break;} else {perror("read fail...\n");break;}}close(fd);close(cfd);return 0;
}

客户端

  • socket:建立通信的文件描述符 fd;注意此处的 fd 和服务器是不同的,该处的 fd 直接用于通信,而服务器中的第一个是用于监听;

fd 直接用于通信;

// 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCP
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {perror("socket fail...!\n");exit(0);
}
  • connect:设置好服务器的 ip 和端口后使用 connect 建立连接;

指定服务器地址,并将字符串转换为网络中的字节序 ip;使用 fd 与服务器建立连接;服务器全部信息由 in_addr 给定;

struct sockaddr_in in_addr;
in_addr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &in_addr.sin_addr.s_addr);
in_addr.sin_port = htons(9898);
int ret = connect(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));
if (ret == -1) {perror("connet fail...\n");exit(0);
}

此处和服务器一致,不再赘述;

char buff[1024];
sprintf(buff, "你好服务器...%d\n", number++);
send(fd, buff, strlen(buff) + 1, 0);
memset(buff, 0, sizeof(buff));
int size = recv(fd, buff, sizeof(buff), 0);
if (size > 0) {printf("server say: %s\n", buff);
} else if(size == 0) {printf("服务器断开连接\n");break;
} else {perror("read fail...\n");break;
}
sleep(1);
  • close:结束连接;
close(fd);

整体代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main () {// 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCPint fd = socket(AF_INET, SOCK_STREAM, 0);if (fd == -1) {perror("socket fail...!\n");exit(0);}struct sockaddr_in in_addr;in_addr.sin_family = AF_INET;inet_pton(AF_INET, "124.223.59.159", &in_addr.sin_addr.s_addr);in_addr.sin_port = htons(9898);int ret = connect(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));if (ret == -1) {perror("connet fail...\n");exit(0);}// 3. 和服务端通信int number = 0;while (1) {// 接收数据的缓冲区char buff[1024];sprintf(buff, "你好服务器...%d\n", number++);send(fd, buff, strlen(buff) + 1, 0);memset(buff, 0, sizeof(buff));int size = recv(fd, buff, sizeof(buff), 0);if (size > 0) {printf("server say: %s\n", buff);} else if(size == 0) {printf("服务器断开连接\n");break;} else {perror("read fail...\n");break;}sleep(1);}close(fd);return 0;
}

运行结果

在这里插入图片描述


http://www.ppmy.cn/devtools/109045.html

相关文章

【STM32】串口

1.串口-printf 使用printf函数向串口发送东西 使用微库&#xff0c;用到了printf, 但是我们发现是不能发送的 因为底层printf是fputc,我们需要自己实现 后面FILE*P不用管&#xff0c;在fputc中调用 第一个参数为uart1的句柄 第二个为要输出的字符 第三个为一次要发送几…

Apache Guacamole 安装及配置VNC远程桌面控制

文章目录 官网简介支持多种协议无插件浏览器访问配置和管理应用场景 Podman 部署 Apache Guacamole拉取 docker 镜像docker-compose.yml部署 PostgreSQL生成 initdb.sql 脚本部署 guacamole Guacamole 基本用法配置 VNC 连接 Mac 电脑开启自带的 VNC 服务 官网 https://guacam…

运维学习————kafka(1)

关于消息队列的概念&#xff0c;三个作用(解耦、异步、削峰)&#xff0c;使用场景&#xff0c;消息队列的两种模式&#xff0c;常见的MQ的框架等等&#xff0c;可以看我之前的RabbitMQ的文章&#xff0c;那里有详细介绍&#xff0c;这里就不过多赘述了........直接就kafka走起 …

机器人5GCPE模块参数的获取与上报设计

目录 1. 5GCPE模块参数功能的获取 2. 5GCPE模块参数功能的上报 3. 5G与WIFICPE运行效果 1. 5GCPE模块参数功能的获取 根据5G皮带机器人新需求&#xff0c;需要增加5GCPE信息的获取与上报参数的需求&#xff0c;以供上位机进行信号强度等信息的展示&#xff0c;所获取的ip地址…

推荐一款开源、高效、灵活的Redis桌面管理工具:Tiny RDM!支持调试与分析功能!

1、引言 在大数据和云计算快速发展的今天&#xff0c;Redis作为一款高性能的内存键值存储系统&#xff0c;在数据缓存、实时计算、消息队列等领域发挥着重要作用。然而&#xff0c;随着Redis集群规模的扩大和复杂度的增加&#xff0c;如何高效地管理和运维Redis数据库成为了许…

极盾故事|某金融租赁机构应用数据保护新策略:“动态脱敏”“二次授权”

数据的流通使用是创新的动力&#xff0c;但安全和合规是不可逾越的底线。企业如何在这三者之间找到平衡点&#xff1f; 极盾科技&#xff0c;助力某金融租赁机构&#xff0c;基于极盾觅踪构建应用数据动态脱敏系统&#xff0c;实现10&#xff0b;核心应用系统的统一管理&#x…

【JavaEE初阶】多线程(3)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 线程状态 线程安全 代码示例 解释 总结原因 解决方案-->加锁 t1和t2都加锁 且 同一个锁对象 t1和t2中只有一个加锁了 t1和t2都加锁,但锁对象不同 加锁 与线程等待…

CTFHub技能树-Git泄漏-Log

目录 一、前提知识 1.git泄漏原理 ​编辑 2.git文件泄漏造成后果 3.利用方法 (1) GitHack是一个.git泄露利用脚本&#xff0c;通过泄露的.git文件夹下的文件&#xff0c;还原重建工程源代码。渗透测试人员、攻击者&#xff0c;可以进一步审计代码&#xff0c;挖掘&#x…

深入理解Oracle数据库中的数据库链接

在Oracle数据库环境中&#xff0c;数据库链接&#xff08;Database Link&#xff09;是一种强大的特性&#xff0c;它允许用户从一个数据库&#xff08;本地数据库&#xff09;访问另一个数据库&#xff08;远程数据库&#xff09;中的数据。这种链接机制极大地增强了数据库的互…

简述CCS平面线性光源

光源在机器视觉系统中起着重要作用&#xff0c;不同环境、场景及应用合适光源都不一样&#xff0c;今天我们来看看LFX3-PT系列平面线性光源。它是最适合检测镜面物体的凹凸,外壳小巧的光源。备有根据检测条件可选的2种线间距。1mm型&#xff08;型号末尾&#xff1a;A&#xff…

每日一练:合并区间

一、题目要求 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;in…

BLIP3技术小结(xGen-MM (BLIP-3): A Family of Open Large Multimodal Models)

paperhttps://www.arxiv.org/abs/2408.08872githubhttps://github.com/salesforce/LAVIS/tree/xgen-mmOrg.Salesforce AI Research个人博客地址http://myhz0606.com/article/blip3 前置阅读&#xff1a;BLIP系列总结 核心思路 虽然过去BLIP系列对LMM发展起到至关重要的作用&…

@Value读取properties中文乱码解决方案

前几天碰到使用Value中文乱码的问题&#xff0c;英文字符则不会出现问题 原因&#xff1a;SpringBoot在加载properties配置文件时&#xff0c;使用的默认编码是&#xff1a;ISO_88599_1 解决方式&#xff1a;将properties改成yml就可以读取成功了 Data Component PropertySou…

R 包管理

R 包管理 函数总结简介安装包选择安装路径 更新包devtoolsGitHub 和 BioConductor加载包迁移扩展包 2024-09-04 函数总结 函数功能install.packages()安装包。不加参数&#xff0c;显示CRAN镜像站点站点&#xff0c;加包名称&#xff0c;直接下载安装包installed.packages()列…

EmguCV学习笔记 VB.Net 第10章 人脸识别

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

仿华为车机UI--图标从Workspace拖动到Hotseat同时保留图标在原来位置

基于Android13 Launcher3,原生系统如果把图标从Workspace拖动到Hotseat里则Workspace就没有了&#xff0c;需求是执行拖拽动作后&#xff0c;图标同时保留在原位置。 实现效果如下&#xff1a; 实现思路&#xff1a; 1.如果在workspace中拖动&#xff0c;则保留原来“改变图标…

项目日志——日志等级类和日志消息类的设计、实现、测试

文章目录 日志等级类设计实现测试 日志消息类设计实现 日志等级类 设计 日志等级一共分7个等级 UNKNOW 0OFF 关闭所有日志输出DEBUG 调试等级INFO 提示等级WARN 警告等级ERROR 错误等级FATAL 致命等级OFF 关闭所有日志的输出 每一个项目都会设置一个默认输出等级&#xff…

前端项目开发之prettier安装和使用

前端项目开发之安装prettier和使用 Prettier 是一个流行的代码格式化工具&#xff0c;可以帮助你保持代码风格的一致性。以下是如何在 Visual Studio Code (VS Code) 中安装和使用 Prettier 的步骤&#xff1a; 安装 Prettier 通过 VS Code 扩展市场安装&#xff1a; 打开 V…

使用AI赋能进行软件测试-文心一言

1.AI赋能的作用 提高速度和效率缺陷预测与分析 2.AI互动指令格式--文心一言 角色、指示、上下文例子、输入、输出 a 直接问AI 针对以下需求&#xff0c;设计测试用例。 需求&#xff1a; 1、账号密码登录系统验证账号和密码的正确性。 验证通过,用户登录成功,进入个人中心;验…

pytorch tensor.expand函数介绍

在 PyTorch 中&#xff0c;tensor.expand()是一个用于扩展张量维度的函数。 一、函数作用 它允许你在不复制数据的情况下&#xff0c;将张量的形状扩展到指定的维度大小。这对于需要在特定维度上重复数据的操作非常有用&#xff0c;例如在进行广播操作时调整张量的形状。 二…