C Tcp通信

news/2025/11/22 22:37:52/文章来源:https://www.cnblogs.com/bigbosscyb/p/19258779

学完了c程序基础知识后,是不是感觉什么也干不了,总想找点事情来练手?本文暂且介绍一下,如何使用socket进行tcp通信;示例程序目前并不健壮,仅为演示socket通信的基本流程。
image

用白话理解tcp通信

TCP通信就像打电话,日常生活中我们打电话,需要进行下面一些动作:
1、拿起电话
2、输入对方号码发起通话
3、进行交谈
4、通话完毕挂断电话
打电话这个过程,在tcp通信协议中有两个比较重要的知识点要介绍:

三次握手

如上面打电话的第二步,发起通话,在tcp协议中这一步称之为建立连接,由于建立连接需要三次消息发送,故将建立连接这个阶段称为“三次握手”
用形象的文字描述三次握手:

  • 你好,听的到么
  • 听得到,你听得到我说话么

  • 这三步对应到tcp中分别是:
  • 通信方A 发送syn标志 A处于syn_send状态
  • 通信方B 回复一个 syn + ack 表示确认收到对方发过来的syn信号 B处于syn-recv状态
  • 通信方A收到syn+ack后处于 established状态(已建立连接状态) 再次发送一个ack信号 表示我也接收到对方回复的ack信号 若B收到ack后,B也会处于established状态(已建立连接状态)
    经历上述三步后就代表连接已成功建立。。。。
    题外话
  • 为什么是三次握手?第一步自不必说,若没有第二步,B有没有听到我讲话都无法确认,如何再继续交流?若没有第三步,B不知道A有没有听到B的声音,他也不知道如何继续交流。。
  • tcp dos攻击:搞一堆不存在的ip向服务端发起连接,服务端回复syn ack后,将生出一堆 syn_recv状态的连接,由于这些ip不存在,所以服务端收不到 ack信号。。。半连接队列满后,服务端无法再接收更多的连接,服务器瘫痪。。

四次挥手

  • 就聊到这吧 没啥事儿了挂了吧
  • 我想了想也没啥事儿了 那你挂吧

  • 这四步对应到tcp中分别是:
  • 通信方A发起断开请求 fin+ack A进入终止等待1 fin_wait_1
  • 通信方B回复ack 确认收到 B进入关闭等待状态 close_wait A进入终止等待2 fin_wait_2
  • 通信方B发送释放信号 fin+ack 进入最后确认状态last_ack状态 A进入时间等待状态 time_wait

时间等待状态很重要 为什么要有这个等待状态?因为要保证B一定能收到自己发出的fin中的ack;这个期间如果通信方B收不到ack信号,会再次重发fin,处在time_wait的A可以再次发送ack。

  • 通信方A回复ack 确认释放 双方关闭连接

好了三次握手、四次挥手介绍完毕,下面开始介绍 socket对象;在c程序中通过socket对象进行tcp通信。

客户端代码实现

对于客户端需要以下几步:

  • 创建套接字对象 指定ipv4/ipv6 指定 tcp/udp
  • 绑定本地地址和端口
  • 连接目标主机(需要指定目标主机的地址和端口)
  • 接收或发送消息
  • 关闭套接字对象
#include <stdio.h>//控制台输入输出函数所在头文件
#include <string.h>//清空字符串函数所在头文件
#include <sys/socket.h>//套接字结构体所在头文件
#include <arpa/inet.h>//字节序网络序所在头文件
#include <unistd.h>//close套接字所在函数
#include <signal.h>//接收程序退出信号的函数所在头文件
#include <stdlib.h>//exit函数所在头文件int socketfd;void handle_exit() {if (socketfd >= 0) {close(socketfd);puts("释放套接字");}
}void handle_signal(int sig) {puts("收到程序退出信息,正在处理...");exit(EXIT_SUCCESS);
}int main(void) {signal(SIGINT, handle_signal);signal(SIGTERM, handle_signal);atexit(handle_exit);//创建套接字 返回文件描述符 非负值标识创建成功socketfd = socket(AF_INET, SOCK_STREAM, 0); //地址族 套接字类型 套接字协议//绑定地址 传入套接字描述符 传入网络信息//创建目标地址信息struct sockaddr_in local_addr;local_addr.sin_family = AF_INET;local_addr.sin_port = htons(2000);local_addr.sin_addr.s_addr = inet_addr("127.0.0.1");bind(socketfd, (struct sockaddr *) &local_addr, sizeof(local_addr));//发起连接struct sockaddr_in remote_addr;remote_addr.sin_family = AF_INET;remote_addr.sin_port = htons(8001);remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");printf("向%s:%d发起连接...\n", inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port));connect(socketfd, (struct sockaddr *) &remote_addr, sizeof(remote_addr));puts("连接成功...");char buf[20];while (1) {memset(buf, 0, sizeof(buf));puts("输入要消息后按回车发送(直接按回车后退出程序)");fgets(buf, sizeof(buf), stdin);if (buf[0] == 0 || buf[0] == '\n') {close(socketfd);break;}send(socketfd, buf, sizeof(buf), 0);}close(socketfd);return 0;
}

