当前位置: 首页 > news >正文

Linux编程——网络编程(tcp)

tcp协议 (传输控制协议)特征:

1.流式套接字

2.数据连续,顺序发送

3.可靠传输,应答机制(保障数据可靠传输),数据发送丢包后启动自动重传机制

4.有链路(服务器和客户端会先打通一条通路,建立连接,连接server和client,互联网传输节点状态信息可以保存)

5.全双工通信

6.机制复杂

7.双缓冲区,收发互不影响

建立连接——三次握手

握手次数发送方

核心内容 [seq:序列号]

目的
第一次客户端SYN=1Seq=X发起连接,告知服务器:客户端的起始序列号。
第二次服务器SYN=1ACK=1Seq=YAck=X+1同意连接,告知客户端:服务器的起始序列号,并确认收到了你的第一个包。
第三次客户端ACK=1Seq=X+1Ack=Y+1确认收到了你的包。至此,双方都确认了对方的发送能力和自己的接收能力。

最核心、必须发送的内容就是双方的初始序列号和对对方序列号的确认!!!

断开连接——四次挥手

操作流程

listen()函数不会阻塞,用于监听服务器状态,接收请求连接的客户端,并与客户端完成三次握手建立通信连接

read() --- write() 也可以调用 send() --- recv()

客户端/服务器端,任意一端断开,套接字进入半链接状态,只能收不能发

服务器端


<1>sock() 建立流式套接字

头文件:#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

原型:        int socket(int domain, int type, int protocol);

功能:        创建一个通信端点(套接字),并返回一个文件描述符。这是网络通信的起点。

参数:        

  • int domain:协议族。常用 AF_INET(IPv4)或 AF_INET6(IPv6)。

  • int type:套接字类型。常用 SOCK_STREAM(面向连接的可靠TCP流)或 SOCK_DGRAM(无连接的UDP数据报)。

  • int protocol:通常设置为 0,表示由系统根据前两个参数自动选择默认协议(如 SOCK_STREAM 默认对应 IPPROTO_TCP)。

返回值:     成功 返回一个非负整数的文件描述符

                   失败 返回-1

示例代码
//创建流式套接字int listfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == listfd){perror("socket fail");return 1;}

<2>bind() 绑定服务器地址和端口

头文件:#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

              #include <netinet/ip.h>

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

功能:        将上一步创建的套接字与一个特定的本地IP地址和端口号绑定起来。服务器必须这么                       做,以便客户端能够知道要连接到哪台机器的哪个服务。

参数:       

  • int sockfdsocket() 函数返回的套接字描述符。

  • const struct sockaddr *addr:指向一个包含协议家族、IP地址、端口号等信息的结构体的指针。

    • 实际使用时,通常填充 struct sockaddr_in (用于IPv4),然后强制转换为 struct sockaddr *

  • socklen_t addrlen:第二个参数所指向的结构体的大小(sizeof(struct sockaddr_in)

返回值:    成功 返回0;

                   失败 返回-1,并设置errno

struct sockaddr_in - 定义IPv4套接字地址

struct in_addr - 存储IPv4地址

示例代码
struct sockaddr_in address;
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));address.sin_family = AF_INET; // IPv4
//127.0.0.1 表示自己的IP地址,本机自己接收
// ser.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_addr.s_addr = INADDR_ANY; // 绑定到本机所有可用的IP地址
address.sin_port = htons(8080); // 绑定到8080端口 (htons用于主机字节序到网络字节序的转换)int ret  =bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{perror("bind fail");return 1;        
}

<3>listen() 使套接字进入监听

头文件:#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

原型:        int listen(int sockfd, int backlog);

功能:        将一个已绑定的套接字置于被动监听状态,告诉操作系统该套接字用于接受来自客户端的连接请求。此调用后,套接字从“CLOSED”状态变为“LISTEN”状态。

参数:        

  • int sockfd:已通过 bind() 绑定的套接字描述符。

  • int backlog连接请求队列的最大长度。当多个客户端同时发起连接时,已完成三次握手但尚未被服务器 accept() 的连接会排在这个队列里。此参数定义了队列的最大值。通常设置为 510 或更大,如 SOMAXCONN(系统允许的最大值)

返回值:     成功 返回0

                   失败 返回-1,并设置errno

示例代码
//监听套接字(建立连接),3-三次握手的排队的客户端个数(同一时刻)
listen(listfd,3);

<4>accept() 接受(建立)连接 【发生三次握手】

头文件:#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

功能:      从 listen() 设置的连接请求队列中取出一个已建立的连接,并为这个连接创建一个新的                     套接字。原来的监听套接字继续用于接受其他连接。
//这是一个阻塞调用(默认情况下)。如果队列为空,进程会进入睡眠状态,直到                     有新的连接到达。      

