网络编程-Socket套接字(TCP、UDP、广播和组播通信)

socket套接字

socket是一个编程接口(网络编程接口) 作用是用来实现网络上不同的主机的应用进程之间进行双向通信

套接字是一种特殊的文件描述符 也就意味着我们使用套接字实现网络通信的时候可以用read/write
比如:客户端可以用write发送网络数据 服务器端可以用read去接收网络数据

要通过互联网进行通信 至少需要一对套接字 其中一个运行在客户端 称之为Client Socket
另一个运行在服务器端 称之为Server Socket

Socket可以分为三种类型:
1) 流套接字(SOCK_STREAM)

流套接字用于提供面向连接 可靠的数据传输服务
主要针对传输层协议为TCP协议的应用
如果等待我们所写的代码利用TCP协议来通信的话 那么我们就需要创建一个流套接字

2) 数据报套接字(SOCK_DGRAM)

数据报套接字提供一种无连接的服务(并不能保证数据传输的可靠性)
主要针对传输层协议为UDP协议的应用

3) 原始套接字(SOCK_RAW)

TCP UDP 特点

TCP特点:
  1、tcp是面向连接的,通信之前需要建立连接,通信结束之后还需释放连接**(三次握手,四次挥手)**
  2、tcp提供了很可靠的支付服务,可靠也就是说:tcp的数据没有重复、没有丢失、没有错误、并且和发送端的数据是一致的。
  3、tcp是面向字节流的。也就是tcp是以字节为单位,虽然传输的过程中数据被划分为了一个一个数据报文,但是这只是为了方便传输,接收端最终接受到的数据和发送端接收到的数据是一样的。
  4、tcp提供全双工通信:就是tcp的两端即可以作为发送端,也可以作为接收端。
  5、最重要的一点就是一个tcp的连接只能有两个端点,支持一对一通信。
  6、tcp首部含有20个字节。

UDP特点:
  1、首先udp是无连接的,通信结束也不需要释放连接。
  2、upd是一种不可靠的协议,发出去就不管了。
  3、udp是一种
面向报文的链接
,udp数据传输的单位是报文,而且不会对数据做任何的拆分和拼接操作。在发送端,应用程序给传输层的udp什么样的数据,udp不会对数据进行拆分,最会增加一个udp头并且交给网络层。在接收端,udp收到网络层的数据之后,除去ip(网络层协议)数据报头部后便交给应用层,不会做任何的拼接操作。
  4、udp是不存在拥塞控制的,并且始终就是用恒定的速率发送数据,并不会根据网络拥塞情况对发送速率做调整。这个状况下就会存在优势和弊端;弊端就是:网络拥塞时有些报文就会丢失,所以才说udp是不可靠的协议;他的优点就是有些使用场景允许报文丢失,比如:直播,语音通话,但是对实时性要求比较高。
  5、udp支持一对一,一对多,多对多,多对一通信
  6、udp首部的开销比较小,只有8个字节。相对于tcp来说,效率还是很高的。

TCP UDP 优缺点
TCP优点:可靠,稳定。tcp的可靠体现在tcp在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认,窗口,重传,拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

TCP缺点:速度慢,效率低,占用系统资源高,易被攻击。TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制,重传机制,拥塞机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU,内存等硬件资源。而且,因为TCP有确认机制,三次握手机制,这些也导致TCP容易被人利用,实现DOS,DDOS,CC等攻击。

UDP优点:速度快,比TCP稍安全。UDP没有TCP的握手,确认,窗口,重传,拥塞控制等机制。UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的。比如:UDP Flood攻击。

UDP缺点:不可靠,不稳定。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会容易丢包。

在这里插入图片描述
————————————————
版权声明:本文为CSDN博主「晗二狗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/skitan/article/details/107444486

TCP套接字编程流程:

TCP网络应用的数据传输的大概过程 
		
			建立连接  
				“三次握手”
			
			发送/接收网络数据 
			
				write/send/sendto 
				read/recv/recvfrom 

			关闭连接 
				“四次挥手”

为什么会有三次握手的机制?

通信双方成功通信的前提条件是双方都要能够建立连接。
那么双方都能连接到一起的前提条件是什么呢?
必须双方都能够收和发。

就比如x想跟y聊天 在聊天之前就需要测试x和y能否能聊天,如果不能聊天 则不建立连接。
比如:x是聋子(不能收)或则y是哑巴(不能发) 则连接就不能成功建立, 只有当x和y都不
是聋子和哑巴的时候 连接才能建立成功!

所以三次握手实际上就是一个测试能不能建立连接的过程。
第一次握手就是测试客户端能不能发
第二次握手就是测试服务器端能不能收和发
第三次握手就是测试客户端能不能收

“四次挥手”:

建立连接是非常重要的 他是数据正确传输的前提 断开连接 他让计算机释放不再使用的资源