上面代码的介绍:

  • 主机字节序和网络字节序,网络字节序都是大端存储,主机字节序不确定是大端还是小端;所以对于ip地址(字符串),需要转换成网络字节序;对于端口(整型),也需要转换成网络字节序;整型转网络字节序、字符串转网络字节序函数不同
  • 注册退出信号回调函数,是为了在退出程序时能正确释放资源

服务端代码实现

服务端的实现需要下面几步:

  • 创建socket对象
  • 绑定本地地址和端口
  • 开启连接监听
  • 调用接收连接函数准备接收来自客户端的连接
  • 接收到来自客户端的连接后 根据获得的客户端的socket指针 与 客户端进行消息发送和接收
  • 通信完毕 释放客户端socket对象 释放服务端socket对象
#include <stdio.h>//控制台输入输出函数所在头文件
#include <string.h>//清空字符串函数所在头文件
#include <sys/socket.h>//套接字结构体所在头文件
#include <arpa/inet.h>//字节序网络序所在头文件
#include <unistd.h>//close套接字所在函数
#include <signal.h>//接收程序退出信号的函数所在头文件
#include <stdlib.h>//exit函数所在头文件int socket_fd;void handle_signal(int sig) {puts("用户退出程序");if (socket_fd > 0) {puts("停止服务");close(socket_fd);}exit(0);
}int main(void) {signal(SIGINT, handle_signal);//解决程序被用户手动关闭 无法正常停止服务的问题signal(SIGTERM, handle_signal);//创建套接字 指定ipv4 套接字类型 套接字协议默认socket_fd = socket(AF_INET, SOCK_STREAM, 0);//在bind之前设置socket 允许重用本地端口int opt = 1;int set_ret = setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));if (set_ret != 0) {perror("setsockopt error");close(socket_fd);exit(-1);}//绑定本地网络信息到套接字struct sockaddr_in local_addr;local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = inet_addr("127.0.0.1");local_addr.sin_port = htons(8001);int ret = bind(socket_fd, (struct sockaddr *) &local_addr, sizeof(local_addr));if (ret != 0) {perror("bind error");close(socket_fd);exit(-1);}//监听连接ret = listen(socket_fd, 5);if (ret != 0) {perror("listen error");close(socket_fd);exit(-1);}printf("服务正在监听%s:%d...\n", inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port));//接受连接int client_fd = accept(socket_fd, NULL, NULL);printf("收到了一个客户端连接\n");//接收或发送消息char buf[200];while (1) {memset(buf, 0, sizeof(buf));ssize_t recv_len = recv(client_fd, buf, sizeof(buf) - 1, 0); //最后一个参数0 代表阻塞if (recv_len == 0) {//客户端主动关闭连接puts("client closed");break;}if (recv_len <= 0) {perror("recv error");break;}printf("收到消息: %s\n", buf);}close(client_fd);close(socket_fd);return 0;
}

上面代码的介绍:

  • 我们知道四次挥手有时间等待状态的存在,它的存在能大概“保活”端口30~120秒左右;而程序异常退出等无法正常close套接字对象的场景,会让操作系统将连接置于time_wait状态,故上面代码增加了对套接字的“端口地址重用”设置;这样便降低了意外关闭的服务端再次启动后出现“Address already in use”错误的概率。
  • recv函数收到数据长度小于0 代码出现错误;等于0代表客户端断开连接;程序中要正确处理这两种情况
  • 释放操作不能忘
  • tcp是全双工通信,上面代码没有演示,但这并不影响 客户端和服务端各自即可以发消息也可以收消息

以上便是对c程序中通过socket对象进行tcp通信的简要介绍。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SZMS 251019 订题赛笔记

串串 原题:[CEOI 2010] pin 题意 给定 \(n\) 个长度为 4 的字符串,你需要找出有多少对字符串满足恰好 \(d\) 个对应位置的字符不同。 \(n \le 5 \times 10 ^ 4, d \le 4\)。 思路 前面忘了。 注意到恰好。 注意到容易…

关于面向对象程序设计的第一阶段大作业总结

