Linux 进程间套接字通信(Socket)基础知识

Linux 进程间套接字通信(Socket)基础知识,第1张

姓名:罗学元    学号:21181214375    学院:广州研究院

【嵌牛导读】Linux进程套接字通信基础

【嵌牛鼻子】Linux 进程间套接字及通信介绍

【嵌牛提问】Linux进程间套接字包含哪些内容,如何实现通信

一、套接字(Socket)通信原理

套接字通信允许互联的位于不同计算机上的进程之间实现通信功能。

二、套接字的属性

套接字的特性由3个属性确定,它们分别是:域、类型和协议。

1. 套接字的域

它指定套接字通信中使用的网络介质,最常见的套接字域是AF_INET,它指的是Internet网络。当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用socket作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。

另一个域AF_UNIX表示UNIX文件系统,就是文件输入/输出,它的地址就是文件名。

2. 套接字类型

因特网提供了两种通信机制:流(stream)和数据报(datagram),因而套接字的类型也就分为流套接字和数据报套接字。我们主要看流套接字。

流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。

流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。

与流套接字相对的是由类型SOCK_DGRAM指定的数据报套接字,它不需要建立连接和维持一个连接,它们在AF_INET中通常是通过UDP/IP实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并不需要总是要建立和维持一个连接。

3.套接字协议

只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。通常只需要使用默认值。

三、套接字地址

每个套接字都有其自己的地址格式,对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件

struct sockaddr_un{

sa_family_t sun_family  //AF_UNIX,它是一个短整型

char sum_path[]  //路径名

}

对于AF_INET域套接字来说,它的地址结构由sockaddr_in来描述,它至少包括以下几个成员:

struct sockaddr_in{

short int sin_family  //AN_INET

unsigned short int sin_port  //端口号

struct in_addr sin_addr    //IP地址

}

而in_addr被定义为:

struct in_addr{

unsigned long int s_addr

}

四、基于流套接字的客户/服务器的工作流程

使用socket进行进程通信的进程采用的客户/服务器系统是如何工作的呢?

1.服务器端

首先,服务器应用程序用系统调用socket来创建一个套接字,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他的进程共享。

接下来,服务器进程会给套接字起个名字,我们使用系统调用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个套接字。

然后,系统卖颂调用listen来创建一个队列,并将其用于存放来自客户的进入连接。

最后,服务器通过系统调用accept来接受客户的连接。它会创建一个与原有的命名套接不拍让同的新套接字,这个套接字只用于与这个特定客户端进行通信,而命名套接字(即原先的套接字)则被保留袭配局下来继续处理来自其他客户的连接。

2.客户端

基于socket的客户端比服务器端简单。同样,客户应用程序首先调用socket来创建一个未命名的套接字,然后讲服务器的命名套接字作为一个地址来调用connect与服务器建立连接。

一旦连接建立,我们就可以像使用底层的文件描述符那样用套接字来实现双向数据的通信。

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

Socket的愿意是插座,通过将这三个参数结合起来,与一个插座socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序或网络连接点额通信,实现数据传输的并发服务。

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

要写网络程序就必须用Socket,这是程序员都知道的,而且在面试的时候,我们也会问对方会不会socket编程,很多人都会说Socket编程基本就是listen、accept、以及send 和write几个基本 *** 作,是的就跟常见的文件 *** 作一样,只要写过就一定知道。

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

我们还知道以下几个事实

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

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

3.服务程序在listen某个端口并accept某个连接请求之后,会生成一个新的socket来对请求进行处理,

于是一个困惑我很久的问题产生了,如果一个socket创建后并与80端口绑定后,是否就意味着该socket占用薯清闷了80端口呢?

如果是这样的,那么当其accept一个请求之后,生成的新的socket到底使用的是什么端口呢?(我一直以为系统会默认给其分配一个空闲的端口号)

如果是一个空闲的端口号,那么一定不是80端口了,于是以后的TCP数据包端口肯定就不是80 了--防火墙会阻止他通过的。

