IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络编程TCP/UDP—2 -> 正文阅读

[网络协议]网络编程TCP/UDP—2

一、相关函数

1、inet_ntop() //网络字节序IP转字符串

SYNOPSIS

   #include <arpa/inet.h>

   const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

==> af : 地址族 AF_INET
==> src : 需要转换的网络字节序的IP变量的地址
==> dst : 转换之后的IP存放的位置
==> size : 第三个参数的size

在这里插入图片描述

2、Send()

NAME
send — send a message on a socket

SYNOPSIS

   #include <sys/socket.h>
   
   ssize_t send(int socket, const void *buffer, size_t length, int flags);

==> socket : TCP套接字
==> buffer : 需要发送的数据
==> length : 发送的数据的大小
==> flags : 数据属性 0 (如果flags赋值0,那么send就相当于write)

3、Recv()

NAME
recv — receive a message from a connected socket

SYNOPSIS

   #include <sys/socket.h>
   
   ssize_t recv(int socket, void *buffer, size_t length, int flags);

==> socket : TCP套接字
==> buffer : 需要接收的数据存放的缓冲区
==> length : 接收的数据的大小
==> flags : 数据属性 0 (如果flags赋值0,那么recv就相当于read)

二、服务器连接多个客户端

实现一个服务器同时连接多个客户端,可以接收每一个客户端的信息。

==> 分析
服务器在初始化套接字之后,TCP套接字会升级为监听套接字,监听套接字可以循环的被客户端连接,每次客户端连接成功,accept函数会返回一个会话ID。

==> 实现方案
服务器初始化监听套接字之后,主线程循环的等待客户端连接,每次有一个客户端成功连接,那就创建一条线程去循环接收这个会话ID里面的内容

例子: 设计一个服务器端程序,服务器可以最多同时连接20个客户端,每次有客户端成功连接,那就打印”客户端ip[192.168.15.xx],port[xxx]连接成功!”, 然后这个客户发送信息都可以接收到。

例子1:

实现一个服务器转发信息功能。服务器循环接收客户端的连接,客户端连接成功之后可以给服务器发送数据,服务器接收到数据之后就给每一个连接的客户端进行转发。

服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

int cli_num;		//当前连接的客户端的数量
int talkfd[20];		//存放客户端的会话ID

/*线程*/
void*Recv_Msg(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024];
	while(1)
	{
		memset(buf,0, sizeof(buf));
		if(recv(fd, buf, sizeof(buf), 0) < 0)
		{
			break;
		}
		printf("recv[%d]:%s", fd, buf);
		//全部转发数据(发送消息的客户端也会收到)	
		for(int i=0; i<cli_num; i++)
		{
			send(talkfd[i], buf, sizeof(buf), 0);	
		}
	}
	printf("client [%d] is disconnect!\n", fd);
	//优化:使用链表存储会话ID,如果有客户端断开连接,删除这个链表节点
	close(fd);
}

/*服务器转发功能 -- 服务器*/
int main(int argc, char *argv[])
{
	//0, 执行程序格式  ./TCP_server <port>
	if(argc != 2)
	{
		printf("按照格式执行程序:%s <port>\n", argv[0]);
		return -1;
	}
	
	//1, 获取TCP套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("socket failed");
		return -1;
	}
	
	//2, bind本机IP和端口号
	struct sockaddr_in  server_addr, client_addr;
	socklen_t addrlen = sizeof(server_addr);
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port =  htons(atoi(argv[1]));	//把字符串的端口转换成网络字节序端口号
	//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	//本机的IP	
	if(bind(sockfd, (struct sockaddr *)&server_addr, addrlen) < 0)
	{
		perror("bind failed");
		return -1;
	}	
	
	//3, 设置监听套接字
	listen(sockfd, 16);
	
	//4, 循环等待客户端连接
	pthread_t tid;
	char ip_addr[20] = {0};
	while(1)
	{
		if(cli_num >= 20)
		{
			printf("客户端已满!\n");
			sleep(1);
			continue;
		}
		talkfd[cli_num] = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
		if(-1 == talkfd[cli_num])
		{
			printf("服务器炸了!\n");
			continue;
		}		
			
		inet_ntop(AF_INET, &(client_addr.sin_addr), ip_addr, sizeof(ip_addr));
		printf("client ip[%s], port[%d] is connect talkfd:%d\n", ip_addr, ntohs(client_addr.sin_port), talkfd[cli_num]);
		
		pthread_create(&tid, NULL, Recv_Msg, (void *)&talkfd[cli_num]);
		//==> 如果有客户端连接,那就记录会话ID,创建线程去接收信息
		//==> 每一条线程循环接收数据,如果接收到数据那就转发给所有的客户端	
		
		cli_num++;
	}
	
	//5, 关闭会话套接字,关闭监听套接字
	close(sockfd);
	
	return 0;
}

