不管是做哪一种开发linux使用socket通信,都离不开网路编程,网路编程又常常会涉及套接字(socket)。Socket最初由加洲学院伯克利校区开发,它是一种全双工的通讯方法,不同于pipe这些单工方法,主要用于实现4.2BSD上的进程间通讯。
我们常说的socket通讯有以下二种,主要会说一下Unixdomainsocket
Internetdomainsocket
也叫IPsocket,它要借助主机的传输层(tcp),可以用于同一台主机上不同进程间的通讯,也可以用于网路上不同主机间的通讯。如同聊QQ一样只要晓得了对方的QQ号就可以聊天了。socket只要晓得了对方的ip地址和端口就可以通讯了所以这些socket通讯是基于网路合同栈的。
Unixdomainsocket
Unixdomainsocket,也叫IPCsocket(inter-precesscommunicationsocket,也就是进程间通讯套接字),用于同一台主机上的不同进程间交换数据,是Posix系统的标准组件。
该socket用于一台主机的进程间通讯,不须要基于网路合同,主要是基于文件系统的。与Internetdomainsocket类似,须要晓得是基于哪一个文件(相同的文件路径)来通讯的。unixdomainsocket有2种工作模式一种是SOCK_STREAM,类似于TCP,可靠的字节流。另一种是SOCK_DGRAM,类似于UDPlinux系统编程,不可靠的字节流。不仅传输数据以外,还可以使用Unixdomainsocket传输文件描述符(filedescriptor)。
工作模型
socket通讯有一个服务端,一个客服端
服务端:创建socket—绑定文件(端口)—监听—接受顾客端联接—接收/发送数据—…—关闭
顾客端:创建socket—绑定文件(端口)—连接—发送/接收数据—…—关闭
代码框架
server
sockaddr_un:为一个系统级的结构体,主要用于储存地址
#define NAME "socketAddr" main(){ ... struct sockaddr_un server; ... int sock = socket(AF_UNIX, SOCK_STREAM, 0); strcpy (server.sun_path, NAME); ... bind(sock, (structsockaddr *) &server, sizeof(struct sockaddr_un)); ... listen(sock, 5); ... while(1){ ... msgsock = accept(sock, 0, 0); ... rval = read(msgsock, buf, 1024)) ... } }
client
#define NAME "socketAddr" main(){ ... sock = socket(AF_UNIX, SOCK_STREAM, 0); strcpy(server.sun_path, NAME); ... if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { close(sock); exit(1); } if (write(sock, DATA, sizeof(DATA)) < 0) perror("writing on stream socket"); close(sock); }
使用:
gccservice.c-oservicegccclient.c-oclientlinux下启动一个窗口运行./service启动另一个窗口运行./clientsocket【这个socket为service中绑定的地址】
运行后可以发觉,在当前目录下会多一个名叫socket的文件,这2个进程就是基于该文件通讯的UnixdomainsocketvsInternetdomainsocket
先来看一个使用案例,配置php-fpm与Nginx交互的socket:
fastcgi_pass 127.0.0.1:9000 fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock
这个案例中,运行在同一台机器上的php和Nginx须要通讯,有2种实现方法:第一种是ipsocket,通过本机回环地址127.0.0.1加端口实现;第二种是通过unixdomainsocket实现。哪一种效率更高呢?
基于localhost的ipsocket须要实现跨网路主机通信的全部环节,包括完善socket联接中文linux操作系统,ACk开支,tcp流控,封装/解封linux使用socket通信,路由。在这个过程中都会有2个contextswitch,由于使用网路层传输数据须要调用systemcall,而调用systemcall会形成中断,致使contextswitch的;另外一个进程接受到来自网路层的联接恳求,也会形成系统中断,致使contextswitch。以上过程造成2个contextswitch的开支,外加其它各类开支(overhead)。
怎样通过命令行访问unixsocket文件
socket为何不能用传统命令访问?
socket文件不能通过普通的文件读写命令操作(例如说echo"xxx">socket.file)它。由于它是在网路层里面工作的。只能通过socket读写函数去操作它。
socat和ncat命令
虽然通过的linux命令socat和ncat可以去操作socket。
其中-U指定了该文件是Unix域socket文件类型,ncat实现了类似于cat命令的访问unixsocket。
ncat-U/tmp/tbsocket1
ncat也可以通过映射socket文件到窃听的端口上。这么通过curl可以发送恳求到该窃听端口,实现写操作。
# 映射tcp的8080流量到unix socket ncat -vlk 8080 -c 'ncat -U /tmp/tbsocket1' # 通过curl发起http请求访问 curl http://localhost:8080
也可以使用功能更强悍的socat来实现。
# 映射8080/tcp 到unix socket socat -d -d TCP-LISTEN:8080,fork UNIX:/tmp/tbsocket1
用Curl命令访问UnixSocket插口的方式
常常碰到一些窃听地址不是IP:Port而是unixsocket的程序,这种程序假如使用的是HTTP合同,unixsocket插口也可以用curl访问。
比如ingress-nginx的窃听地址为unix:/tmp/nginx-status-server.sock:
server { listen unix:/tmp/nginx-status-server.sock; set $proxy_upstream_name "internal"; keepalive_timeout 0; gzip off; access_log off; location /healthz { return 200; } location /nginx_status { stub_status on; } ... 省略... }
用curl访问它的unixsocket的方式如下:
$ curl --unix-socket /tmp/nginx-status-server.sock http://localhost/nginx_status Active connections: 77 server accepts handled requests 64273 64273 971368 Reading: 0 Writing: 12 Waiting: 65
--unix-socket指定unixsocket文件的地址,是要恳求的路径。
注意localhost可以依据实际情况修改成其它数值但不可省略,假如省略后弄成,这么nginx_status会被认作是Host,Path被觉得是/:
$ curl -v --unix-socket /tmp/nginx-status-server.sock http://nginx_status * Expire in 0 ms for 6 (transfer 0xe464ab3dd0) * Trying /tmp/nginx-status-server.sock... * Expire in 200 ms for 4 (transfer 0xe464ab3dd0) * Connected to nginx_status (/tmp/nginx-status-server.sock) port 80 (#0) > GET / HTTP/1.1 > Host: nginx_status > User-Agent: curl/7.64.0 > Accept: */*