C/C++实现tcp客户端和服务端的实现(从零开始写自己的高性能服务器)

目录

tcp客户端通信流程

tcp客户端设计

1、创建通信对象

2、链接服务器

3、发送数据 / 读取数据

4、关闭通信

tcp服务端设计

1、创建通信对象

2、绑定服务器地址信息

3、设置服务器为监听模式

4、接收客户的链接请求

编写tcp客户端和服务端,实现双向通信

server.c

client.c


tcp客户端通信流程

1、创建通信对象

2、链接服务器

3、发送数据 / 读取数据

4、关闭通信

tcp客户端设计

1、创建通信对象

#include <sys/socket.h>

int socket(int domain, int type, int protocol);  //创建一个终端链接

domain:网络层协议  
type:     传输层协议   SOCK_STREAM    TCP协议     
                                  SOCK_DGRAM     UDP协议  

protocol:属性,默认为 0   
返回值: 成功  通信对象 
             失败  -1 

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

tcp_socket = socket(AF_INET, SOCK_STREAM, 0);    //创建TCP对象 
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);     //创建UDP对象 
raw_socket = socket(AF_INET, SOCK_RAW, protocol); //创建自定义协议对象

demo:

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);if (tcp_socket < 0)
{perror("创建TCP客户端通信对象失败");return 1;
}
else
{printf("创建TCP客户端通信对象成功\n");
}

2、链接服务器

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
                                                       socklen_t addrlen);

sockfd:客户端通信对象 
addr:服务器地址 
addrlen:服务器地址的大小 
返回值:  成功   0  
              失败   -1

sockaddr_in结构体

struct sockaddr_in 
{
sa_family_t    sin_family;    /* address family: AF_INET 网络层协议*/
in_port_t      sin_port;        /* port in network byte order 端口*/  
struct in_addr sin_addr;   /* internet address 网络地址*/  
};struct in_addr 
{uint32_t    s_addr;     /* address in network byte order 网络地址*/
};   

sockaddr和sockaddr_in区别

struct sockaddr
{  
     sa_family_t sin_family;//地址族
     char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
}; 

sockaddr的缺陷是sa_data把目标地址和端口信息混在一起了


二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了

demo:

//网络服务器地址设置
struct sockaddr_in addr;                         // 创建一个服务器地址结构体
addr.sin_family = AF_INET;                       // 设置地址族为IPv4
addr.sin_port = htons(8888);                     // 设置端口号为8888
addr.sin_addr.s_addr = inet_addr("172.17.112.1"); // 设置IP地址为(我本机的网卡地址)//链接服务器
if (connect(tcp_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{perror("连接服务器失败:\n");return 1;
}
else
{printf("连接服务器成功\n");
}

补充:

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);  //把本地端口转换为网络端口
uint16_t ntohs(uint16_t netshort);   //把网络端口转换为本地端口

---------------------------------------------------------------------------------------------------------------------------------

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);  //把字符串地址转换为网络地址  
char *inet_ntoa(struct in_addr in);   //把网络地址转换为字符串地址

 

3、发送数据 / 读取数据

a1fa416818f6444ea3338130f7c9d19c.png

4、关闭通信

#include <unistd.h>                                                                                                                          int close(int fd);

---------------------------------------------------------------------------------------------------------------------------------

tcp服务端设计

1.创建通信对象

2.绑定服务器地址信息

3.设置服务器为监听模式

4.接收客户的链接请求

5.读取数据

6.发送数据

7.关闭通信

1、创建通信对象

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

2、绑定服务器地址信息

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
             socklen_t addrlen);

sockfd:服务器对象 
addr:服务器的地址信息 
addrlen:服务器地址信息的大小 
返回值: 成功  0  
             失败  -1 

 

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡的IP地址if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{perror("bind 失败");return -1;
}

一般绑定的都是本机的网卡地址,可以通过ip addr查看本机的ip地址