参数:       

  • int sockfd:处于监听状态的套接字描述符(server_fd)。

  • struct sockaddr *addr:一个指向 struct sockaddr 的指针,用于获取客户端的地址信息(IP和端口)。如果不需要,可以设为 NULL

  • socklen_t *addrlen:这是一个值-结果参数。调用时,需要将其初始化为 addr 指向的缓冲区的大小。函数返回时,它会被设置为实际存放地址信息的长度。

返回值:     成功 返回一个新的套接字描述符(new_socket)这个新套接字是专门用于与这个                       特定客户端通信的。

                  失败 返回-1 ,并设置errno

示例代码
//通信套接字,用于后续通信的通道int conn = accept(listfd,(SA)&cli,&len);if(-1 == conn){perror("accept fail");return 1;}

<5>recv() 接收数据

头文件:#include <sys/types.h>
#include <sys/socket.h>

原型:        ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能:        从已连接的套接字(由 accept() 返回的)接收对端发送过来的数据

参数:        

  • int sockfd:已连接的套接字描述符(new_socket)。

  • void *buf:指向接收缓冲区的指针,用于存放接收到的数据。

  • size_t len:接收缓冲区的最大长度。

  • int flags:标志位,用于控制接收行为。通常设置为 0(表示阻塞模式,常规操作)

返回值:     成功 返回0(0表示对端已经关闭连接,即收到了FIN包); 返回实际接收到的字节数(>0)

                   失败 返回-1,并设置errno


<6>send() 发送数据

头文件:#include <sys/types.h>
#include <sys/socket.h>

原型:        ssize_t send(int sockfd, const void *buf, size_t len, int flags);

功能:        从已连接的套接字(由 accept() 返回的)接收对端发送过来的数据

参数:        

  • int sockfd:已连接的套接字描述符(new_socket)。

  • const void *buf:指向要发送数据的缓冲区的指针。

  • size_t len:要发送的数据的字节数。

  • int flags:标志位,通常设置为 0

返回值:     成功 返回实际发送出去的字节数

                   失败 返回-1,并设置errno


<7>close() 关闭套接字和监听套接字


服务器端示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <netinet/in.h>typedef struct sockaddr *SA;int	main(int argc, char **argv)
{//监听套接字int listfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == listfd){perror("socket fail");return 1;}//man 7 ipstruct sockaddr_in ser,cli;bzero(&ser, sizeof(ser));bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);//127.0.0.1 表示自己的IP地址,本机自己接收// ser.sin_addr.s_addr = inet_addr("127.0.0.1");ser.sin_addr.s_addr = INADDR_ANY;int ret  =bind(listfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind fail");return 1;}//监听套接字(建立连接),3-三次握手的排队的客户端个数(同一时刻)listen(listfd,3);socklen_t len = sizeof(cli);//通信套接字int conn = accept(listfd,(SA)&cli,&len);if(-1 == conn){perror("accept fail");return 1;}time_t tm;while (1){char buf[1024] = {0};int ret = recv(conn,buf,sizeof(buf),0);if(ret <= 0)    //客户端断开连接{break;}time(&tm);sprintf(buf,"%s %s",buf,ctime(&tm));   //数据处理send(conn,buf,strlen(buf),0); }close(listfd);close(conn);return 0;
}

客户端

头文件:#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

<1>socket() 创建套接字

<2>connect() 发起连接

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

功能:        客户端调用此函数向指定的服务器发起连接请求。它会触发TCP三次握手过程