四次挥手的具体过程: 
		
			建立连接后	客户端和服务器端都处于ESTABLISHED状态  客户端发起断开连接的请求
			
			1)客户端调用close函数 向服务器发送FIN数据包 进入FIN_WAIT_1状态  
			   FIN就是表示断开连接 FINISH 
			 
			2)服务器收到数据包后 检测到设置了FIN标志位 知道要断开连接。于是向客户端发送”确认包“
			   键入CLOSE_WAIT  
			   注意:服务器收到请求后并不是立即断开连接 而是先向服务器端发送”确认包“
					 告诉他我知道了 我需要准备一下才能断开连接 
			
			3)客户端收到”确认包“ 后进入FIN_WAIT_2状态 等待服务器准备完毕之后再发一个数据包过来 
			
			4)等待片刻之后 服务器准备完毕了 可以断开连接 于是再主动地向客户端发送FIN 告诉它我准备
			   好了 断开连接把  然后进入LAST_ACK状态 
			   
			5)客户端收到服务器地FIN包后 再向服务器发送ACK包 告诉他你断开连接把 然后进入TIME_WAIT状态 
				
			6)服务器收到客户端的ACK包后 就断开连接 关闭套接字 进入CLOSED状态

利用Socket套接字实现TCP通信和UDP通信的函数流程

TCP Server: 
		
			socket : 绑定一个套接字 
			
			bind : 把一个套接字和一个网络地址绑定在一起  
				   如果你想让其他人来主动连接或联系你 你就需要bind一个地址 并把这个地址告诉别人 
					不调用bind 并不代表你得socket没有地址, 相反 调不调用bind socket在通信的时候 内核都会
					动态地为你的socket指定一个地址
				   
			listen: 让套接字进入一个”监听模式“
			
			accpet: 接收客户端的连接 
					多次调用accept就可以与不同的客户端建立连接

			write/send/sendto  or  read/recv/recvfrom 
			
			close/shutdown : ”四路挥手“

代码实现:

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

int flag=0;
void sys_erro(char * str)
{
    
    
	perror(str);
	

}
int main(int argc,char * argv[])
{
    
    
	if(argc!=3)
	{
    
    
		
		return -1;
	}

	//创建套接字
	int sock_fd=socket(AF_INET,SOCK_STREAM,0); //ipV4
	if(-1==sock_fd)
		sys_erro("socket error\n");


	//指定服务器
	struct sockaddr_in serv;
	serv.sin_family=AF_INET;
	serv.sin_port=htons(atoi(argv[2])); //端口号 转成16bits
	serv.sin_addr.s_addr=inet_addr(argv[1]); //ip
	int ret=bind(sock_fd,(struct sockaddr*)&serv,sizeof(serv));//绑定网络地址
	if(-1 == ret)
	{
    
    
		close(sock_fd);
		sys_erro("bind error\n");
	}


	//监听套接字
	int listen_ret=listen(sock_fd,10);
	if(-1 == listen_ret)
	{
    
    
		close(sock_fd);
		sys_erro("listen error\n");
	}
	
	//阻塞等待连接
	struct sockaddr_in Client;
	socklen_t len=sizeof(Client);
	
	while (1)
	{
    
    
		int accept_ret=accept(sock_fd,(struct sockaddr*)&Client, &len);
		if(-1 == accept_ret)
		{
    
    
			sys_erro("accept error\n");
		}
		printf("connet [%s] [port:%d]\n",inet_ntoa(Client.sin_addr),ntohs(Client.sin_port));
		pid_t pid=fork();
		
		if(pid==0)
		{
    
    
			while (1)
			{
    
    
				char buf1[1024]={
    
    0}; //写
				char buf2[1024]={
    
    0}; //读
				int Rret;

				//接收
				Rret=recv(accept_ret,buf2,sizeof(buf2),0);
				if(Rret==-1)
				{
    
    
					sys_erro("recv error\n");

				}
				else if(Rret>0)
				{
    
    
					if(strncmp(buf2,"SeeYou",6)==0)
					{
    
    
						close(accept_ret);
						flag=1;
						break;
					}
					printf("服务器接收:%s\n",buf2);
				}

				
				//发送
				fgets(buf1,1023,stdin); //键盘输入
				send(accept_ret,buf1,strlen(buf1),0);
				
				
			}
		}
		else if(pid>0)
		{
    
    
			close(accept_ret);
		}
		else
		{
    
    
			sys_erro("fork erro\n");

		}
		if(flag==1)
		{
    
    
			break;
		}
	
	}
	close(sock_fd);
	printf("服务器关闭\n");
}

TCP Client: 
		
			socket : 绑定一个套接字 
			
			bind  : 可要可不要 
			
			connect : 主动与TCP Server建立连接  ”三次握手“
			
			write/send/sendto  or  read/recv/recvfrom 
			
			close/shutdown 

代码实现:

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