3、设置服务器为监听模式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

sockfd:服务器socket  
backlog:最大监听数  (同时可以有多少个客户端链接进来)
返回值:成功  0    
            失败  -1

if (listen(sockfd, 10) < 0)
{perror("listen 失败");return -1;
}

4、接收客户的链接请求

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:服务器对象 
addr:对方的IP地址信息  (保存链接者的地址)
addrlen:对方的IP地址大小
返回值:      成功  返回一个新的文件描述符(用于与当客户端通信)
                  失败  -1  

1.accept 接口会一直阻塞等待,直到有客户端链接为止!       
2.accept 会返回一个新的文件描述符,该描述符用于读写通信!
 

int new_socket = accept(sockfd, NULL, NULL);
if (new_socket < 0)
{perror("accept 失败");return -1;
}char buf[1024] = {0};
scanf("%s", buf);
write(new_socket, buf, strlen(buf));

编写tcp客户端和服务端,实现双向通信

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>void *send_task(void *arg)
{int *new_socket = arg;while (1){char buf[1024] = {0};scanf("%s", buf);ssize_t n = write(*new_socket, buf, strlen(buf));
#if 0if (n < 0){if (errno == EPIPE){// 对端关闭了连接printf("客户端已关闭,线程退出\n");close(*new_socket);break;}perror("write 错误");}
#endif}return NULL;
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0){perror("bind 失败");return -1;}if (listen(sockfd, 10) < 0){perror("listen 失败");return -1;}while (1){printf("等待客户端链接\n");int new_socket = accept(sockfd, NULL, NULL);if (new_socket < 0){perror("accept 失败");return -1;}printf("新的客户端链接\n");pthread_t tid;pthread_create(&tid, NULL, send_task, &new_socket);// 读取客户端发送过来的数据char buf[1024] = {0};ssize_t n;while ((n = read(new_socket, buf, sizeof(buf))) > 0){buf[n] = '\0';printf("客户端发送过来的数据:%s\n", buf);}
#if 0if (n == 0){// 客户端关闭连接printf("客户端关闭连接,关闭套接字\n");close(new_socket);}else if (n < 0){perror("read 错误");}}
#endifreturn 0;
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int tcp_socket;// 发送任务
void *send_task(void *arg)
{while (1){// 3.发送数据char buf[1024] = {0};scanf("%s", buf);write(tcp_socket, buf, strlen(buf));}
}int main(int argc, char *argv[])
{if (argc!= 3){printf("请输入服务器的IP和端口\n");return 0;}// 1.创建客户端对象tcp_socket = socket(AF_INET, SOCK_STREAM, 0);// 2.设置服务器地址信息struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(atoi(argv[2]));      // 端口server_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP// 3.连接服务器int ret = connect(tcp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));if (ret < 0){printf("连接服务器失败\n");return 0;}else{printf("连接服务器成功\n");}// 创建一个线程pthread_t tid;pthread_create(&tid, NULL, send_task, NULL);char buf[1024] = {0};ssize_t n;while ((n = read(tcp_socket, buf, sizeof(buf))) > 0){printf("服务端发来的数据:%s\n", buf);}
#if 0if (n == 0){// 服务器关闭连接printf("服务器已关闭连接,客户端退出\n");close(tcp_socket);}else if (n < 0){perror("读取数据出错");// 可以根据错误情况决定是否尝试重新连接或者直接退出close(tcp_socket);}
#endifreturn 0;
}

        在demo中注释掉的代码是因为当服务器(客户端)进程退出时,客户端(服务器)的read函数会持续返回 0(表示对端关闭),导致无限循环接收空内容。需要在read函数返回 0 或者发生错误时进行适当处理,以避免这种情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/19935.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程

引言 C 标准模板库&#xff08;STL&#xff09;提供了一组功能强大的容器类&#xff0c;用于存储和操作数据集合。不同的容器具有独特的特性和应用场景&#xff0c;因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C 的开发者来说&#xff0c;了解这些容…

快速上手并使用Muduo库

Muduo muduo库是基于主从reactor模型的高性能服务器&#xff08;高并发服务器&#xff09;框架 reactor模型&#xff1a;基于事件触发的模型&#xff08;基于epoll进行IP事件监控&#xff09; 主从reactor模型&#xff1a;将IO事件监控有进行进一步的层次划分 主reactor&#x…

深入解析【C++多态】:探索面向对象编程中的动态绑定与行为多样性和多态的核心概念与应用实践

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: C专栏 目录 多态的概念 多态的定义及实现 实现多态还有两个必须重要条件 虚函数 虚函数的重写/覆盖 多态场景的⼀个选择题 虚函数重写的⼀些其他问题 协变(了解进行) 析构函数的重写 override 和 final关…

React Native Mac 环境搭建

下载 Mac 版Android Studio 下载 安装 JDK 环境 Flutter 项目实战-环境变量配置一 安装 Node.js 方式一 通过Node.js 官网下载 下载完成后点击安装包进行安装 安装完成

【Word】一键批量引用论文上标——将正文字体改为上标格式

【Word】一键批量引用论文上标——将正文字体改为上标格式 写在最前面Word一键批量引用论文上标技巧分享核心思路&#xff1a;Word 替换功能 通配符步骤详解1. 打开 Word 替换功能2. 输入通配符模式3. 设置替换格式为上标4. 批量替换 实际效果展示技巧扩展 &#x1f308;你好呀…

深入探索Python数据可视化:自定义颜色映射、标签与进阶技巧

目录 一、自定义颜色映射&#xff08;Cmap&#xff09; 1. 内置Cmap类型 2. 使用内置Cmap 3. 自定义Cmap 二、标签添加 1. 在散点图上添加标签 2. 在折线图上标记关键点 3. 在柱状图上添加标签 三、进阶技巧 1. 多图形布局 2. 添加图例 3. 3D数据可视化 四、总结 …

【Java SE】数据库连接池

数据库连接池是一个管理数据库连接的容器。它的主要作用是分配和管理数据库连接&#xff0c;允许应用程序重复使用现有的连接&#xff0c;而不是每次都重新建立新的连接。此外&#xff0c;连接池会释放那些空闲时间超过最大限制的连接&#xff0c;从而避免因未及时释放连接而造…

FastAPI重载不生效?解决PyCharm中Uvicorn无法重载/重载缓慢的终极方法!

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 重载缓慢 📒📝 问题概述🚨 相关原因📝 解决方案一📝 解决方案二📝 解决方案三📝 解决方案四⚓️ 相关链接 ⚓️📖 介绍 📖 在使用FastAPI开发时,reload=True 本应让你在修改代码后自动重启服务,提升开发效率…

CPU算法分析LiteAIServer视频智能分析平台未戴安全帽检测算法

随着人工智能技术的不断进步&#xff0c;CPU算法分析在视频智能分析平台中的应用日益广泛。特别是在工地安全管理领域&#xff0c;未戴安全帽检测算法已成为一项关键的安全保障措施。LiteAIServer视频智能分析平台通过结合CPU的高效运算能力和先进的深度学习算法&#xff0c;实…

两网站定时数据exchange项目详解

功能说明 A网站&#xff1a;用户可以通过表单输入嫌疑人信息&#xff0c;这些信息会被存储在内存中&#xff0c;并通过API接口返回。B网站&#xff1a;通过API接口接收从A网站同步过来的嫌疑人数据&#xff0c;并显示这些数据。主应用&#xff1a;使用APScheduler每隔1分钟从A…

【云计算】腾讯云架构高级工程师认证TCP--考纲例题,知识点总结

【云计算】腾讯云架构高级工程师认证TCCP–知识点总结&#xff0c;排版整理 文章目录 1、云计算架构概论1.1 五大版块知识点&#xff08;架构设计&#xff0c;基础服务&#xff0c;高阶技术&#xff0c;安全&#xff0c;上云&#xff09;1.2 课程详细目录1.3 云基础架构设计1.4…

sql server查看当前正在执行的sql

#统计某类sql执行次数&#xff0c;并按总体cpu消耗时间降序排序 with a as ( select er.session_id,db_name(er.database_id) as DBNAME,sy.last_batch AS 最后执行时间, er.cpu_time ,er.total_elapsed_time/1000 as sum_elapsed_time_s, CAST(csql.text AS varchar(8000)) A…

【UE5】Slider控件样式

实现根据滑柄位置确定滑条样式的功能&#xff0c;效果如下。 效果 步骤 1. 添加Slider控件和文本控件&#xff0c;其中文本控件用于显示滑条的值 2. 文本控件绑定变量&#xff0c;这里变量为“Year” 3. 当滑条的值变更后&#xff0c;设置“Year”的值&#xff0c;然后调用函…

JVM性能分析工具JProfiler的使用

一、基本概念 JProfiler&#xff1a;即“Java Profiler”&#xff0c;即“Java分析器”或“Java性能分析工具”。它是一款用于Java应用程序的性能分析和调试工具&#xff0c;主要帮助开发人员识别和解决性能瓶颈问题。 JVM&#xff1a;即“Java Virtual Machine”&#xff0c…

TongRDS 可视化连接

说明&#xff1a;TongRDS 增加了 redis 兼容接口&#xff0c;所以 redis 能连接的可视化方式&#xff0c;TongRDS 也是可以的 Redis Insight Redis Insight DataGrip DataGrip

【WPF】Prism学习(八)

Prism Dependency Injection 1.处理解析错误 1.1. 处理解析错误&#xff1a; 这个特性是在Prism 8中引入的&#xff0c;如果你的应用目标是早期版本&#xff0c;则不适用。 1.2. 异常发生的原因&#xff1a; 开发者可能会遇到多种原因导致的异常&#xff0c;常见的错误包括…

游戏引擎学习第19天

介绍 这段内容描述了开发者在进行游戏开发时&#xff0c;对于音频同步和平台层的理解和调整的过程。以下是更详细的复述&#xff1a; 开发者表达了他希望今天继续进行的工作内容。他提到&#xff0c;昨天他讲解了一些关于音频的内容&#xff0c;今天他想稍微深入讲解一下他正…

蓝队技能-应急响应篇Rookit后门进程提取网络发现隐藏技术Linux杀毒OpenArk

知识点&#xff1a; 1、应急响应-Windows-Rootkit-分析&清除 2、应急响应-Linux-Rootkit-分析&查毒&清除 内存马和rookit都是属于权限维持技术&#xff0c; 内存马一般是用来控制网站&#xff0c;rookit一般是用来控制服务器&#xff08;隐藏常规C2后门&#xff…

MAC创建一个自动操作,启动系统【睡眠】功能,并将绑定快捷键

目的 通过 Automator 创建一个服务来启动系统【睡眠】这个功能&#xff0c;并绑定快捷键。 步骤一&#xff1a;创建 Automator 服务 打开 Automator&#xff1a; ○ 在 Spotlight 中搜索 Automator&#xff0c;然后打开。选择服务类型&#xff1a; ○ 在 Automator 的启动界…

OpenLayers教程11_在OpenLayers中启用WebGL渲染

在 OpenLayers 中启用 WebGL 渲染&#xff1a;提高地图渲染性能的完整指南 目录 一、引言二、WebGL 渲染在 Web GIS 中的作用 WebGL 的优势WebGL 与 Canvas 渲染的区别 三、在 OpenLayers 中启用 WebGL 的方法四、代码实现步骤 1. 初始化地图和基本 WebGL 渲染2. 加载大规模点…