实际上我们可以看到,防火墙并没有阻止这样的连接,而且这是最常见的连接 请求和处理方式,我不理解的就是为什么防火墙没有阻止这样的连接,他是如何判断那条连接是因为connect80端口生成的?是不是TCP数据包里面有什么特别的标志,或者防火墙已经记住了什么东西?

后来我又仔细读了TCP/IP协议栈原理,对很多概念有了更深刻的认识,比如TCP和UDP同属于传输层,共同假设在IP层之上,而IP层主要负责在节点之间的数据包传输,这里的节点是一台网络设备,比如计算机,因为IP曾只负责把数据送到节点之上,而不能区分上面的不同应用,所以TCP、UDP在协议的基础上加入了端口的信息,端口是标识的是一个节点上的应用,处理增加端口信息,UDP协议基本就没有对IP层的数据进行任何处理了,而TCP协议还加入了更复杂的传输控制,比如滑动的数据发送窗口,以及接受确认和重发机制,以达到数据的可靠传输,不管应用层看到的是怎么样一个稳定的TCP数据流,下面传送的都是一个个的IP数据包,需要由TCP协议来进行数据重组。

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

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

在Socket编程接口里,设计者提出了一个很重要的概念,那就是socket。这个数弯socket跟文件句柄很相似,实际上,在BSD系统里就是跟文件句柄一样存放在一样的 进程句柄 里。 这个 socket其实是一个序号,表示其正冲在句柄表中的位置。这一点,我们已经见过很多了,比如文件句柄,窗口句柄等。 这些句柄,其实是代表了系统中的某些特定的对象,用于在各种函数中作为参数传入,以对特定对象进行 *** 作 ——这其实是C语言的问题,在C++语言里,这个句柄其实就是this指针,实际就是对象指针啦。

现在我们知道,socket跟TCP/IP并没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以,socket的出现只是可以更方便的使用TCP/IP协议栈而已,其对TCP/IP进行了抽象,形成了几个最基本的函数接口。比如create, listen, accept, connect, read和write等。

明白socket只是对TCP/IP协议栈 *** 作的抽象,而不是简单的映射关系,这很重要!

昨天和朋友聊了下网络编程,关于Socket,这里写一下我个人的一些理解:)

程序里可以创建Socket,分为普通Socket和原始Socket两种类型。

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

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

有无连接数据包式套接字(SOCK_DGRAM),属于针对UDP方式的应用。

对于普通Socket,我曾经有个模糊的问题,在 多线程情况 下,服务器端监听(listen)某个端口(假设8080)后,每accept一个客户端的连接就会产生一个新的Socket。那么这些新产生的Socket的端口是什么?程序里肯定没有指定,那就应该有两种可能,1:产生随机端口。2:还是8080端口。第一种假设想了就觉得不可能,防火墙非常有可能会阻止这些随机端口的包。那么就是第二种假设了,服务端端口还是8080。但这推翻了我原有的认识,就是“一个端口被程序占有,其他程序就不能用该端口了”。我觉得其实最有可能的是范围不同:就是在程序与程序间不能用同一端口,但是在程序内部不同的Socket还是可以用同一端口的。所以,为了能够使“客户端发给服务端的同一端口(8080)不同线程(即不同的Socket连接)的包能够被区分开并进行组合”,必须得有一个区分包是来自不同连接的显著特征,那就是传输层包头里的源端口了,即一个Socket连接里客户端那方的端口。总结一下,对于这种情况,就是传输层包头里源端口(客户端)会随着产生的Socket不同,而宿端口相同(服务器端)。

很多疑问都在文章里里找到了答案,特别是“端口是用来区分不同应用程序的”,之前也困惑多线程下,每个线程建立连接的端口是否可以相同。 另外文中说每accept一个连接会得到一个新的socket,这个表述不是很完美,其实不是一个新的socket而是得到了客户端的socket,这个socket里的ip和端口当然是客户端的ip和端口了。表达accept客户端之后,服务端用的是哪个端口。

单个进程监听多个端口

单个进程创建多个 socket 绑定不同的端口,TCP, UDP 都行

多个进程监听同一个端口(multiple processes listen on same port)

通过 fork 创建子进程的方式可以实现,其他情况下不行。

