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网络的通信 -> 正文阅读

[网络协议]TCP/UDP网络的通信

  • 一、基础通信
    • 1.TCP通信架构
    • 2.UDP通信
  • 总结

一、基础通信

1.TCP通信架构

对于服务器,其通信流程一般有如下步骤:

?? ?1.调用socket函数创建socket,这一步会创建一个文件描述符fd。
?? ?2.调用bind函数将socket(也就是fd)绑定到某个ip和端口的二元组上。
?? ?3.调用listen函数开启侦听端口。
?? ?4.调用accept阻塞等待接受连接,当有客户端请求连接上来后,

产生一个新的socket(客户端socket)。
?? ?5.基于新产生的socket调用send或recv函数开始与客户端进行数据通信。
?? ?6.通信结束后,调用close函数关闭客户端socket。
对于客户端,其通信流程一般有如下步骤:

?? ?1.调用socket函数创建客户端socket。
?? ?2.调用connect函数尝试连接服务器。
?? ?3.连接成功以后调用send或recv函数开始与服务器进行数据通信。
?? ?4.通信结束后,调用close函数关闭socket。

其流程图如下所示:

?服务器端代码:??

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

#include <netinet/in.h>
#include <netinet/ip.h>

#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <pthread.h>
#include <stdlib.h>


#include "common.h"

struct thread_msg {
	int cli_socket;
	pthread_t tid;
	
};


void *serv_for_client(void *args)
{
	int ret;
	struct thread_msg *pmsg = args;
	int socket = pmsg->cli_socket;
	
	struct msg_struct serv_msg;
	while(1){
		memset(&serv_msg,0,sizeof(serv_msg));
		ret = recv(socket,&serv_msg,sizeof(serv_msg),0);
		if(ret<0){
			perror("recv err"); close(socket); free(pmsg); return NULL;
		}else if(ret == 0){
			printf("serv found peer shutdwon\n"); close(socket);  free(pmsg);
			return NULL;
		}
		
		printf("serv got msg:temp%d %d %d cnt%d des:%s\n",\
			serv_msg.temp,serv_msg.humidity,serv_msg.wind,\
				serv_msg.sendcnt,serv_msg.des);		
		serv_msg.serv_response++;
	
		ret = send(socket,&serv_msg,sizeof(serv_msg),0);
		if(ret<0){
			perror("send err");  return NULL;
		}
	}
	close(socket);
	return NULL;
}


