TCP——客户端和服务器的双工通信
TCP协议下的全双工通信
不同网络的两台主机上的应用进程如果想要进行通信,就需要通过物理层、数据链路层、网络层这三层进行数据包转发,再通过传输层把数据包发送给主机中的指定进程,所以网络模型中的传输层至关重要。传输层中最为常见的两个协议分别是传输控制协议TCP(Transmission Control Protocol)和用户数据报协议UDP(User Datagram Protocol),想要掌握这两种协议,则需要阅读协议的标准文件RFC 793
TCP是面向连接的,高度可靠的,全双工的。基于字节流的传输协议。
-
TCP的握手机制
第一次:client -->sever 发送SEQ序号 , 标志:SYN
第二次:sever-->client 发送SEQ序号,ACK序号,两个标志:SYN,ACK
第三次:client -->sever 发送SEQ序号,ACK序号,标志:ACK
-
TCP的挥手机制
第一次:client -->sever 发送 SEQ序号,标志: FIN
第二次:sever-->client 发送 SEQ序号,ACK序号,标志:ACK
第三次:sever-->client 发送SEQ序号,ACK序号, 两个标志:FIN,ACK
第四次:client -->sever 发送SEQ序号,ACK序号,标志:ACK
程序实现:
客户端
/********************************************************************************************
* file name: 6_tcp_c.c
* author : liaojx2016@126.com
* date : 2024/06/05
* function : TCP协议下实现客户端的收发
* note : None
*
* CopyRight (c) 2023-2024 liaojx2016@126.com All Right Reseverd
*
*********************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
/**************************************************************************************
*
*
* @name : task_snd
* @func : 发送消息
* @params :
* @arg: 套接字文件的句柄地址
* @retval : None
* @date : 2024/06、05
* @author : liaojx2016@126.com
* @version:
* @note :
*
*
* ***********************************************************************************/
void* task_snd(void *arg)
{
socklen_t tcp_fd = *(socklen_t*)arg;
char buf[128]= {0};
while (1)
{
printf("请输入消息\n");
scanf("%s",buf);
//发送数据给服务器
write(tcp_fd,buf,sizeof(buf));
bzero(buf,sizeof(buf));
}
}
/**************************************************************************************
*
*
* @name : task_recv
* @func : 接受消息
* @params :
* @arg: 套接字文件的句柄地址
* @retval : None
* @date : 2024/06、05
* @author : liaojx2016@126.com
* @version:
* @note :
*
*
* ***********************************************************************************/
void* task_recv(void *arg)
{
socklen_t tcp_fd = *(socklen_t*)arg;
char buf[128] = {0};
//4.等待接受客户端的连接请求
struct sockaddr_in sever;
//socklen_t sever_len = sizeof(sever);
//5.说明双方建立连接,此时可以接收数据
while(1)
{
if( !read(tcp_fd,buf,sizeof(buf)))
{
continue;
}
printf("recv from [%s],data is = %s\n", inet_ntoa(sever.sin_addr) ,buf);
bzero(buf,sizeof(buf));
}
}
//运行指令格式:./xxx 端口(本机和目标端口都是这个,本机和目标端口可以不一样) 目标地址
int main(int argc, char const *argv[])
{
if (argc != 3)
{
fprintf(stderr, "argument is invaild ,errno:%d,%s\n", errno, strerror(errno));
exit(1);
}
//创建套接字文件
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_socket == -1)
{
fprintf(stderr, "tcp socket error,errno:%d,%s\n", errno, strerror(errno));
exit(1);
}
//2.绑定自身的IP地址和端口
struct sockaddr_in host_addr;
host_addr.sin_family = AF_INET; //协议族,是固定的
host_addr.sin_port = htons(atoi(argv[1])); //本机端口,必须转换为网络字节序
printf("port = %d\n",host_addr.sin_port);
//host_addr.sin_addr.s_addr = inet_addr(SEVER_ADDR); //本机地址 INADDR_ANY 这个宏是一个整数,所以需要使用htonl转换为网络字节序
host_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(tcp_socket,(struct sockaddr *)&host_addr, sizeof(host_addr));
//定义地址结构体,存目标地址信息
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET; // 协议族,是固定的
dest_addr.sin_port =htons( atoi(argv[1])); // 目标端口,必须转换为网络字节序
printf("port = %d\n",dest_addr.sin_port);
dest_addr.sin_addr.s_addr = inet_addr(argv[2]); //目标地址 "192.168.64.xxx" 已经转换为网络字节序 INADDR_ANY
//连接服务器)
printf("******开始连接*******\n");
int ret_con = connect(tcp_socket,(struct sockaddr*)&dest_addr,sizeof( dest_addr));
if (ret_con == -1)
{
fprintf(stderr, "connect error,errno:%d,%s\n", errno, strerror(errno));
exit(1);
}
//创建线程
pthread_t pthread_snd,pthread_recv;
pthread_create(&pthread_snd,NULL,task_snd,(void *)&tcp_socket);
pthread_create(&pthread_recv,NULL,task_recv,(void *)&tcp_socket);
pthread_detach(pthread_snd);
pthread_detach(pthread_recv);
pthread_exit(NULL);
//close(tcp_socket);
return 0;
}
服务器端
/********************************************************************************************
* file name: 6_tcp_sever.c
* author : liaojx2016@126.com
* date : 2024/06/05
* function : TCP协议下实现服务器端的收发
* note : None
*
* CopyRight (c) 2023-2024 liaojx2016@126.com All Right Reseverd
*
*********************************************************************************************/
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
//TCP服务器代码 ./xxx port
#define SEVER_ADDR "172.16.191.59"
int connect_fd[20];
int cnt;
struct sockaddr_in client;
/**************************************************************************************
*
*
* @name : task_snd
* @func : 发送消息
* @params :
* @arg: 套接字文件的句柄地址
* @retval : None
* @date : 2024/06、05
* @author : liaojx2016@126.com
* @version:
* @note :
*
*
* ***********************************************************************************/
void* task_snd(void *arg)
{
socklen_t tcp_communi = *(socklen_t*)arg;
char buf[128]= {0};
while (1)
{
printf("请输入消息\n");
scanf("%s",buf);
//发送数据给服务器
write(tcp_communi,buf,sizeof(buf));
bzero(buf,sizeof(buf));
}
}
/**************************************************************************************
*
*
* @name : task_recv
* @func : 接受消息
* @params :
* @arg: 套接字文件的句柄地址
* @retval : None
* @date : 2024/06、05
* @author : liaojx2016@126.com
* @version:
* @note :
*
*
* ***********************************************************************************/
void* task_recv(void *arg)
{
socklen_t tcp_communi = *(socklen_t*)arg;
char buf[128] = {0};
//struct sockaddr_in client;
//socklen_t client_len = sizeof(client);
while(1)
{
if( !read(tcp_communi,buf,sizeof(buf)))
{
continue;
}
printf("recv from [%s],data is = %s\n", inet_ntoa(client.sin_addr) ,buf);
bzero(buf,sizeof(buf));
}
}
int main(int argc, char const *argv[])
{
//1.创建TCP套接字
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_socket == -1)
{
fprintf(stderr, "tcp socket error,errno:%d,%s\n",errno,strerror(errno));
exit(1);
}
//2.绑定自身的IP地址和端口
struct sockaddr_in host_addr;
host_addr.sin_family = AF_INET; //协议族,是固定的
host_addr.sin_port = htons(atoi(argv[1])); //目标端口,必须转换为网络字节序
printf("port = %d\n",host_addr.sin_port);
//host_addr.sin_addr.s_addr = inet_addr(SEVER_ADDR); //目标地址 INADDR_ANY 这个宏是一个整数,所以需要使用htonl转换为网络字节序
host_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(tcp_socket,(struct sockaddr *)&host_addr, sizeof(host_addr));
//3.设置监听 队列最大容量是5
listen(tcp_socket,5);
//4.等待接受客户端的连接请求
//struct sockaddr_in client;
socklen_t client_len = sizeof(client);
connect_fd[cnt] = accept(tcp_socket,(struct sockaddr *)&client,&client_len); //会阻塞
char buf[128] = {0};
pthread_t pthread_snd,pthread_recv;
pthread_create(&pthread_snd,NULL,task_snd,(void *)&connect_fd[0]);
pthread_create(&pthread_recv,NULL,task_recv,(void *)&connect_fd[0]);
pthread_detach(pthread_snd);
pthread_detach(pthread_recv);
pthread_exit(NULL);
return 0;
}