int sys_erro(char * str)
{
    
    
	perror(str);
	return -1;

}
int main(int argc,char * argv[])
{
    
    
	if(argc!=3)
	{
    
    
		
		return -1;
	}

	//创建套接字
	int sock_fd=socket(AF_INET,SOCK_STREAM,0);
	if(-1==sock_fd)
		sys_erro("socket error\n");


	//指定服务器
	struct sockaddr_in serv;
	serv.sin_family=AF_INET;
	serv.sin_port=htons(atoi(argv[2])); //端口号 转成16bits
	serv.sin_addr.s_addr=inet_addr(argv[1]); //ip
	


	//请求连接
	int connect_ret=connect(sock_fd,(struct sockaddr*)&serv,sizeof(serv));
	if(connect_ret==-1)
	{
    
    
		close(sock_fd);
		sys_erro("connect error\n");
	}
	
	
	
	while (1)
	{
    
    
		int Rret;
		
		char buf1[1024]={
    
    0}; //写
		char buf2[1024]={
    
    0}; //读

		//键盘写
		fgets(buf1,1023,stdin);
		write(sock_fd,buf1,strlen(buf1));
		
		if(strncmp(buf1,"SeeYou",4)==0)
		{
    
    
			break;
		}

		//读 
		Rret=recv(sock_fd,buf2,sizeof(buf2),0);
		printf("%s\n",buf2);
	}
	close(sock_fd);
	
}


UDP Server: 
			socket : 绑定一个套接字 
			
			bind : 把一个套接字和一个网络地址绑定在一起  
				   如果你想让其他人来主动连接或联系你 你就需要bind一个地址 并把这个地址告诉别人 
			write/send/sendto  or  read/recv/recvfrom 
			
			close/shutdown 
UDP Client: 
			socket : 绑定一个套接字 
			
			write/send/sendto  or  read/recv/recvfrom 
			
			close/shutdown 

套接字选项

每个套接字再不同的协议层次(级别level)上有不同的行为属性(选项) 有两个函数用于获取/设置套接字的选项

	getsockopt : 获取套接字的选项 
	
	setsockopt : 设置套接字的选项 
#include <sys/types.h>          /* See NOTES */
			   #include <sys/socket.h>

			    int getsockopt(int sockfd, int level, int optname,
							  void *optval, socklen_t *optlen);
							  
				函数参数: 
						sockfd: 你要设置哪个套接字的选项 
						
						level: 你要获取的套接字的选项是属于哪个level ---> “查表”
						
						optname:你要获取的那个套接字选项的“名字”宏 ---> “查表”
						
						optval:指向一段空间 这段空间用来保存获取到的选项的值的 
							    因为不同的选项 值的类型不同 
								如: SO_RCVBUF			-> int * 
									SO_SNDTIMEO         -> struct timeval * 
									...
						optlen: 指向一段空间 这段空间保存获取到的选项值的数据长度的 
						
				返回值: 
						成功返回0 
						失败返回-1 并且errno被设置

						
			    int setsockopt(int sockfd, int level, int optname,
							  const void *optval, socklen_t optlen);

				函数参数: 
							前面三个跟上面函数类型 指定你要设置哪个套接字的具体哪个选项 
							
							optval:指向一段空间 这段空间里面保存了你要设置的值
			
							optlen:你要设置的选项值所占的空间的长度 
							
				返回值: 
							成功返回0 
							失败返回-1 并且errno被设置

广播和组播(多播)

1.广播 broadcast

广播就是向局域网内所有的人说话 但是广播还是要指定接收者的端口号的

a.只有传输层协议是UDP协议的时候 才支持广播

因为TCP是端对端 广播是一对多
b.广播地址
广播地址是专门用于同时向网络中所有工作站进行发送的一个地址
子网内广播地址:主机号全为1的IP地址就是广播地址

2.多播(组播)multicast
单播用于两个主机之间的端对端的通信 广播用于一个主机对整个局域网上所有的主机进行通信
单播和广播是两个极端
有时候 我们需要对一组特定的主机进行通信 --> 多播
也就是说 处于同一组的主机就能收到数据 不在同一组的主机就收不到数据 —》类似于QQ群

注意:
a.多播也只有传输层协议为UDP时 才支持多播(组播)功能
b.多播地址是IPV4的D类地址

D 1110多播组号(28bit) 224.0.0.0 - 239.255.255.255

多播的编程思路:

多播同样分为服务器(多播发送者)和客户端(多播接收者)

服务器(多播发送者):

					1.创建一个套接字 --》 UDP的套接字  
					
					2.通过sendto发送信息到一个多播组地址中  
						
						多播组地址: D类IP地址 + PORT
		
					3.关闭套接字 
					
					注意:服务器(多播发送者)不需要加入多播组 就可以直接向某个多播组发送数据 
						  而如果你想要接收数据  那么就必须要加入多播组

代码实现:

客户端(多播接收者)

				
					1.创建一个套接字--》UDP的套接字 
					
					2.加入一个多播组   
					
					3.绑定地址 
					
						绑定多播组的地址 --》 D类IP + PORT
		
					4.接收多播组的信息 
					
					5.也可以发送信息到多播组 
					
					6.关闭套接字
		

猜你喜欢

转载自blog.csdn.net/weixin_46836491/article/details/127022257