int main()
{
	/*
		
	   #include <sys/types.h>          
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
			domain:  域,范围,区域
				AF_UNIX,: linux进程内部通信
				AF_INET ,用于ipv4通信
				AF_INET6,用于ipv6通信
			type:
				SOCK_STREAM, TCP专属,提供面向连接 字节流 安全可靠服务
				SOCK_DGRAM,   UDP专属
				SOCK_RAW ,   自己实现一套接口
			protocol: 为0即可
			
			返回值:
				正数--新的连接socket
				-1:  errno
				
	*/
	int socket_main = socket(AF_INET,SOCK_STREAM, 0 );
	if(socket_main <0){
		perror("create main socket err");
		return -3;
	}
	
	/*
		给main socket指定服务器自己的ip和端口号
	   #include <sys/types.h>          
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
		addr:应该包含ip和端口号
			我们没有使用这个类型
				struct sockaddr {
				   sa_family_t sa_family;		//地址族,采用AF_xxx的形式,如:AF_INET
				   
				   char        sa_data[14];		//14字节的特定协议地址
			   }
			   
			编程中一般不针对 sockaddr  数据结构操作,而是使用与 sockaddr 等价的sockaddr_in 数据结构
			   
			我们使用的是下面的类型:
			
			   struct sockaddr_in {		man  7 ip
				   sa_family_t    sin_family; 	永远等于AF_INET.   address family: AF_INET 
				   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
				   
				   struct in_addr sin_addr;     internet address  IP地址
			   };

			   Internet address.  
			   struct in_addr {
				   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
			   };
		
			addrlen: addr结构体长度
			返回值:
				0-success 
				-1: errno
	
	*/
	struct sockaddr_in  serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10086);  //host to net short
	
	/* int inet_addr(char *ip)
		负责将字符串表示的 点分法,转换为 u32,以大端方式存放
	*/
	
	serv_addr.sin_addr.s_addr = INADDR_ANY;      //inet_addr("192.168.3.197");   将a.b.c.d形式的IP转换为32位的IP
											//将一个点分十进制的IP转换成一个长整型数(u_long类型)等同于inet_addr()。
	/*	如果你指定ip地址,改程序只能跑在指定服务器上,   如何让服务器自动获取本地ip
		serv_addr.sin_addr.s_addr = 0;  bind函数发现ip为0,自动获取本地ip	
	*/
	int ret =  bind(socket_main,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret<0){
		perror("bind err");
		return -34;
	}
	
	/*监听整个网络
	
	   #include <sys/types.h>          
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);
			backlog:  主socket在一瞬间可以同时处理的客户端个数
					一般选5,表示可以同时处理2*5+1
			返回:
				0-success 
				-1:errno
	*/
	ret = listen(socket_main,5);
	if(ret<0){
		perror("listen err");
		return -3;
	}
	
	/*
	
	
		等待客户端连接-----阻塞等待
		
	   #include <sys/types.h>         
       #include <sys/socket.h>

       int accept(int main_sockfd, struct sockaddr *addr, socklen_t *addrlen);
			main_sockfd:主socket,用于监听网络,等待客户端连接 
			addr:  客户端连接上来之后,客户端的ip+port存放在addr 
				真实的类型是 struct sockaddr_in
			
					  struct sockaddr_in {		man  7 ip
						   sa_family_t    sin_family; 	永远等于AF_INET.   address family: AF_INET 
						   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
						   struct in_addr sin_addr;     internet address  
					   };

					   Internet address.  
					   struct in_addr {
						   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
					   };
			addrlen:  双向参数  ,  对方地址的长度
					调用者(你)告诉accept存放地址的buf有多长.
					accept获取对方地址之后,会告诉真实的长度
			返回值:
				正数: 一个新的socket,用于和客户端进行通信
				-1:errno
	*/
	struct sockaddr_in cli_addr;
	socklen_t addrlen=sizeof(cli_addr);
	
	struct thread_msg *pmsg  =NULL;
	
	int socket_new;
loop:
	socket_new = accept(socket_main ,(struct sockaddr *)&cli_addr, &addrlen  );
	if(socket_new<0){
		perror("accept err");
		return -3;
	}
	
	printf("serv accept client ip=%s port=%d\n",\
			inet_ntoa (cli_addr.sin_addr  ),   ntohs(cli_addr.sin_port)  );
				// inet_ntoa ,将32位IP转换为a.b.c.d的格式
		
	
	pmsg = malloc(sizeof(*pmsg));
	pmsg->cli_socket = socket_new;
	pthread_create(&pmsg->tid,NULL,   serv_for_client   , pmsg   );

	//处理完毕一个连接,然后继续去等待新客户端
	goto loop;

	close(socket_main);  //一般最后才能关闭
	return 0;
}



客户端代码为:

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

#include <netinet/in.h>
#include <netinet/ip.h>

#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>

#include "common.h"