参数:        

  • int sockfdsocket() 函数返回的套接字描述符。

  • const struct sockaddr *addr:指向一个包含服务器协议家族、IP地址、端口号等信息的结构体的指针。这是连接的目标地址。(结构体指针信息详细见服务器端 bind()函数

    • 实际使用时,填充 struct sockaddr_in,然后强制转换为 struct sockaddr *

  • socklen_t addrlen:第二个参数所指向的结构体的大小

返回值:     成功 返回0 (表示此时三次握手成功,TCP连接已建立)

                   失败 返回-1,并设置errno

代码示例
//man 7 ipstruct sockaddr_in ser,cli;bzero(&ser, sizeof(ser));   bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);//127.0.0.1 表示自己的IP地址,本机自己接收// ser.sin_addr.s_addr = inet_addr("127.0.0.1");ser.sin_addr.s_addr = INADDR_ANY;//三次握手成功判断int ret = connect(conn,(SA)&ser,sizeof(ser));if(-1 == ret){perror("connect fail");return 1;}

<3>send() 发送数据

<4>recv() 接收数据

<5>close() 关闭打开的套接字(触发四次挥手)


客户端代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <netinet/in.h>typedef struct sockaddr *SA;int	main(int argc, char **argv)
{int conn = socket(AF_INET,SOCK_STREAM,0);if(-1 == conn){perror("socket fail");return 1;}//man 7 ipstruct sockaddr_in ser,cli;bzero(&ser, sizeof(ser));   bzero(&cli, sizeof(cli));ser.sin_family = AF_INET;ser.sin_port = htons(50000);//127.0.0.1 表示自己的IP地址,本机自己接收// ser.sin_addr.s_addr = inet_addr("127.0.0.1");ser.sin_addr.s_addr = INADDR_ANY;//三次握手成功判断int ret = connect(conn,(SA)&ser,sizeof(ser));if(-1 == ret){perror("connect fail");return 1;}int i = 10;while (i--){char buf[1024] = "hello this is client";send(conn,buf,strlen(buf),0);bzero(buf, sizeof(buf));recv(conn,buf,sizeof(buf),0);printf("from ser: %s\n",buf);sleep(1);}close(conn);   return 0;
}

数据粘包:因为tcp协议是流式套接字,数据与数据之间没有边界,发送方发送数据,接收方无法解析数据(接收方无法区分)

解决方案:

1.设置边界;

2.固定大小;

3.自定义协议;

TCP与UDP的区别

特性维度TCP (传输控制协议)UDP (用户数据报协议)
连接方式面向连接无连接
传输数据前必须通过三次握手建立可靠连接无需建立连接,可直接发送数据
可靠性安全可靠不安全、不可靠
通过应答确认、超时重传、流量控制、拥塞控制等机制保证数据不丢失、不重复、按序到达。不提供任何可靠性保证,可能丢失、重复、乱序
传输速度相对较慢非常快
由于复杂的控制机制和握手过程,延迟较高,传输速度慢。没有连接开销和复杂控制,延迟极低,传输速度快。
复杂度机制复杂实现简单
实现复杂,需要维护连接状态、序列号、确认号、窗口等。协议本身非常简单,几乎只有端口和校验和。
通信模式全双工通信支持全双工,但本质是报文交换
建立连接后,双方可同时可靠地发送和接收数据。使用发送缓冲区和接收缓冲区可以发送和接收,但每个数据包都是独立的。
数据形式字节流数据报
无消息边界,应用程序看到的是一串无结构的字节流,需自行处理粘包/拆包问题。有消息边界,接收到的数据与发送时完全一致,保留报文长度。
控制机制拥有完整的流量控制、拥塞控制、差错处理机制。无拥塞控制。网络拥堵时发送速率不变,会导致更高丢包率。
传输单元数据报
广播/组播不支持支持
仅支持一对一通信。支持一对一、一对多(广播)、多对多(组播)通信。
首部开销
标准首部20字节,加上可选选项可达60字节。固定首部仅8字节。
典型应用要求数据完整、可靠的应用:
• Web浏览
• 电子邮件
• 文件传输
• 数据库操作
要求低延迟、实时性的应用:
• 视频会议、直播
• 语音通话
• 在线游戏
• DNS查询
• SNMP网络管理
套接字类型SOCK_STREAM (流式套接字)SOCK_DGRAM (数据报套接字)

数据报:

发送次数和接收次数需要对应;

数据与数据之间有边界

http://www.dtcms.com/a/365740.html

相关文章:

  • 演员-评论员算法有何优点?
  • JavaScript原型与原型链:对象的家族传承系统
  • 3-7〔OSCP ◈ 研记〕❘ WEB应用攻击▸REST API概述
  • 漫谈《数字图像处理》之图像清晰化处理
  • 更新远程分支 git fetch
  • 计算机三级网络应用题大题技巧及练习题
  • 【微实验】使用MATLAB制作一张赛博古琴?
  • 最左匹配原则:复合索引 (a,b,c) 在 a=? AND b>? AND c=? 查询下的使用分析
  • 波浪模型SWAN学习(2)——波浪浅化模拟(Shoaling on sloping beach)
  • 14.错误和异常(二)
  • PastePal for Mac 剪贴板历史记录管理器
  • 学习嵌入式第四十五天
  • 设计原则与设计模式
  • flume拓扑结构详解:从简单串联到复杂聚合的完整指南
  • 蓝牙modem端frequency offset compensation算法描述
  • 技术重构人力管理 —— 打造人力资源流程自动化、智能化专业服务方案
  • 小企业环境-火山方舟和扣子
  • 字节跳动后端 一面凉经
  • 数据库与大数据技术栈
  • ElasticSearch倒排索引原理
  • redis中五大数据类型的操作命令
  • 编程基础-eclipse创建第一个程序
  • 【开题答辩全过程】以 基于java的隔离酒店管理系统设计与开发为例,包含答辩的问题和答案
  • 线程通信机制
  • 记录一下node后端写下载https的文件报错,而浏览器却可以下载。
  • 开源与闭源的再对决:从Grok到中国力量,AI生态走向何方?
  • 并发编程指南 同步操作与强制排序
  • Claude Code初体验:让AI成为你的结对程序员
  • Linux学习——管理基本存储(十八)
  • A股大盘数据-2025093分析