多线程情况下,服务器端监听(listen)某个端口后,每accept一个客户端的连接就会产生一个新的Socket

新产生的Socket的端口是多少?

答案是服务器端口还是Listen端口。

进程间不能用同一端口,但是进程内部不同Socket可以用同一个端口。

Client端发送给Server同一端口的不同Socket怎么区分。

用Client端Socket端口区分!

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

客户端connect函数是开始调用到函数返回正好是三次握手的过程,第三次握手成功则返回

server端三次握手之后内核调用accept函数,accept函数执行后会产生一个新的socket与client端进行连接。

Q:编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?

A:这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,

而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止

后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR 选项非常有用。必须意识到,此时任何

非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。

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

那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。

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

SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)

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

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

3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。

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

需要注意的是,设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用:

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

接着,通过网络调试助手给这个服务器发送数据,结果显示,只有最后一个套接字sockfd_two会正常接收数据。

一个连接的唯一标识是[server ip, server port, client ip, client port]也就是说。 *** 作系统,接收到一个端口发来的数据时,会在该端口,产生的连接中,查找到符合这个唯一标识的并传递信息到对应缓冲区。

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

用socket实现进程通信 ,和socket的普通用法一样,只不过服务端IP为127.0.0.1 而已

下面附上代码示例:

//服务器端代码 进程1

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>

int main(int argc, char *argv[])

{

int sock

//sendto中使用的对方地址

struct sockaddr_in toAddr

//在recvfrom中使用的对方主机地址

struct sockaddr_in fromAddr

int recvLen

unsigned int addrLen

char recvBuffer[128]

sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)

if(sock < 0)

{

 printf("创建套接字失败了.\r\n")

 exit(0)

}

memset(&fromAddr,0,sizeof(fromAddr))

fromAddr.sin_family=AF_INET

fromAddr.sin_addr.s_addr=htonl(INADDR_ANY)

fromAddr.sin_port = htons(4000)

if(bind(sock,(struct sockaddr*)&fromAddr,sizeof(fromAddr))<0)

{

 printf("bind() 函数使用失败了.\r\n")

 close(sock)

 exit(1)

}

while(1){

addrLen = sizeof(toAddr)

if((recvLen = recvfrom(sock,recvBuffer,128,0,(struct sockaddr*)&toAddr,&addrLen))<0)

{

 printf("()recvfrom()函数使用失败了.\r\n")

 close(sock)

 exit(1)

}

if(sendto(sock,recvBuffer,recvLen,0,(struct 盯枯sockaddr*)&toAddr,sizeof(toAddr))!=recvLen){

printf("sendto fail\r\n")

close(sock)

exit(0)

}

return 0

}

}  //客户端代码 进程2

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <unistd.h>棚桥int main(int argc, char *argv[])

{

if(argc < 2)

{

 printf("请输入要传送的内容.\r\n")

 exit(0)

}

int sock

//sendto中使用的对方地址

struct sockaddr_in toAddr

//在recvfrom中使用的凯和洞对方主机地址

struct sockaddr_in fromAddrunsigned int fromLen

char recvBuffer[128]

sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)if(sock < 0)

{

 printf("创建套接字失败了.\r\n")

 exit(1)

}memset(&toAddr,0,sizeof(toAddr))

toAddr.sin_family=AF_INET

toAddr.sin_addr.s_addr=inet_addr("127.0.0.1")

toAddr.sin_port = htons(4000)if(sendto(sock,argv[1],strlen(argv[1]),0,(struct sockaddr*)&toAddr,sizeof(toAddr)) != strlen(argv[1]))

{

 printf("sendto() 函数使用失败了.\r\n")

 close(sock)

 exit(1)

}fromLen = sizeof(fromAddr)

if(recvfrom(sock,recvBuffer,128,0,(struct sockaddr*)&fromAddr,&fromLen)<0)

{

 printf("()recvfrom()函数使用失败了.\r\n")

 close(sock)

 exit(1)

}

printf("recvfrom() result:%s\r\n",recvBuffer)

close(sock)}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/8218945.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-14
下一篇 2023-04-14

发表评论

登录后才能评论

评论列表(0条)

保存