阅读量:0
详细介绍如何在 C/C++ 中使用 TCP 协议实现客户端和服务器之间的网络通信。网络编程在现代软件开发中非常重要,尤其是开发分布式系统、实时应用程序或客户端-服务器架构时,掌握 TCP 网络通信的基本知识和操作流程是必不可少的。
目录
- 网络通信的基础知识
- TCP 协议简介
- 客户端与服务器端的通信流程
- 代码案例讲解
- 客户端代码
- 服务器端代码
- 总结
1. 网络通信的基础知识
网络通信指的是两台或多台计算机通过网络互相传递数据的过程。通常有两种通信模式:
- 面向连接的通信(如 TCP)
- 无连接的通信(如 UDP)
在面向连接的通信中,双方在发送数据之前首先需要建立一个可靠的连接。TCP 就是面向连接的协议,它确保数据的传输是可靠的。
2. TCP 协议简介
TCP(Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的协议。在 TCP 协议中,数据的传输过程如下:
- 建立连接:客户端和服务器端进行三次握手,建立可靠的连接。
- 数据传输:在连接建立之后,数据以流的方式进行传输,TCP 会确保数据的完整性和顺序。
- 断开连接:通信结束后,双方通过四次挥手断开连接。
3. 客户端与服务器端的通信流程
TCP 通信一般分为两部分:服务器端 和 客户端。
服务器端:
- 创建套接字(socket)
- 绑定 IP 地址和端口号
- 监听连接请求
- 接受客户端的连接
- 进行数据的收发
- 关闭连接
客户端:
- 创建套接字
- 连接到服务器
- 进行数据的收发
- 关闭连接
下面我们将通过代码案例来详细说明每一步的实现。
4. 代码案例讲解
4.1 客户端代码
客户端的功能主要是连接服务器,并向服务器发送数据。以下是一个简单的客户端代码示例:
用于通信的结构体定义:
#include<iostream> #include<cstring> #include<arpa/inet.h> #include<unistd.h> #include<stdlib.h> //客户端代码 int main() { // 1. 创建通信的套接字 int fd_lis = socket(AF_INET, SOCK_STREAM, 0); if(fd_lis == -1) { perror("socket_listen error!"); return -1; } // 2. 链接服务器的IP和端口 struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); inet_pton(AF_INET, "192.168.159.129", &saddr.sin_addr.s_addr); // 3. 连接服务器 int ret = connect(fd_lis, (struct sockaddr*)&saddr, sizeof(saddr)); if(ret == -1) { perror("connect_error"); return -1; } // 4. 开始通信 int number = 0; while (1) { std::cout << "客户端发送了信息" << std::endl; char buff[1024]; sprintf(buff, "你好,hello world,%d...\n", number++); send(fd_lis, &buff, sizeof(buff), 0); memset(buff, 0, sizeof(buff)); int len = recv(fd_lis, &buff, sizeof(buff), 0); if(len > 0) { std::cout << "server says: " << buff << std::endl; } else if (len == 0) { // 通信结束 break; } else { perror("recv_error"); break; } sleep(1); } // 5. 关闭套接字 close(fd_lis); return 0; }
客户端代码说明:
- 创建套接字:
socket()
函数用于创建套接字,使用 IPV4 协议(AF_INET
),流式传输(SOCK_STREAM
),并指定使用 TCP 协议(0
)。 - 指定服务器地址和端口:
struct sockaddr_in
用于指定服务器的 IP 地址和端口,htons(9999)
将端口号转为网络字节序,inet_pton
函数将字符串形式的 IP 地址转换为二进制形式。 - 连接服务器:
connect()
函数用于连接到服务器。 - 发送和接收数据:
send()
函数发送数据,recv()
函数用于接收服务器端返回的数据。 - 关闭套接字:
close()
关闭套接字,断开连接。
字节序和函数功能不清楚的,可以查看套接字-Socket | 爱编程的大丙。老师写的非常清楚,B站的课程也讲的十分清晰。
4.2 服务器端代码
服务器端的功能是接受客户端的连接请求,并进行通信。以下是服务器端的代码示例:
#include<iostream> #include<string> #include<arpa/inet.h> #include<unistd.h> //服务器端 int main(){ // 1:创建监听的套接字 具体参数含义为:使用IPV4地址,采用流式传输,使用TCP协议。 返回监听的文件描述符 int fd_lis=socket(AF_INET,SOCK_STREAM,0); if(fd_lis==-1){ perror("socket_listen error!"); return -1; } // 2:绑定本地IP和端口 (IP和端口是addr结构体) 该结构体指定地址类型,通信端口和IP地址 struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_port=htons(9999); //INADDR_ANY 使用宏定义的0地址,能够自动读取服务器的网卡IP地址 saddr.sin_addr.s_addr=INADDR_ANY; int ret = bind(fd_lis,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1){ perror("bing _error"); return -1; } // 3设置监听 (监听的文件描述符,一次最大监听的访问量) ret=listen(fd_lis,128); if(ret==-1){ perror("listen _error"); return -1; } // 4 阻塞并等待客户端的链接,该结构体存储来自客户端的端口和地址 struct sockaddr_in caddr; caddr.sin_family=AF_INET; caddr.sin_port=INADDR_ANY; int addrlen=sizeof(caddr); int fd_acp=accept(fd_lis,(struct sockaddr*)&caddr,(socklen_t*)&addrlen); if(fd_acp==-1){ perror("accept _error"); return -1; }else{ //打印端口,客户端的IP和端口号 char ip_arrd[32]; std::cout<<"客户端ip地址:"<<inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ip_arrd,sizeof(ip_arrd))<<std::endl; std::cout<<"客户端端口号:"<<ntohs(caddr.sin_port)<<std::endl; } // 5:开始通信 while (1) { char buff[1024]; int len=recv(fd_acp,&buff,sizeof(buff),0); if(len>0){ std::cout<<"client says:"<<buff<<std::endl; send(fd_acp,buff,sizeof(buff),0); }else if (len==0) { //通信结束 break; }else{ perror("accept_error"); break; } } close(fd_lis); close(fd_acp); return 0; }
服务器端代码说明:
- 创建套接字:与客户端类似,
socket()
函数创建监听套接字。 - 绑定 IP 和端口:使用
bind()
函数绑定本地 IP 和端口。INADDR_ANY
可以自动获取服务器的 IP 地址。 - 监听连接:
listen()
函数开始监听端口,允许多个客户端连接。 - 接受连接:
accept()
函数阻塞等待客户端的连接请求,并返回一个新的文件描述符,用于与客户端通信。 - 通信过程:使用
recv()
接收客户端发送的数据,使用send()
将数据返回给客户端。 - 关闭套接字:通信结束后,关闭文件描述符,断开连接。