TCP(TCP客户端、服务器如何通信)

embedded/2024/11/25 17:33:56/

一、TCP介绍

TCP的特点:

  1. 面向连接的协议:TCP是一种可靠的、面向连接的协议,在通信之前需要建立连接,以确保数据的可靠传输。这意味着在传输数据之前,发送方和接收方之间需要建立一条可靠的连接通道。
  2. 流式协议:TCP将数据看作是一连串的字节流,而不是独立的数据包。这意味着在传输过程中,数据可以被分割成多个数据包进行传输,并在接收端进行重新组装。
  3. 可靠传输:TCP通过使用校验和、序列号和确认应答等机制,确保数据的可靠传输。如果在传输过程中发生错误或丢失,TCP会进行重传,以确保数据的完整性和准确性。
  4. 出错重传:当TCP接收到错误的数据包时,它会要求发送方重新发送该数据包,以确保数据的正确性。
  5. 确认应答:TCP使用确认应答机制来确保数据的可靠传输。当接收方收到一个数据包时,它会向发送方发送一个确认应答,表示已经收到了该数据包。
  6. 服务器被动连接,客户端主动连接:在TCP连接中,服务器通常处于被动状态,等待客户端的连接请求。而客户端则处于主动状态,负责发起连接请求。一旦连接建立成功,双方就可以进行数据传输。  

TCP与UDP的差异

TCP与UDP流程对比 

TCP编程流程 

服务器端流程:

  1. 创建套接字(socket):使用socket()函数创建一个套接字,指定使用的协议族(如IPv4或IPv6)和socket类型(如流式socket)。
  2. 绑定套接字(bind):使用bind()函数将套接字与服务器网络信息(如IP地址和端口号)进行绑定。
  3. 监听套接字(listen):使用listen()函数将套接字设置为监听状态,准备接收客户端的连接请求。
  4. 接受连接(accept):使用accept()函数阻塞等待客户端的连接请求,并返回一个新的套接字用于与该客户端进行通信。
  5. 进行通信(recv/send):使用recv()send()函数与客户端进行数据的接收和发送。
  6. 关闭套接字(close):通信完成后,使用close()函数关闭套接字,释放资源。

客户端流程:

  1. 创建套接字(socket):与服务器端相同,使用socket()函数创建一个套接字。
  2. 连接服务器(connect):使用connect()函数向服务器发起连接请求,指定服务器的IP地址和端口号。
  3. 进行通信(send/recv):连接建立后,使用send()recv()函数与服务器进行数据的发送和接收。
  4. 关闭套接字(close):通信完成后,使用close()函数关闭套接字,释放资源。

 二、TCP编程-socket

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);功能:创建一个套接字,返回一个文件描述符
参数:domain:通信域,协议族AF_UNIX 本地通信AF_INET ipv4网络协议AF_INET6 ipv6网络协议AF_PACKET 底层接口type:套接字的类型SOCK_STREAM 流式套接字(tcp)SOCK_DGRAM 数据报套接字(udp)SOCK_RAW 原始套接字(用于链路层)protocol:附加协议,如果不需要,则设置为0返回值:成功:文件描述符失败:‐1

 案例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{//通过socket函数创建一个TCP套接字int sockfd;if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("fail to socket");exit(1);}printf("sockfd = %d\n", sockfd);return 0;
}

这段代码演示了如何使用C语言创建一个TCP套接字。在代码中,首先通过socket()函数创建了一个套接字,指定了协议族为AF_INET(IPv4),套接字类型为SOCK_STREAM(TCP)。如果创建失败,则会输出错误信息并退出程序。如果创建成功,则会输出套接字的文件描述符。最后,程序返回0表示正常退出。

 执行结果

 三、TCP客户端-connect、send、recv

3.1 connect函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:给服务器发送客户端的连接请求
参数:sockfd:文件描述符,socket函数的返回值addr:要连接的服务器网络信息结构体(需要自己设置)addrlen:add的长度
返回值:成功:0失败:-1

注意:

  1. connect建立连接之后不会产生新的套接字
  2. 连接成功后才可以开始传输TCP数据
  3. 头文件:#include 

3.2 send函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);功能:发送数据
参数:sockfd:文件描述符客户端:socket函数的返回值
服务器:accept函数的返回值buf:发送的数据len:buf的长度flags:标志位0 阻塞MSG_DONTWAIT 非阻塞返回值:成功:发送的字节数失败:-1

注意不能用TCP协议发送0长度的数据包

3.3 recv函数

#include <sys/types.h>#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:sockfd:文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值buf:保存接收到的数据len:buf的长度flags:标志位0 阻塞MSG_DONTWAIT 非阻塞
返回值:成功:接收的字节数失败:-1
如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0

3.4 客户端code

使用windows下的网络调试助手作为服务器

客户端的程序 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>#define N 128int main(int argc, char const *argv[])
{if(argc < 3){fprintf(stderr, "Usage: %s [ip] [port]\n", argv[0]);exit(1);}//第一步:创建套接字int sockfd;if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("fail to socket");exit(1);}//printf("sockfd = %d\n", sockfd);//第二步:发送客户端连接请求struct sockaddr_in serveraddr;socklen_t addrlen = sizeof(serveraddr);serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1){perror("fail to connect");exit(1);}//第三步:进行通信//发送数据char buf[N] = "";fgets(buf, N, stdin);buf[strlen(buf) - 1] = '\0';if(send(sockfd, buf, N, 0) == -1){perror("fail to send");exit(1);}//接收数据char text[N] = "";if(recv(sockfd, text, N, 0) == -1){perror("fail to recv");exit(1);}printf("from server: %s\n", text);//第四步:关闭套接字文件描述符close(sockfd);return 0;
}

这段代码是一个简单的TCP客户端程序。它接收两个命令行参数:服务器的IP地址和端口号。然后,程序会执行以下步骤:

  1. 创建一个TCP套接字。
  2. 使用connect()函数向服务器发送连接请求。
  3. 从标准输入读取一行数据,并将其发送给服务器
  4. 接收服务器返回的数据,并将其打印到标准输出。
  5. 关闭套接字文件描述符。

 执行结果

 

四、TCP服务器-bind、listen、accept 

4.1 做为TCP服务器需要具备的条件

1、具备一个可以确知的地址

2、让操作系统知道是一个服务器,而不是客户端

3、等待连接的到来 对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始

4.2 bind函数

#include <sys/types.h>#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:sockfd:文件描述符,socket的返回值addr:网络信息结构体通用结构体(一般不用)struct sockaddr网络信息结构体 sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
addrlen:addr的长度
返回值:成功:0失败:-1

 简单写个例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>int main() {// 创建一个流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("socket");return -1;}// 初始化网络信息结构体struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8080); // 绑定到8080端口addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到任意IP地址// 将套接字与网络信息结构体绑定socklen_t addrlen = sizeof(addr);if (bind(sockfd, (struct sockaddr *)&addr, addrlen) == -1) {perror("bind");return -1;}printf("Bind successful\n");// 关闭套接字close(sockfd);return 0;
}

 在这个实例中,我们首先使用socket()函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待客户端的连接请求
参数:sockfd:文件描述符,socket函数的返回值addr:接收到的客户端的信息结构体(自动填充,定义变量即可)addrlen:addr的长度
返回值:成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)失败:-1

创建了一个流式套接字,然后初始化了一个sockaddr_in结构体,将套接字绑定到8080端口和任意IP地址。最后,我们使用bind()函数将套接字与网络信息结构体绑定,如果绑定成功,则会输出"Bind successful"。

 4.3 listen函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:将套接字设置为被动监听状态,这样做之后就可以接收到连接请求
参数:sockfd:文件描述符,socket函数返回值backlog:允许通信连接的主机个数,一般设置为5、10
返回值:成功:0失败:-1

4.4 accept函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待客户端的连接请求
参数:sockfd:文件描述符,socket函数的返回值addr:接收到的客户端的信息结构体(自动填充,定义变量即可)addrlen:addr的长度
返回值:成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)失败:-1

 4.5 TCP服务器例子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>#define N 128int main(int argc, char const *argv[])
{if(argc < 3){fprintf(stderr, "Usage: %s [ip] [port]\n", argv[0]);exit(1);}//第一步:创建套接字int sockfd;if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("fail to socket");exit(1);}//第二步:将套接字与服务器网络信息结构体绑定struct sockaddr_in serveraddr;socklen_t addrlen = sizeof(serveraddr);serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1){perror("fail to bind");exit(1);}//第三步:将套接字设置为被动监听状态if(listen(sockfd, 10) == -1){perror("fail to listen");exit(1);}//第四步:阻塞等待客户端的链接请求int acceptfd;struct sockaddr_in clientaddr;if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) == -1){perror("fail to accept");exit(1);}//打印连接的客户端的信息printf("ip:%s, port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));//第五步:进行通信//tcp服务器与客户端通信时,需要使用accept函数的返回值char buf[N] = "";if(recv(acceptfd, buf, N, 0) == -1){perror("fail to recv");}printf("from client: %s\n", buf);strcat(buf, " *_*");if(send(acceptfd, buf, N, 0) == -1){perror("fail to send");exit(1);}//关闭套接字文件描述符close(acceptfd);close(sockfd);return 0;
}

 

  1. 创建一个TCP套接字。
  2. 将套接字与服务器的IP地址和端口号绑定。
  3. 将套接字设置为被动监听状态,等待客户端的连接请求。
  4. 阻塞等待客户端的连接请求,并接受连接。
  5. 打印连接的客户端的IP地址和端口号。
  6. 接收客户端发送的数据,并将其打印出来。
  7. 向客户端发送数据。
  8. 关闭与客户端的连接,并关闭套接字。

 执行结果