一、前言 本次三次题目集聚焦 “单部电梯调度系统”,是面向对象编程的迭代式实践。涉及的知识点包括 Java 类与对象、枚举封装、集合框架(LinkedList)、单一职责原则(SRP)、电梯调度算法及输入校验。但对我而言,…

Spring Boot核心知识点全解析 - 实践

Spring Boot核心知识点全解析 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monac…

RHCA - DO374 | Day03:通过自动化控制器运行剧本 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

离职/毕业-清理电脑

这段内容主要面向打工人,强调离职前清理电脑个人信息、保护隐私的重要性,并详细介绍了从微信聊天记录、电脑使用记录、上网记录、软件缓存四个方面一键清理个人信息的方法。开篇指出离职小白常误以为卸载软件就能清理…

2025.11.22

今天学习vue的项目布局,明天继续学习布局,然后就要开始前后端连接了

在 Python 和 NumPy 的常规书写规范中,ndarray需要大写吗?

在 Python 和 NumPy 的常规书写规范中,ndarray 通常使用小写,不需要大写。 ✅ 正确用法:ndarray(小写) 官方文档、NumPy 源码和社区惯例中,ndarray 始终是小写的。 它是 NumPy 中一个类的名称(numpy.ndarray),…

ddddocr: 对图片处理提升识别率

一,识别有误 dataurl: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAABQCAMAAAAQlwhOAAAA81BMVEUAAAB/TiCdbD6GVSdoNwnPnnB7ShyDUiTDkmR5SBrVpHbMm22FVCa5iFrUo3WUYzW9jF5yQRPDkmS/jmCFVCZyQRNnNghvPhCVZDZfL…

`np.array` 和 `np.ndarray`是什么关系?

np.array 和 np.ndarray 是 NumPy 中两个密切相关但用途和行为完全不同的概念。理解它们的关系是掌握 NumPy 的关键之一。🔹 1. np.ndarray:NumPy 数组的底层类(类型)numpy.ndarray 是 所有 NumPy 数组对象的实际…

大数据部门和AI部门边界

目录背景和价值1. 大数据部门的职责 (Big Data Department - 平台建设方)2. AI部门的职责 (AI Department - 价值应用方)总结:技术总监的战略划分参考资料 背景和价值 这是一个在组织架构中非常关键的问题,也是检验技…

Post Processing

Post Processing后处理 Unity Grab URP Render Feature Godot screen_texture Godot 面片法

工作草稿

自行车产业链发展的三种模式 一、加工增值模式进口高端碳纤维(德国织布机) 17%关税车架、车把 8%关税 → 增值 50%高端自行车日本禧玛诺车零件关税 20% 增值 30% 国内销往鱼杆、船桨、高尔夫 (昆山、吉林、呼和浩特…

【Rust编程:从新手到大师】Rust 环境搭建(详细版) - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

OOP第一到三次作业总结 -单部电梯调度

一、前言 博主近期疏于学业,故而三次作业均没有完整实现出来,因此这是一篇反思文,同博主一样起步浑浑不清者,可借此文规避一些低效的思路。 (博主依赖ai成瘾,借主播血泪教训,建议读者切忌一味信赖ai!应当试着自…

2025年11月南通宠物医疗市场深度分析:专业服务与行业规范的标杆选择

在宠物经济快速发展的浪潮中,宠物医疗行业正经历从"基础服务"向"专业医疗"的深刻转型。据最新行业报告显示,南通地区宠物医疗市场规模年均增长达16.8%,宠物主人对医疗服务的期望已从"能看…

用户亲测!用 DiffMind 用 AI,省一半时间还出效果,小白也能轻松上手

用户亲测!用 DiffMind 用 AI,省一半时间还出效果,小白也能轻松上手“用 AI 写文案要切 3 个平台,改简历改 5 遍还不对,查个信息怕 AI 瞎编”—— 如果你也有这些糟心经历,那一定要试试DiffMind(官网www.diffmin…

软工团队作业3

作业信息这个作业属于哪个课程 首页 - 计科23级34班 - 广东工业大学 - 班级博客 - 博客园这个作业要求在哪里 团队作业3--需求改进&系统设计 - 作业 - 计科23级34班 - 班级博客 - 博客园这个作业的目标 明确团队项…

电梯调度迭代编程作业复盘

一、对三次电梯调度题目集的整体认知 (一)知识点覆盖与考察重点 三次电梯调度题目集聚焦面向对象编程核心知识点与工程化开发能力,核心考察内容可归纳为三类: 设计原则:核心是单一职责原则。从第一次的 “单类包揽…

球坐标系的大运动方程组

球坐标系的大运动方程组P=P(λ, φ, r) 其中,λ——经度φ——纬度r——地心到空间点的距离

【数据库】navicat的下载以及数据库约束 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …