Linux学习笔记:TCP协议

接着上一篇,本篇主要是关于TCP协议,在此之前,先介绍一个网络抓包工具---wireshark。

一、网络抓包工具

wireshark是个网络抓包工具,可以抓取经过网卡的网络数据,用来分析网络问题和网络数据。

Linux下安装:

bash

sudo apt-get install wireshark

抓包流程:

  1. 启动wireshark:sudo wireshark

  2. 选择抓取的网卡:any

  3. 选择抓取数据包的过滤条件并触发,比如 udp.port == 50000

  4. 进行网络通信

二、TCP协议

TCP在传输层,叫传输控制协议,也叫流式套接字。

TCP特点

  1. 有连接

  2. 面向字节流

  3. 安全可靠的传输协议,靠三次握手、四次挥手、应答机制、超时重传机制这些保证

  4. 机制复杂,实时性和效率没有UDP高

常见用TCP的协议:HTTPS、MQTT、FTP。

三次握手和四次挥手

三次握手是TCP建立连接时用的,通过三次握手确保通信双方都已经准备就绪,由客户端发起。

四次挥手是TCP断开连接时用的,确保通信双方数据都已经收发结束。

TCP编程流程

客户端:socket() → connect() → send() → recv() → close()

服务端:socket() → bind() → listen() → accept() → recv() → send() → close()

和UDP比,服务端多了listen和accept两步,客户端多了connect,收发用send和recv而不是sendto和recvfrom。

相关函数

connect

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

请求建立连接。参数是套接字、服务端地址、地址长度。成功返回0,失败返回-1。

listen

int listen(int sockfd, int backlog);

监听客户端的三次握手。backlog是最多允许监听的客户端个数。成功返回0,失败返回-1。

accept

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

接收完成三次握手的客户端,并返回一个通讯套接字。参数里的addr用来保存接入的客户端地址信息。成功返回通讯套接字,失败返回-1。

注意这里返回的新套接字才是用来和客户端收发数据的,原来的sockfd继续负责监听。

recv

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

接收网络数据。参数是通讯套接字、存放数据的空间首地址、期望接收的字节数、flags(0默认方式)。

返回值:成功返回实际收到的字节数,失败返回-1,返回0表示对方断开连接。

TCP报文头部

TCP报文头部有几个标志位:

  • SYN:请求建立连接标志位

  • ACK:应答报文标志位

  • PSH:携带数据的报文标志位

  • FIN:请求断开连接标志位

  • URG:紧急数据标志位

  • RST:重置标志位

TCP机制

  • 应答机制:TCP为发送的数据进行编号,发送数据时报文头部的序列号是这包数据第一个数据的编号;接收方需要给这包数据发ACK,ACK报文中确认号是收到的最后一个字节编号+1

  • 超时重传机制:TCP每发送一包数据后都要等待应答,如果超时时间之内没有收到应答,就重新发送这包数据

  • 滑动窗口机制:本质是个缓冲区,保存已发送并收到应答的数据、已发送未收到应答的数据、未发送但在对方处理范围内的数据

  • 延迟应答机制:TCP可以发送多组数据,发送的同时等待应答

  • 流量控制机制:TCP会根据发送端的数据处理和接收能力调整自己的发送速率,根据ACK中窗口值的大小进行动态调整流量

  • 捎带应答机制:ACK可以和应用层发送的数据一起发出,表示对上包数据的响应

三、TCP客户端示例

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

int main(void)

{

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

if (-1 == sockfd) {

perror("socket error");

return -1;

}

struct sockaddr_in seraddr;

seraddr.sin_family = AF_INET;

seraddr.sin_port = htons(50000);

seraddr.sin_addr.s_addr = inet_addr("192.168.0.119");

if (connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) {

perror("connect error");

close(sockfd);

return -1;

}

char buff1024 = {0};

fgets(buff, sizeof(buff), stdin);

buffstrcspn(buff, "\\n") = '\0';

ssize_t size = send(sockfd, buff, strlen(buff), 0);

if (-1 == size) {

perror("send error");

close(sockfd);

return -1;

}

memset(buff, 0, sizeof(buff));

size = recv(sockfd, buff, sizeof(buff) - 1, 0);

if (-1 == size) {

perror("recv error");

} else if (0 == size) {

printf("服务器断开连接\n");

} else {

printf("收到: %s\n", buff);

}

close(sockfd);

return 0;

}

四、TCP服务端示例

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <netinet/in.h>

#include <unistd.h>

#include <string.h>

int main(void)

{

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

if (-1 == sockfd) {

perror("socket error");

return -1;

}

struct sockaddr_in seraddr;

seraddr.sin_family = AF_INET;

seraddr.sin_port = htons(50000);

seraddr.sin_addr.s_addr = inet_addr("192.168.0.119");

if (bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) == -1) {

perror("bind error");

close(sockfd);

return -1;

}

if (listen(sockfd, 5) == -1) {

perror("listen error");

close(sockfd);

return -1;

}

struct sockaddr_in cliaddr;

socklen_t addrlen = sizeof(cliaddr);

int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);

if (-1 == connfd) {

perror("accept error");

close(sockfd);

return -1;

}

printf("客户端 %s:%d 接入\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

char buff1024 = {0};

while (1) {

memset(buff, 0, sizeof(buff));

ssize_t size = recv(connfd, buff, sizeof(buff) - 1, 0);

if (-1 == size) {

perror("recv error");

break;

}

if (0 == size) {

printf("客户端断开连接\n");

break;

}

printf("%s:%d : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);

send(connfd, buff, size, 0);

}

close(connfd);

close(sockfd);

return 0;

}

TCP和UDP最大的区别就是TCP要先建立连接再通信,服务端需要listen监听、accept接收客户端,收发用send和recv,断开时走四次挥手。

相关推荐
Brilliantwxx24 分钟前
【Linux】 进程(9)程序与进程地址空间(基础+进阶+面试题)
linux·运维·服务器·开发语言·c++
不怕犯错,就怕不做1 小时前
git prune 自动删除本地记录中那些远程已经不存在的分支引用
linux·服务器·git
噜~噜~噜~1 小时前
操作系统笔记-2.3.5.1.1生产者-消费者问题
笔记·操作系统
yi.Ist1 小时前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
晊晌_h2 小时前
嵌入式从0到精通——数据结构总结[特殊字符]
数据结构·算法·排序算法
我找到地球的支点啦2 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
罗西的思考2 小时前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
一池秋_2 小时前
arm低配linux设备,桌面应用冷启动提速方法
linux·运维·arm开发
惜离殇3 小时前
从零开始的敲代码生活--Linux应用软件(文件操作基础1)
linux·c语言·文件操作·标准io