4.6  close关闭套接字

  1. 使用close函数即可关闭套接字 关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包
  2. 服务器时 1>关闭监听套接字将导致服务器无法接收新的连接,但不会影响已经建立的连接 2>关闭accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器 的监听
  3. 做客户端时 关闭连接就是关闭连接,不意味着其他

 


http://www.ppmy.cn/embedded/34132.html

相关文章

【DPU系列之】如何通过带外口登录到DPU上的ARM服务器?(Bluefield2举例)

文章目录 1. 背景说明2. 详细操作步骤2.1 目标拓扑结构2.2 连接DPU带外口网线&#xff0c;并获取IP地址2.3 ssh登录到DPU 3. 进一步看看系统的一些信息3.1 CPU信息&#xff1a;8核A723.2 内存信息 16GB3.3 查看ibdev设备 3.4 使用小工具pcie2netdev查看信息3.5 查看PCIe设备信息…

使用 docker-compose 编排 lnmp(dockerfile) 完成 wordpress

一、使用 docker-compose 编排 lnmp(dockerfile) 完成 wordpress 环境准备 ##修改主机名 hostnamectl set-hostname lyh bash [rootlyh ~]###关闭防火墙及核心防护 systemctl stop firewalld ##关闭核心防护 setenforce 0 ##关闭核心防护##安装Docker-20…

前端之深拷贝

前提&#xff1a; 就是在实际开发中&#xff0c;我有一个编辑的弹窗&#xff0c;可以查看和编辑&#xff0c;因为弹窗里面是一个步骤条&#xff0c;点击下一步就要向对应的接口发送请求&#xff0c;考虑到就比如我点击下一步&#xff0c;此次表箱信息其实不需要修改&#xff0…

抓包证书安装到安卓7.0+手机

前言: 首先理解一下,这个不只是证书到浏览器,而是抓包证书到安卓7.0+手机上的文章; 还有一点区分,在浏览器上装的证书,只是让抓包工具可以抓取手机浏览器的包,而不是抓取手机app上的包; 如果你的证书只是简单的在浏览器下进行安装,那么你的手机app是走不了代理网络的…

探索CSS3文本效果:打造魅力无限的网页排版

CSS3为网页设计带来了革命性的变化&#xff0c;特别是在文本效果方面&#xff0c;它赋予了开发者前所未有的创意空间。本文将带你深入了解CSS3中一些令人兴奋的文本效果&#xff0c;从基本的阴影处理到复杂的动画效果&#xff0c;每个技巧都将通过实际代码示例展现其魅力所在。…

Linux——命名管道

管道特点 只能用于具有具体祖先的进程之间的通信&#xff0c;通常&#xff0c;一个管道由一个进程创建&#xff0c;然后该进程调用fork&#xff0c;创建子进程&#xff0c;关闭相应的读写端&#xff0c;然后父子进程就可以通信了管道提供流式服务一般而言&#xff0c;进程退出…

UDP通信

文章目录 recvfrom和sendto函数UDP通信server和client流程UDP实现的并发服务器和客户端 recvfrom和sendto函数 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);sockfd&#xff1a; 套接字buf&#xff1a;缓冲…

PC端Discord设置代理2022

开始使用 方法非常简单&#xff0c;在此下载release.zip并解压version.dll https://github.com/aiqinxuancai/discord-proxy/releases​github.com/aiqinxuancai/discord-proxy/releases 将version.dll放在Discord.exe所在目录&#xff08;如Discord有更新&#xff0c;则可能…