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;
}
posted @ 2024-06-05 21:42  沉舟道人  阅读(45)  评论(0编辑  收藏  举报