int main()
{
	int socket_cli = socket(AF_INET,SOCK_STREAM, 0 );
	if(socket_cli <0){
		perror("create client socket err");
		return -3;
	}
	
	/*
	
		//int connect(int socket_cli, serv_ip,serv_port);
	       #include <sys/types.h>          
       #include <sys/socket.h>

         int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

				struct sockaddr_in {
				   sa_family_t    sin_family; 	永远等于AF_INET.   address family: AF_INET 
				   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
				   struct in_addr sin_addr;     internet address  
			   };

			   Internet address.  
			   struct in_addr {
				   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
			   };
			成功返回 0
			失败 -1,errno 
		


	*/
	struct sockaddr_in  serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10086); 
	serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197"); 
	int ret = connect(socket_cli,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret <0){
		perror("connect err");
		return -3;
	} 
	
	struct msg_struct cli_msg;
	
	while(1){
		cli_msg.temp=45;
		cli_msg.humidity=40;
		cli_msg.wind=10;
		strcpy(cli_msg.des,"today sun day");
		
		ret = send(socket_cli,&cli_msg,sizeof(cli_msg),0);
		if(ret<0){
			perror("send err");
			return -34;
		}


		memset(&cli_msg,0,sizeof(cli_msg));
		ret = recv(socket_cli,&cli_msg,sizeof(cli_msg),0);
		if(ret<0){
			perror("recv err");
			return -34;
		}else if(ret ==0){
			printf("cli found serv shutdown\n");
			return 0;
		}
		
		printf("cli got msg:response=%d\n",cli_msg.serv_response);
		 
		sleep(1);
	}
}

头文件为:common.h

#ifndef COMM_HH
#define COMM_HH


struct msg_struct {
	char temp;
	int humidity;
	int wind;
	
	int sendcnt;
	int serv_response;
	char des[32];
};

#endif

2.UDP通信

udp通信架构:

? ? ? ? 对于udp通信没有严格的服务器和客户端的说法,谁先发送,谁就是发送端。 也没有没了 listen? accept 和 conect的这些操作,它天生支持并发。

流程图如下:

服务器端代码为:

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

#include <netinet/in.h>
#include <netinet/ip.h>

#include <arpa/inet.h>
#include <string.h>

#include "common.h"

int main()
{
	/*
		
	   #include <sys/types.h>          
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
			domain:  域,范围,区域
				AF_UNIX,: linux进程内部通信
				AF_INET ,用于ipv4通信
				AF_INET6,用于ipv6通信
			type:
				SOCK_STREAM, TCP专属,提供面向连接 字节流 安全可靠服务
				SOCK_DGRAM,   UDP专属
				SOCK_RAW ,    自己实现一套接口
			protocol: 为0即可
			
			返回值:
				正数--新的连接socket
				-1:  errno
				
	*/
	int socket_serv = socket(AF_INET,SOCK_DGRAM, 0 );
	if(socket_serv <0){
		perror("create main socket err");
		return -3;
	}
	
	/*
		给main socket指定服务器自己的ip和端口号
	   #include <sys/types.h>          
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
		addr:应该包含ip和端口号
			我们没有使用这个类型
				struct sockaddr {
				   sa_family_t sa_family;
				   char        sa_data[14];
			   }
		我们使用的是下面的类型:
		
		   struct sockaddr_in {		man  7 ip
			   sa_family_t    sin_family; 	永远等于AF_INET.   address family: AF_INET 
			   in_port_t      sin_port;     端口号以大端方式存放的   port in network byte order 
			   struct in_addr sin_addr;     internet address  
		   };

		   Internet address.  
		   struct in_addr {
			   uint32_t       s_addr;      IP地址,以大端方式存放   address in network byte order  
		   };

		addrlen: addr结构体长度
		返回值:
			0-success 
			-1: errno
	*/
	struct sockaddr_in  serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(10096);  //host to net short
	/* int inet_addr(char *ip)
		负责将字符串表示的 点分法,转换为 u32,以大端方式存放
	*/
	serv_addr.sin_addr.s_addr = INADDR_ANY;      //inet_addr("192.168.3.197"); 
	/*	如果你指定ip地址,改程序只能跑在指定服务器上,   如何让服务器自动获取本地ip
		serv_addr.sin_addr.s_addr = 0;  bind函数发现ip为0,自动获取本地ip	
	*/
	int ret =  bind(socket_serv,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret<0){
		perror("bind err");
		return -34;
	}


	char txbuf[128];
	struct msg_struct recvmsg;
	
	while(1){
		/*
		       #include <sys/types.h>
			   #include <sys/socket.h>
			   
			   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
								struct sockaddr *src_addr, socklen_t *addrlen);
					socketfd:通信的手机/套接字
					void *buf, size_t len ,接收数据存放的buf,和你想要接收的长度
					flags: 0
					src_addr: 对方的ip和端口号
					addrlen: 双向参数,你告诉它地址buf有多长,对方告诉你真实的长度
					返回值:
						正值--表示真实接收的字节数
						-1: errno 
		
		*/
		 struct sockaddr_in cli_addr;
		 socklen_t addrlen=sizeof(cli_addr);
	
		memset(&recvmsg,0,sizeof(recvmsg));
		ret = recvfrom(socket_serv,&recvmsg,sizeof(recvmsg),0, (struct sockaddr *)&cli_addr, &addrlen);
		if(ret<0){
			perror("recvfrom err");
			return -34;
		}
		
		printf("ser cli ip=%s %d temp%d %d %d cnt%d des:%s\n", 
			inet_ntoa (cli_addr.sin_addr  ), ntohs(cli_addr.sin_port),\
				recvmsg.temp,recvmsg.humidity,recvmsg.wind,recvmsg.sendcnt,recvmsg.des);
		
		/*
		   #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);
		
				返回值:  
					正数--真实发送的长度
					-1:errno 
		*/
		strcpy(txbuf,"hello cli,Your msg be got.");
		ret = sendto( socket_serv,  txbuf,strlen(txbuf), 0, (struct sockaddr *)&cli_addr, sizeof(cli_addr) );
		if(ret<0){ 
			perror("sendto err");
			return -34;
		}	

	}
}

客户端代码为:

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

#include <netinet/in.h>
#include <netinet/ip.h>

#include <arpa/inet.h>
#include <string.h>

#include "common.h"

int main()
{
	int ret; 
	
	int socket_cli = socket(AF_INET,SOCK_DGRAM, 0 );
	if(socket_cli <0){
		perror("create main socket err");
		return -3;
	}
	
	/* 
		客户端其实是可以bind的,但是一般我们都不用
		因为在sendto函数中,如果没有bind,会自动帮你分配一个
	*/
 
	char rxbuf[128];
	struct msg_struct cli_msg;
	memset(&cli_msg,0,sizeof(cli_msg));
	
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET; 
	serv_addr.sin_port = htons(10096);
	serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197");
		
	while(1){
		
		cli_msg.temp=43;
		cli_msg.humidity = 40;
		cli_msg.wind = 3;
		strcpy(cli_msg.des,"sun day");
		cli_msg.sendcnt++;
		
		ret = sendto( socket_cli,&cli_msg,sizeof(cli_msg),0,(struct sockaddr *)&serv_addr, sizeof(serv_addr) );
		if(ret<0){
			perror("sendto err");
			return -34;
		}
	
		socklen_t addrlen = sizeof(serv_addr);
		memset(&cli_msg,0,sizeof(cli_msg));
		ret = recvfrom(socket_cli,&cli_msg,sizeof(cli_msg),0, (struct sockaddr *)&serv_addr, &addrlen);
		if(ret<0){
			perror("recvfrom err");
			return -34;
		}
		
		printf("cli got serv: response=%d\n", cli_msg.serv_response );
		sleep(1);
	}
	
	close(socket_cli);
}

总结

这里也讲到了tcp和udp的区别(12条消息) 第三十四篇,网络编程UDP通信过程实现和函数接口_肖爱Kun的博客-CSDN博客_udp通信过程icon-default.png?t=M85Bhttps://blog.csdn.net/weixin_44651073/article/details/124225375?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166269910816782427467151%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166269910816782427467151&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-124225375-null-null.142^v47^control_1,201^v3^control_2&utm_term=udp%E9%80%9A%E4%BF%A1%E6%B5%81%E7%A8%8B&spm=1018.2226.3001.4187如果有什么错误的地方,欢迎在评论区留言

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:52:32  更:2022-09-13 11:52:36 
 
开发: 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年11日历 -2024/11/25 22:56:21-

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