客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

pthread_t tid1, tid2;

/*读取信息*/
void *Recv_Msg(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024];
	while(1)
	{
		memset(buf, 0, sizeof(buf));
		if( read(fd, buf, sizeof(buf)) <= 0)
		{
			pthread_cancel(tid2);	//关闭写入线程
			pthread_exit(NULL);		
		}
		printf("readbuf:%s", buf);
		if( strcmp("QUIT_TALK\n", buf) == 0 )
		{
			pthread_cancel(tid2);	//关闭写入线程
			pthread_exit(NULL);
		}		
	}
}

/*写入信息*/
void *Send_Msg(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024];
	while(1)
	{
		memset(buf, 0, sizeof(buf));

		fgets(buf, sizeof(buf), stdin);
		write(fd, buf, sizeof(buf));
		if( strcmp("QUIT_TALK\n", buf) == 0 )
		{
			pthread_cancel(tid1);	//关闭读取线程
			pthread_exit(NULL);
		}		
	}
}

/*TCP双向通信--客户端: 执行格式:./client <port> <IP>*/
int main(int argc, char *argv[])
{
	//0,执行格式
	if(argc != 3)
	{
		printf("请按照格式执行程序:%s <port> <IP>\n", argv[0]);
		return -1;
	}
	
	//1,获取TCP套接字		--	socket()
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
	{
		perror("socket failed");
		return -1;
	}	
	
	//2,连接服务器			--	connect()
	struct sockaddr_in  server_addr;
	socklen_t addrlen = sizeof(server_addr);
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port =  htons(atoi(argv[1]));	//把字符串的端口转换成网络字节序端口号
	server_addr.sin_addr.s_addr = inet_addr(argv[2]);	//把字符串的IP转换成网络字节序IP
	//server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	//本机的IP	
	
	if(connect(sockfd,  (struct sockaddr *)&server_addr, addrlen) < 0 )
	{
		perror("connect failed");
		return -1;
	}
	
	//3,创建两条线程 --> 一条线程循环读取; 一条线程循环写入

	pthread_create(&tid1, NULL, Recv_Msg, (void *)&sockfd);		//读信息线程
	pthread_create(&tid2, NULL, Send_Msg, (void *)&sockfd);		//写信息线程
	
	//4,主线程保证不结束
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	
	close(sockfd);	
	
	
	return 0;
}

左上为服务器,其他3为客户端,在3个终端页面运行同一个客户端代码就能产生3个客户端。
在这里插入图片描述

三、TCP传输控制协议实现文件传输

TCP传输协议是一个可靠的传输协议,在通信之前需要先建立连接。

1、TCP通信中的三次握手与四次分手

在这里插入图片描述

2、实现文件传输的步骤

1)发送方

==> 给发送方文件路径名 (判断文件是否存在,不存在就结束,如果存在,那就获取文件信息: 文件名字,大小,属性…)

==> 连接到接收方 --> connect

==> 第一个数据包包含文件的相关信息 (文件名,文件大小…)

==> 循环发送文件内容 --> 直到文件内容被发送完毕,结束。

==> 断开连接,结束程序

2)接收方

==> 初始化网络连接 --> accept();

==> 接收第一个数据包,对数据包进行解析,得到文件的相关信息

==> 在本地创建一个同名文件

==> 循环接收数据,写入新文件 (直到接收到的数据大小等于文件大小时停止接收)

==> 断开连接,结束程序。

例子2:

tcp传输文件
接收方

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

/*文件接收方*/
int main(int argc, char *argv[])
{
	//1,获取TCP套接字  -- socket()
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
	{
		perror("socket failed");
		return -1;
	}
	
	//2,绑定自己的IP和端口号	-- bind()
	struct sockaddr_in  server_addr, client_addr;
	socklen_t addrlen = sizeof(server_addr);
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port =  htons(9999);	//把字符串的端口转换成网络字节序端口号
	//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	//本机的IP
	
	if(bind(sockfd, (struct sockaddr *)&server_addr, addrlen) < 0)
	{
		perror("bind failed");
		return -1;
	}
	
	//3,设置监听套接字		-- listen()
	listen(sockfd, 1);	
	
	int talkfd;
	long filesize, recvsize = 0;
	size_t ret;
	char filename[32] = {0};
	char ip_addr[20] = {0};
	char MsgBuf[1024] = {0};
	
	talkfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
	inet_ntop(AF_INET, &(client_addr.sin_addr), ip_addr, sizeof(ip_addr));
	printf("发送方 ip[%s], port[%d]\n", ip_addr, ntohs(client_addr.sin_port));	
	//5,接收第一个数据包 --> 解析文件名,文件大小
	recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
	sscanf(MsgBuf, "%ld:%s", &filesize, filename);
	printf("接收的文件名:%s, 文件大小:%ld\n",filename, filesize);
	
	//6,在本地创建一个同名文件 --> 打开文件
	FILE *fp = fopen(filename, "w");
	if(NULL == fp)
	{
		perror("fopen failed");
		return -1;
	}
	//7,循环接收数据,记录接收的数据总大小 --> 如果接收的总数据大小大于等于文件大小 --> 结束
	while(1)
	{
		memset(MsgBuf, 0, sizeof(MsgBuf));
		ret = recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
		fwrite(MsgBuf, 1, ret, fp);
		recvsize += ret;	//当前接收的文件大小
		if(recvsize >= filesize)
			break;
	}

	//8,关闭套接字,关闭文件描述符
	fclose(fp);
	close(talkfd);
	close(sockfd);
	
	return 0;
}

