如何区分不同应用进程之间的网络通信和链接?(一)

为了区分不同应用进程之间的网络通信和链接,主要有三个参数:通信的目的IP地址、使用的传输层协议(TCP/UDP)和使用的端口号。

Socket 愿意成为一个套接字。通过将这三个参数结合起来,绑定到一个socket socket,应用层就可以通过socket接口与传输层进行通信,区分不同应用或者网络连接点的通信,实现数据传输。并发服务。

accept() 生成的 Socket 端口号是多少?

写一个网络程序,必须使用Socket,这是所有程序员都知道的,而且在面试的时候,我们也会互相询问对方是否懂socket编程。很多人会说,Socket 编程基本上就是听、接受、发送和写。几个基本的操作,没错,就像常见的文件操作一样,只要你写过,就一定知道。

对于网络编程,我们必须说 TCP。似乎其他网络协议不再存在。对于 TCP,我们也知道 UDP。前者可以保证数据的正确性和可靠性,后者允许数据丢失。最后我们也知道,在建立连接之前,必须知道对方的IP地址和端口号。另外,普通程序员也不会知道太多。在很多情况下,这些知识就足够了。最多编写服务程序时,使用多线程处理。并发访问。

我们还知道以下事实

1.一个指定的端口号不能被多个应用程序共享。比如IIS占用了80端口,那么APACHE就不能使用这个端口。

2.许多防火墙只允许数据包通过特定的目标端口。

3.在监听到一个端口并接受一个连接请求后,服务程序会生成一个新的socket来处理这个请求。

于是,一个困扰我很久的问题出现了。如果创建了一个socket并绑定到80端口,是不是表示这个socket占用了80端口呢?

如果是这样,新套接字在接受请求时使用什么端口?(我一直以为系统会默认给它分配一个空闲端口号)

如果是空闲端口号,那么一定不是80端口,所以以后TCP包的端口一定不能是80——防火墙会阻止他通过。

其实我们可以看到防火墙并没有阻塞这样的连接,而这是最常见的连接请求和处理方式,我不明白的是防火墙为什么不阻塞这样的连接,他是如何判断连接的是因为connect80端口产生的吗?TCP数据包中是否有一些特殊的标志,或者防火墙已经记住的东西?

后来仔细阅读了TCP/IP协议栈的原理,对很多概念有了更深的理解。比如TCP和UDP属于传输层,共同假设在IP层之上应用编程接口和套接字,IP层主要负责节点之间的通信。数据包传输,这里的节点是网络设备,比如电脑,因为IP只负责向节点发送数据,无法区分上面的不同应用,所以TCP和UDP在协议的基础上增加了端口. 信息,端口是标识一个节点上的应用,处理添加端口信息,UDP协议基本不对IP层的数据做任何处理,TCP协议也增加了更复杂的传输控制,比如作为滑动数据传输。Window,以及接收确认​​和重传机制,实现可靠的数据传输,无论应用层看到什么稳定的TCP数据流,后面传输的都是IP数据包,需要通过TCP协议传输。执行数据重组。

所以我有理由怀疑防火墙没有足够的信息来判断TCP数据包的更多信息,除了IP地址和端口号,我们也看到所谓的端口是为了区分不同的应用到不同的IP 地址。当数据包到达时,它可以被正确转发。

TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须具体实现,同时它还对外提供了操作接口,就像操作系统会提供标准的编程接口一样,比如Win32的编程接口,TCP、IP也要对外提供一个编程接口,也就是Socket编程接口——就是这么回事

在Socket编程接口中,设计者提出了一个很重要的概念应用编程接口和套接字,那就是socket。这个套接字与文件句柄非常相似。事实上,在BSD系统中,它与文件句柄存储在同一个进程句柄中。这个socket其实是一个序号,表示它在句柄表中的位置。这个我们见多了,比如文件句柄、窗口句柄等。这些句柄其实代表了系统中的一些特定对象,用来在各种函数中作为参数传入,对特定对象进行操作——这其实是个问题用C语言。在C++语言中,this句柄其实就是this指针,其实就是对象指针。

现在我们知道套接字不一定与 TCP/IP 相关。设计 Socket 编程接口时,希望它也能适应其他网络协议。所以socket的出现只是为了让TCP/IP协议栈的使用更加方便。它抽象了 TCP/IP 并形成了几个基本的功能接口。比如创建、监听、接受、连接、读写等。

现在我们明白了,如果一个程序创建一个套接字并让它监听端口 80,它实际上向 TCP/IP 堆栈声明了它拥有端口 80。以后所有发往80端口的TCP包都会被转发给程序(这里的程序,因为使用的是Socket编程接口,所以先由Socekt层处理)。所谓accept函数,其实就是抽象了TCP连接建立过程。accept函数返回的新socket其实是指本次创建的连接,一个连接包含两部分信息,一个是源IP和源端口,一个是接收IP和接收端口。在这种情况下,这些socket sink端口都可以是80!同时防火墙对IP包的处理规则也一目了然,

