Linux进程间通信方式之socket使用实例
套接字是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。
套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocolfamily)的不同而不同。每个协议族又可以使用一个或多个地址族定义地址格式。
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即IP地址。
在计算机系统内部,端口通过分配一个唯一的16位的整数来表示,在系统外部,则需要通过IP地址和端口号的组合来确定。
2.套接字类型
流套接字(在某些方面类似域标准的输入/输出流)提供的是一个有序,可靠,双向字节流的连接。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。他们也是AF_UNIX域中常见的套接字类型。
数据包套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据包套接字不建立和维持一个连接。它对可以发送的数据包的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失,复制或乱序到达。
数据报套接字实在AF_INET域中通过UDP/IP连接实现,它提供的是一种无需的不可靠服务。
3.套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,我们就可以为套接字选择一个特定的协议。
先上一个代码
服务端:
//s_unix.c #include#include #include #include #defineUNIX_DOMAIN"/tmp/UNIX.domain" intmain(void) { socklen_tclt_addr_len; intlisten_fd; intcom_fd; intret; inti; staticcharrecv_buf[1024]; intlen; structsockaddr_unclt_addr; structsockaddr_unsrv_addr; listen_fd=socket(PF_UNIX,SOCK_STREAM,0); if(listen_fd<0) { perror("cannotcreatecommunicationsocket"); return1; } //setserveraddr_param srv_addr.sun_family=AF_UNIX; strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1); unlink(UNIX_DOMAIN); //bindsockfd&addr ret=bind(listen_fd,(structsockaddr*)&srv_addr,sizeof(srv_addr)); if(ret==-1) { perror("cannotbindserversocket"); close(listen_fd); unlink(UNIX_DOMAIN); return1; } //listensockfd ret=listen(listen_fd,1); if(ret==-1) { perror("cannotlistentheclientconnectrequest"); close(listen_fd); unlink(UNIX_DOMAIN); return1; } //haveconnectrequestuseaccept len=sizeof(clt_addr); com_fd=accept(listen_fd,(structsockaddr*)&clt_addr,&len); if(com_fd<0) { perror("cannotacceptclientconnectrequest"); close(listen_fd); unlink(UNIX_DOMAIN); return1; } //readandprintfsentclientinfo printf("/n=====info=====/n"); for(i=0;i<4;i++) { memset(recv_buf,0,1024); intnum=read(com_fd,recv_buf,sizeof(recv_buf)); printf("Messagefromclient(%d)):%s/n",num,recv_buf); } close(com_fd); close(listen_fd); unlink(UNIX_DOMAIN); return0; }
客户端:
//c_unix.c #include#include #include #include #defineUNIX_DOMAIN"/tmp/UNIX.domain" intmain(void) { intconnect_fd; intret; charsnd_buf[1024]; inti; staticstructsockaddr_unsrv_addr; //creatunixsocket connect_fd=socket(PF_UNIX,SOCK_STREAM,0); if(connect_fd<0) { perror("cannotcreatecommunicationsocket"); return1; } srv_addr.sun_family=AF_UNIX; strcpy(srv_addr.sun_path,UNIX_DOMAIN); //connectserver ret=connect(connect_fd,(structsockaddr*)&srv_addr,sizeof(srv_addr)); if(ret==-1) { perror("cannotconnecttotheserver"); close(connect_fd); return1; } memset(snd_buf,0,1024); strcpy(snd_buf,"messagefromclient"); //sendinfoserver for(i=0;i<4;i++) write(connect_fd,snd_buf,sizeof(snd_buf)); close(connect_fd); return0; }
使用套接字除了可以实现网络间不同主机间的通信外,还可以实现同一主机的不同进程间的通信,且建立的通信是双向的通信。socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。
一、创建socket流程
(1)创建socket,类型为AF_LOCAL或AF_UNIX,表示用于进程通信:
创建套接字需要使用socket系统调用,其原型如下:
intsocket(intdomain,inttype,intprotocol);
其中,domain参数指定协议族,对于本地套接字来说,其值须被置为AF_UNIX枚举值;type参数指定套接字类型,protocol参数指定具体协议;type参数可被设置为SOCK_STREAM(流式套接字)或SOCK_DGRAM(数据报式套接字),protocol字段应被设置为0;其返回值为生成的套接字描述符。
对于本地套接字来说,流式套接字(SOCK_STREAM)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;数据报式套接字(SOCK_DGRAM)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。
二、命名socket。
SOCK_STREAM式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用structsockaddr_un类型的变量。
structsockaddr_un{ sa_family_tsun_family;/*AF_UNIX*/ charsun_path[UNIX_PATH_MAX];/*路径名*/ };
这里面有一个很关键的东西,socket进程通信命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。
另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0]=0,下面用代码说明:
第一种方式:
//nametheserversocket server_addr.sun_family=AF_UNIX; strcpy(server_addr.sun_path,"/tmp/UNIX.domain"); server_len=sizeof(structsockaddr_un); client_len=server_len;
第二种方式:
#defineSERVER_NAME@socket_server
//namethesocket server_addr.sun_family=AF_UNIX; strcpy(server_addr.sun_path,SERVER_NAME); server_addr.sun_path[0]=0; //server_len=sizeof(server_addr); server_len=strlen(SERVER_NAME)+offsetof(structsockaddr_un,sun_path);
其中,offsetof函数在#include
#defineSERVER_NAME@socket_server
前面的@符号就表示占位符,不算为实际名称。
提示:客户端连接服务器的时候,必须与服务端的命名方式相同,即如果服务端是普通命名方式,客户端的地址也必须是普通命名方式;如果服务端是抽象命名方式,客户端的地址也必须是抽象命名方式。
三、绑定
SOCK_STREAM式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用structsockaddr_un类型的变量,将相应字段赋值,再将其绑定在创建的服务器套接字上,绑定要使用bind系统调用,其原形如下:
intbind(intsocket,conststructsockaddr*address,size_taddress_len);
其中socket表示服务器端的套接字描述符,address表示需要绑定的本地地址,是一个structsockaddr_un类型的变量,address_len表示该本地地址的字节长度。实现服务器端地址指定功能的代码如下(假设服务器端已经通过上文所述的socket系统调用创建了套接字,server_sockfd为其套接字描述符):
structsockaddr_unserver_address; server_address.sun_family=AF_UNIX; strcpy(server_address.sun_path,"ServerSocket"); bind(server_sockfd,(structsockaddr*)&server_address,sizeof(server_address));
客户端的本地地址不用显式指定,只需能连接到服务器端即可,因此,客户端的structsockaddr_un类型变量需要根据服务器的设置情况来设置,代码如下(假设客户端已经通过上文所述的socket系统调用创建了套接字,client_sockfd为其套接字描述符):
structsockaddr_unclient_address; client_address.sun_family=AF_UNIX; strcpy(client_address.sun_path,"ServerSocket");
四、监听
服务器端套接字创建完毕并赋予本地地址值(名称,本例中为ServerSocket)后,需要进行监听,等待客户端连接并处理请求,监听使用listen系统调用,接受客户端连接使用accept系统调用,它们的原形如下:
intlisten(intsocket,intbacklog); intaccept(intsocket,structsockaddr*address,size_t*address_len);
其中socket表示服务器端的套接字描述符;backlog表示排队连接队列的长度(若有多个客户端同时连接,则需要进行排队);address表示当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于自身的信息;address_len表示当前连接客户端本地地址的字节长度,这个参数既是输入参数,又是输出参数。实现监听、接受和处理的代码如下:
#defineMAX_CONNECTION_NUMBER10 intserver_client_length,server_client_sockfd; structsockaddr_unserver_client_address; listen(server_sockfd,MAX_CONNECTION_NUMBER); while(1) { //......(someprocesscode) server_client_length=sizeof(server_client_address); server_client_sockfd=accept(server_sockfd,(structsockaddr*)&server_client_address,&server_client_length); //......(someprocesscode) }
这里使用死循环的原因是服务器是一个不断提供服务的实体,它需要不间断的进行监听、接受并处理连接,本例中,每个连接只能进行串行处理,即一个连接处理完后,才能进行后续连接的处理。如果想要多个连接并发处理,则需要创建线程,将每个连接交给相应的线程并发处理。
客户端套接字创建完毕并赋予本地地址值后,需要连接到服务器端进行通信,让服务器端为其提供处理服务。对于SOCK_STREAM类型的流式套接字,需要客户端与服务器之间进行连接方可使用。连接要使用connect系统调用,其原形为
intconnect(intsocket,conststructsockaddr*address,size_taddress_len);
其中socket为客户端的套接字描述符,address表示当前客户端的本地地址,是一个structsockaddr_un类型的变量,address_len表示本地地址的字节长度。实现连接的代码如下:
connect(client_sockfd,(structsockaddr*)&client_address,sizeof(client_address));
无论客户端还是服务器,都要和对方进行数据上的交互,这种交互也正是我们进程通信的主题。一个进程扮演客户端的角色,另外一个进程扮演服务器的角色,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。发送和接收数据要使用write和read系统调用,它们的原形为:
intread(intsocket,char*buffer,size_tlen); intwrite(intsocket,char*buffer,size_tlen);
其中socket为套接字描述符;len为需要发送或需要接收的数据长度;对于read系统调用,buffer是用来存放接收数据的缓冲区,即接收来的数据存入其中,是一个输出参数;对于write系统调用,buffer用来存放需要发送出去的数据,即buffer内的数据被发送出去,是一个输入参数;返回值为已经发送或接收的数据长度。例如客户端要发送一个"Hello"字符串给服务器,则代码如下:
charbuffer[10]="Hello"; write(client_sockfd,buffer,strlen(buffer));
交互完成后,需要将连接断开以节省资源,使用close系统调用,其原形为:
intclose(intsocket);
不多说了,直接使用,大家一定都会,呵呵!
上面所述的每个系统调用都有-1返回值,在调用不成功时,它们均会返回-1,这个特性可以使得我们用if-else或异常处理语句来处理错误,为我们提供了很大的方便。
SOCK_DGRAM数据报式本地套接字的应用场合很少,因为流式套接字在本地的连接时间可以忽略,所以效率并没有提高,而且发送接收都需要携带对方的本地地址,因此很少甚至几乎不使用。
与本地套接字相对应的是网络套接字,可以用于在网络上传送数据,换言之,可实现不同机器上的进程通信过程。在TCP/IP协议中,IP地址的首字节为127即代表本地,因此本地套接字通信可以使用IP地址为127.x.x.x的网络套接字来实现。
总结
以上就是本文关于Linux进程间通信方式之socket使用实例的全部内容,希望对大家有所帮助。欢迎参阅:浅谈Linux进程间通信方式及优缺点、Linux下文件的切分与合并的简单方法介绍、Linux中在防火墙中开启80端口方法示例等,感谢朋友们对本站的支持。