发送方

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

#include <sys/stat.h>



/*文件发送方*/
int main(int argc, char *argv[])
{
	//0,程序执行格式  ./send_file <filename> 
	if(argc != 2)
	{
		printf("安装格式执行:%s <文件路径名>\n", argv[0]);
		return -1;
	}
		
	//1,判断文件是否存在,如果存在,那就获取文件信息 (文件大小) -- stat
	if( access(argv[1], F_OK) )//access函数用来判断指定的文件或目录是否存在(F_OK)
	{
		printf("发送的文件不存在!\n");
		return -1;
	}
	struct stat buf;
	stat(argv[1], &buf);//获取文件信息
	printf("发送的文件:%s, 大小:%ld\n", argv[1], buf.st_size);
	
	//2,获取套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
	{
		perror("socket failed");
		return -1;
	}
	
	//3,连接到接收方
	struct sockaddr_in  recv_addr;
	socklen_t addrlen = sizeof(recv_addr);
	
	recv_addr.sin_family = AF_INET;
	recv_addr.sin_port =  htons(9999);	//把字符串的端口转换成网络字节序端口号
	recv_addr.sin_addr.s_addr = inet_addr("192.168.15.3");

	if(connect(sockfd,  (struct sockaddr *)&recv_addr, addrlen) < 0 )
	{
		perror("connect failed");
		return -1;
	}

	//4,发送第一个数据包  文件名:文件大小
	char MsgBuf[1024];
	memset(MsgBuf, 0, sizeof(MsgBuf));
	sprintf(MsgBuf, "%ld:%s", buf.st_size, argv[1]);
	send(sockfd, MsgBuf, sizeof(MsgBuf), 0);

	//5,循环发送数据 --> 循环读取文件内容 --> 循环发送 --> 发送完毕,结束
	FILE *fp = fopen(argv[1], "r");
	if(NULL == fp)
	{
		perror("fopen failed");
		return 0;
	}
	size_t ret;
	while(1)
	{
		ret = fread(MsgBuf, 1, sizeof(MsgBuf), fp);	//从一个流中读取文件信息  ret:成功读取的元素个数
		send(sockfd, MsgBuf, ret, 0);				//读取的文件信息发送给对方
		if(ret <= 0)
			break;
	}
	printf("文件发送结束!\n");
	//6,关闭套接字,关闭文件描述符
	fclose(fp);
	close(sockfd);
	
	return 0;
}

四、UDP传输协议实现通信

1、UDP协议特点

·面向非连接 (不需要建立连接 --> 效率高)
·不可靠传输协议 (不保证数据一定正确到达)

==> 一般在网络环境不太稳定的情况下需要使用UDP协议

2、UDP通信实现流程 – 寄信

在这里插入图片描述

==> 例子: 设计UDP通信的发送端和接收端功能代码。

相关函数:

1)Sendto() //–> 发送一个UDP数据包

SYNOPSIS

   #include <sys/types.h>
   #include <sys/socket.h>

   ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);

==> sockfd : 套接字
==> buf : 需要发送的数据内容
==> len : 需要发送的数据大小
==> flags : 数据标志 0
==> dest_addr : 对方的IP地址结构体
==> addrlen : 地址结构体大小
返回值: 成功返回实际发送的字节数,失败返回-1

2)recvform() //–> 接收数据

SYNOPSIS

   #include <sys/socket.h>

   ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
       int flags, struct sockaddr *restrict address,
       socklen_t *restrict address_len);

==> sockfd : 套接字
==> buf : 存放接收的的数据内容的缓冲区
==> len : 想要接收的数据的大小
==> flags : 数据标志 0
==> dest_addr : 发送的IP地址结构体
==> addrlen : 地址结构体大小
返回值: 成功返回实际接收的字节数,失败返回-1

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 16:59:17  更:2021-07-31 17:00:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 13:05:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计