TCP通信创建流程
1. 客户端创建TCP连接
在整个流程中, 主要涉及以下⼏个接⼝socket() : 创建套接字, 使⽤的套接字类型为流式套接字connect() : 连接服务器send() : 数据发送recv() : 数据接收
创建套接字
首先,我们需要创建套接字,套接字是通信的基础。我们可以通过 socket() 函数来创建套接字。
int socket(int domain, int type, int protocol);
参数:@domain地址族AF_UNIX, AF_LOCAL 本地通信,数据不仅过网卡AF_INET IPV4 ineter⽹通信 AF_INET6 IPV6 ineter⽹通信AF_PACKET 网卡上的数据包通信....@ type使⽤协议类型SOCK_STREAM 流式套接字(TCP)SOCK_DGRAM 报⽂套接字(UDP)SOCK_RAW原始套接字: (IP,ICMP)......@protocol协议编号0 : 让系统⾃动识别IPPROTO_TCP : TCP协议IPPROTO_UDP : UDP协议返回值:成功返回得到的⽂件描述符。当前可使用的最小描述符失败返回 -1
连接服务器
创建套接字之后,我们需要连接服务器。连接服务器需要调用 connect() 函数。
发起对套接字的连接 (基于⾯向连接的协议)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:@sockfd套接字描述符@addr 连接的套接字的地址结构对象的地址 (⼀般为服务器)服务器地址struct sockaddr {unsigned short sa_family; // 地址族 对应socket()中的domainchar sa_data[14]; // 地址数据 ip地址端口信息};struct sockaddr_in { short int sin_family; // 地址族 AF_INETunsigned short int sin_port; // 端口号struct in_addr sin_addr;// IP地址unsigned char sin_zero[8]; // 填充字节 为了对齐sockaddr};struct in_addr {uint32_t s_addr; // IP地址};@addrlen地址长度返回值:成功返回0失败返回-1 并设置 errno
数据发送
连接服务器之后,我们就可以向服务器发送数据。发送数据需要调用 send() 函数。
基于套接字(建⽴连接)发送数据
int send(int sockfd, const void *buf, size_t len, int flags);
参数:@sockfd套接字描述符@buf 发送的数据@len 发送数据的长度@flags 发送标志
函数返回值:成功返回发送的字节数失败返回-1,并设置errno
数据接收
服务器向客户端发送数据之后,客户端就可以接收数据。接收数据需要调用 recv() 函数。
接收套接字的数据 (基于⾯向连接的协议)
int recv(int sockfd, void *buf, size_t len, int flags);
参数:@sockfd套接字描述符@buf 接收的数据@len 接收数据的长度@flags 接收标志
函数返回值:成功返回接收的字节数失败返回-1,并设置errno
完整流程
//todo tcp客户端,循环发送数据,接收回传数据
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>#define N 128
//初始化socket
int init_socket(char *ip,char *port){int init_socket_fd= socket(AF_INET,SOCK_STREAM,0);if (init_socket_fd==-1){printf("init_socket err");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;socklen_t len=sizeof(server_addr);bzero(&server_addr,len);server_addr.sin_family=AF_INET;inet_aton(ip,&server_addr.sin_addr);server_addr.sin_port= htons(atoi(port));//连接int ret= connect(init_socket_fd,(struct sockaddr*)&server_addr,len);if (ret==-1){printf("connect error,连接失败\n");exit(EXIT_FAILURE);}return init_socket_fd;
}//客户端接收数据
int Client_Receive_data(int socket_fd){char receive_msg[N];bzero(receive_msg,N);int recv_len= recv(socket_fd, receive_msg,sizeof(receive_msg),0);if (recv_len == -1) {printf("recv error\n");exit(EXIT_FAILURE);}receive_msg[recv_len] = '\0';printf("收到客户端数据:[%s]\n",receive_msg);
}//客户端发送数据
int Client_Send_data(int socket_fd){char msg[N];while (1){bzero(&msg, sizeof (msg));printf("请输入:\n");fgets(msg, sizeof(msg),stdin);msg[strlen(msg)-1]='\0';printf("发送数据%s\n",msg);int Send_data_len= send(socket_fd,&msg, strlen(msg),0);if (Send_data_len==-1){printf("发送失败 send err\n");exit(EXIT_FAILURE);}printf("发送了%d个字节\n",Send_data_len);if (strncmp(msg, "exit", 4) == 0) {printf("退出通信\n");close(socket_fd);break;}break;//接收Client_Receive_data(socket_fd);}return 0;
}int main(){//初始化连接int socket_fd = init_socket("172.17.128.1","8888");//发送数据Client_Send_data(socket_fd);return 0;
}
服务端流程
在上述流程中,相对于客户端主要增加以下新的流程
bind : 绑定 ip 地址与端⼝号,⽤于客户端连接服务器
listen : 建⽴监听队列,并设置套接字的状态为 listen 状态, 表示可以接收连接请求
accept : 接受连接, 建⽴三次握⼿, 并创建新的⽂件描述符, ⽤于数据传输
socket 套接字状态如下图:
CLOSED : 关闭状态
SYN-SENT : 套接字正在试图主动建⽴连接 [发送 SYN 后还没有收到 ACK],很短暂
SYN-RECEIVE : 正在处于连接的初始同步状态 [收到对⽅的 SYN,但还没收到⾃⼰发过去的SYN 的 ACK]
ESTABLISHED : 连接已建⽴
bind 函数 绑定 ip 地址与端⼝号,
函数头文件:
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);函数功能:
绑定 ip 地址与端⼝号, 使得套接字可以接收客户端的连接请求参数:
sockfd : 套接字描述符
addr : 指向 sockaddr 结构体的指针, 包含了要绑定的 ip 地址和端⼝号
addrlen : 结构体 sockaddr 的长度返回值:
成功 : 0
失败 : -1, 并设置 errno 变量
在服务器绑定 ip 地址与端⼝号之后, 则需要让服务器 socket 套接字设置成被动监听状态,并
创建监听队列,这⾥需要调⽤ listen 函数
listen 函数 建⽴监听队列
函数头文件:
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd, int backlog);函数功能:
建⽴监听队列, 并设置套接字的状态为 listen 状态, 表示可以接收连接请求参数:
sockfd : 套接字描述符
backlog : 监听队列的最大长度返回值:
成功 : 0
失败 : -1, 并设置 errno 变量
在服务器端调用 listen 函数之后, 则可以开始接收客户端的连接请求, 并创建新的套接字
用于数据传输, 这⾥需要调⽤ accept 函数
accept 函数 接受连接, 建⽴三次握⼿, 并创建新的⽂件描述符, ⽤于数据传输
函数头文件:
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);函数功能:
接受连接, 建⽴三次握⼿, 并创建新的⽂件描述符, ⽤于数据传输参数:
sockfd : 套接字描述符
addr : 指向 sockaddr 结构体的指针, 用于返回客户端的 ip 地址和端⼝号
addrlen : 指向 socklen_t 类型的指针, 用于返回 sockaddr 结构体的长度返回值:
成功 : 新的套接字描述符
失败 : -1, 并设置 errno 变量
在服务器端调用 accept 函数之后, 则可以接收客户端的连接请求, 并创建新的套接字用于数据
传输, 调⽤ recv 和 send 函数进行数据传输
// todo TCP服务端程序 循环接收客户端数据,将数据回传
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>#define N 128//初始化socket
int init_socket(char *ip,char *port){int init_socket_fd= socket(AF_INET,SOCK_STREAM,0);if (init_socket_fd==-1){printf("init_socket err");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;socklen_t len=sizeof(server_addr);bzero(&server_addr,len);server_addr.sin_family=AF_INET;inet_aton(ip,&server_addr.sin_addr);server_addr.sin_port= htons(atoi(port));int bind_ret= bind(init_socket_fd,(struct sockaddr*)&server_addr,len);if (bind_ret == -1) {printf("bind error\n");exit(EXIT_FAILURE);}int listen_ret= listen(init_socket_fd,10);if (listen_ret == -1) {printf("listen error\n");exit(EXIT_FAILURE);}return init_socket_fd;
}//客户端发送消息
int Server_Send_data(int clientFD,char* msg){strcat(msg,"-回传");int server_send_len=send(clientFD,msg,strlen(msg),0);if (server_send_len == -1) {printf("send error\n");exit(EXIT_FAILURE);}if (server_send_len == 0) {printf("客户端关闭连接\n");return -1;}printf("发送给客户端数据:[%s]\n",msg);return 0;
}//接收数据
int Server_Receive_data(int clientFD){while (1){//接收-使用新的文件描述符char recv_buf[N];bzero(recv_buf, sizeof(recv_buf));int recv_len = recv(clientFD, recv_buf, sizeof(recv_buf), 0);if (recv_len == -1) {printf("recv error\n");exit(EXIT_FAILURE);}if (recv_len == 0) {printf("客户端关闭连接\n");break;}if (strncmp(recv_buf, "exit", 4) == 0) {printf("客户端退出通信\n");close(clientFD);break;}printf("收到客户端消息:|%s|\n",recv_buf);Server_Send_data(clientFD, recv_buf);}return 0;
}int main(){int socket_fd = init_socket("172.17.140.183","8080");struct sockaddr_in cli_addr;socklen_t cli_len=sizeof(cli_addr);//获取客户端连接int clientFD= accept(socket_fd,(struct sockaddr*)&cli_addr,&cli_len);if (clientFD == -1){printf("accept error\n");exit(EXIT_FAILURE);}printf("连接 ip:%s, port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));//接收数据Server_Receive_data(clientFD);//关闭连接close(clientFD);
}