很重要的一点要明白,sockets只是TCP/IP协议栈操作的抽象,并不是简单的映射关系!

昨天我和朋友们聊起了网络编程。关于Socket,以下是我个人的一些理解:)

Sockets可以在程序中创建,分为普通Sockets和原始Sockets两种。

一:Common Socket是TCP/IP协议栈中传输层操作的一种编程接口(一种API)。

有面向连接的流式套接字(SOCK_STREAM),属于TCP模式的应用;

没有连接包socket(SOCK_DGRAM),属于UDP的应用。

对于普通的 Sockets,我曾经有一个模糊的问题。在多线程的情况下,服务器监听某个端口(假设是8080),每一个接受来自客户端的连接都会生成一个新的Socket)。.那么这些新生成的Socket的端口是什么?程序中一定不能指定,所以应该有两种可能: 1:生成随机端口。2:还是8080端口。第一个假设想了想是不可能的,防火墙很有可能这些随机的端口包会被屏蔽掉。那么就是第二个假设,服务器端口还是8080。但是这颠覆了我原来的理解,就是“一个端口被一个程序占用了,其他程序不能使用这个端口。我觉得很可能是范围不同:即程序之间不能使用同一个端口,但是程序中的不同套接字仍然可以使用相同的端口。因此,为了使“客户端发送服务到同一端口的数据包(8080)终端的不同线程(即不同的Socket连接)可以区分和组合”),必须有一个区别特征是数据包来自不同的连接,即在传输层头中源端口是Socket连接中客户端的端口。 layer header 将与生成的 Socket 不同,而 sink 端口相同(server.end)。不同的Socket连接)可以区分和组合”),必须有一个区别特征是数据包来自不同的连接,即在传输层头中源端口是Socket中客户端的端口联系。综上所述,在这种情况下,传输层头中的源端口(客户端)会与生成的 Socket 不同,而宿端口是相同的(服务器)。结尾)。不同的Socket连接)可以区分和组合”),必须有一个区别特征是数据包来自不同的连接,即在传输层头中源端口是Socket中客户端的端口联系。综上所述,在这种情况下,传输层头中的源端口(客户端)会与生成的 Socket 不同,而宿端口是相同的(服务器)。结尾)。而接收端口是相同的(服务器)。结尾)。而接收端口是相同的(服务器)。结尾)。

文章中已经回答了很多问题,特别是“端口用于区分不同的应用程序”。我也很困惑,多线程下每个线程建立连接的端口是否可以相同。另外,文中说每个accept连接都会得到一个新的socket。这种表达方式并不完美。其实不是新的socket,而是客户端的socket。这个socket里面的ip和port当然是客户端的ip和port。表示接受客户端后,服务器使用哪个端口。

一个进程监听多个端口

单个进程创建多个绑定到不同端口的套​​接字,包括 TCP 和 UDP

多个进程监听同一个端口

这可以通过 fork 创建子进程来实现,但在其他情况下则不行。

在多线程的情况下,服务器监听到一个端口后,每次接受客户端的连接都会产生一个新的Socket。

新生成的Socket的端口是什么?

答案是服务器端口或监听端口。

进程之间不能使用相同的端口,但进程内的不同套接字可以使用相同的端口。

如何区分客户端发送到服务器同一端口的不同套接字。

使用Client Socket端口来区分!

Socket 是 TCP/IP 协议的网络接口。Socket是对TCP/IP协议操作的抽象。

客户端connect函数是从调用到函数返回的过程,也就是三次握手,第三次握手成功返回

服务端三次握手后,内核调用accept函数。执行accept函数后,会生成一个新的socket连接到客户端。

Q:写TCP/SOCK_STREAM服务程序时SO_REUSEADDR是什么意思?

A:这个socket选项通知内核如果端口忙但是TCP状态是TIME_WAIT,这个端口可以被重用。如果端口繁忙,

当 TCP 状态处于其他状态时,在重用端口时,您仍然会收到一条错误消息,指示“地址已在使用中”。如果您的服务程序停止

如果您想立即重启,但新的套接字仍然使用相同的端口,那么 SO_REUSEADDR 选项非常有用。必须认识到,任何

意外的数据到达可能会导致服务程序响应混乱,但这只是一种可能,实际上可能性很小。

端口复用最常见的用途应该是防止之前绑定的端口在服务器重启或程序突然退出而系统没有释放端口时被释放。这种情况下,如果设置了端口复用,新启动的服务器进程可以直接绑定端口。如果没有设置端口复用,绑定会失败,说明ADDR已经在使用了——那你要等重试,麻烦!

那么如何让sockfd_one、sockfd_two两个socket能够成功绑定8000端口呢?这时候就需要使用端口复用了。端口多路复用允许应用程序将 n 个套接字绑定到一个端口而不会出错。

设置socket的SO_REUSEADDR选项实现端口复用:

int opt = 1;
// sockfd为需要端口复用的套接字
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));

SO_REUSEADDR 可以在以下四种情况下使用。(来自 Unix 网络编程,第 1 卷,UNPv1)

1、当有一个本地地址和端口相同的socket1处于TIME_WAIT状态,而你启动的程序的socket2要占用这个地址和端口时,你的程序会使用这个选项。

2、SO_REUSEADDR 允许在同一端口上启动同一服务器的多个实例(多个进程)。但是,每个实例绑定的IP地址不能相同。这种情况可以在多网卡或者IP Alias技术的机器上测试。

3、SO_REUSEADDR 允许单个进程将同一个端口绑定到多个套接字,但每个套接字绑定到不同的 ip 地址。这与 2 非常相似,不同之处请参见 UNPv1。

4、SO_REUSEADDR 允许重复绑定完全相同的地址和端口。但这仅适用于 UDP 的多播,不适用于 TCP。

需要注意的是,设置端口复用函数需要在绑定之前调用,并且只要绑定到同一个端口的所有socket都必须复用:

// sockfd_one, sockfd_two都要设置端口复用
// 在sockfd_one绑定bind之前,设置其端口复用
int opt = 1;
setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );
err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
 
// 在sockfd_two绑定bind之前,设置其端口复用
opt = 1;
setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt) );
err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));

端口多路复用允许应用程序将 n 个套接字绑定到一个端口而不会出错。同时n个socket正常发送信息,没有问题。但是,并不是所有这些套接字都可以读取信息,只有最后一个套接字才能正常接收数据。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
// 线程1的回调函数
void *recv_one(void *arg)
{
	printf("===========recv_one==============\n");
	int sockfd = (int )arg;
	while(1){
		int recv_len;
		char recv_buf[512] = "";
		struct sockaddr_in client_addr;
		char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
		socklen_t cliaddr_len = sizeof(client_addr);
		
		recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
		printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
		printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);
	
	}
 
	return NULL;
}
 
// 线程2的回调函数
void *recv_two(void *arg)
{
	printf("+++++++++recv_two++++++++++++++\n");
	int sockfd = (int )arg;
	while(1){
		int recv_len;
		char recv_buf[512] = "";
		struct sockaddr_in client_addr;
		char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
		socklen_t cliaddr_len = sizeof(client_addr);
		
		recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
		inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
		printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
		printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",recv_len,recv_buf);
	
	}
 
	return NULL;
}
 
int main(int argc, char *argv[])
{
	int err_log;
	
	/////////////////////////sockfd_one
	int sockfd_one;
	sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
	if(sockfd_one < 0)
	{
	perror("sockfd_one");
	exit(-1);
	}
 
	// 设置本地网络信息
	struct sockaddr_in my_addr;
	bzero(&my_addr, sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(8000);		// 端口为8000
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	// 在sockfd_one绑定bind之前,设置其端口复用
	int opt = 1;
	setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, 
					(const void *)&opt, sizeof(opt) );
 
	// 绑定,端口为8000
	err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
	if(err_log != 0)
	{
		perror("bind sockfd_one");
		close(sockfd_one);		
		exit(-1);
	}
	
	//接收信息线程1
	pthread_t tid_one;
	pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);
	
	/////////////////////////sockfd_two
	int sockfd_two;
	sockfd_two = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字two
	if(sockfd_two < 0)
	{
		perror("sockfd_two");
		exit(-1);
	}
 
	// 在sockfd_two绑定bind之前,设置其端口复用
	opt = 1;
	setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, 
					(const void *)&opt, sizeof(opt) );
	
	// 新套接字sockfd_two,继续绑定8000端口,成功
	err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
	if(err_log != 0)
	{
		perror("bind sockfd_two");
		close(sockfd_two);		
		exit(-1);
	}
	//接收信息线程2
	pthread_t tid_two;
	pthread_create(&tid_two, NULL, recv_two, (void *)sockfd_two);
	
	
	while(1){	// 让程序阻塞在这,不结束
		NULL;
	}
 
	close(sockfd_one);
	close(sockfd_two);
 
	return 0;
}

然后,通过网络调试助手向本服务器发送数据,结果显示只有最后一个socket sockfd_two会正常接收数据。

连接的唯一标识符是[server ip, server port, client ip, client port]。当操作系统从某个端口接收到数据时,会在该端口生成的连接中找到与该唯一标识匹配的连接,并将该信息传送到相应的缓冲区中。

所以一个socket可以对应多个tcp连接。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论