一、socket本机通信背景:
UNIX Domain Socket 用于 IPC 更有效率:不需要经过 网络协议栈,不需要 打包拆包、计算校验 和、维护序号和应答等,只是将应用层数据从一个进程 拷贝 到 另一个进程 。
X Window 服务器 和 GUI程序 之间就是通过UNIX Domain Socket通讯的 。
UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可 。
二、服务器端 api 总结:
1. socket: 建立一个socket
2. bind: 将这个socket绑定在某个文件上(AF_UNIX)或某个端口上(AF_INET),我们会分别介绍这两种。
3. listen: 开始监听
4. accept: 如果监听到客户端连接,则调用accept接收这个连接并同时新建一个socket来和客户进行通信
5. read/write:读取或发送数据到客户端
6. close: 通信完成后关闭socket
三、客户端 api 总结:
1. socket: 建立一个socket
2. connect: 主动连接服务器端的某个文件(AF_UNIX)或某个端口(AF_INET)
3. read/write:如果服务器同意连接(accept),则读取或发送数据到服务器端
4. close: 通信完成后关闭socket
四、案例小总结:
vim Makefile
all: tcp_client.c tcp_server.c
gcc -g -Wall -o tcp_client tcp_client.c
gcc -g -Wall -o tcp_server tcp_server.c
clean:
rm -rf *.o tcp_client tcp_server
tcp_server.c:
1. #include <sys/types.h>
2. #include <sys/socket.h>
3. #include <sys/un.h>
4. #include <unistd.h>
5. #include <stdlib.h>
6. #include <stdio.h>
7.
8. int main()
9. {
10. /* delete the socket file */
11. unlink("server_socket");
12.
13. /* create a socket */
14. int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
15.
16. struct sockaddr_un server_addr;
17. server_addr.sun_family = AF_UNIX;
18. strcpy(server_addr.sun_path, "server_socket");
19.
20. /* bind with the local file */
21. bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
22.
23. /* listen */
24. listen(server_sockfd, 5);
25.
26. char ch;
27. int client_sockfd;
28. struct sockaddr_un client_addr;
29. socklen_t len = sizeof(client_addr);
30. while(1)
31. {
32. printf("server waiting:\n");
33.
34. /* accept a connection */
35. client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
36.
37. /* exchange data */
38. read(client_sockfd, &ch, 1);
39. printf("get char from client: %c\n", ch);
40. ++ch;
41. write(client_sockfd, &ch, 1);
42.
43. /* close the socket */
44. close(client_sockfd);
45. }
46.
47. return 0;
48. }
tcp_client.c:
1. #include <sys/types.h>
2. #include <sys/socket.h>
3. #include <sys/un.h>
4. #include <unistd.h>
5. #include <stdlib.h>
6. #include <stdio.h>
7.
8. int main()
9. {
10. /* create a socket */
11. int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
12.
13. struct sockaddr_un address;
14. address.sun_family = AF_UNIX;
15. strcpy(address.sun_path, "server_socket");
16.
17. /* connect to the server */
18. int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
19. if(result == -1)
20. {
21. perror("connect failed: ");
22. exit(1);
23. }
24.
25. /* exchange data */
26. char ch = 'A';
27. write(sockfd, &ch, 1);
28. read(sockfd, &ch, 1);
29. printf("get char from server: %c\n", ch);
30.
31. /* close the socket */
32. close(sockfd);
33.
34. return 0;
35. }
以AF_UNIX方式进行通信的,这种方式是通过文件来将服务器和客户端连接起来的,因此我们应该先运行tcp_server,创建这个文件,默认情况下,这个文件会创建在当前目录下,并且第一个s表示它是一个socket文件.
我们调用socket函数创建一个socket:
int socket(int domain, int type, int protocol)
domain:指定socket所属的域,常用的是AF_UNIX或AF_INET
AF_UNIX表示以文件方式创建socket,AF_INET表示以端口方式创建socket(我们会在后面详细讲解AF_INET)
type:指定socket的类型,可以是SOCK_STREAM或SOCK_DGRAM
SOCK_STREAM表示创建一个有序的,可靠的,面向连接的socket,因此如果我们要使用TCP,就应该指定为SOCK_STREAM
SOCK_DGRAM表示创建一个不可靠的,无连接的socket,因此如果我们要使用UDP,就应该指定为SOCK_DGRAM
protocol:指定socket的协议类型,我们一般指定为0表示由第一第二两个参数自动选择。
socket()函数返回新创建的socket,出错则返回-1
地址格式:
常用的有两种socket域:AF_UNIX或AF_INET,因此就有两种地址格式:sockaddr_un和sockaddr_in,分别定义如下:
1. struct sockaddr_un
2. {
3. sa_family_t sun_family; /* AF_UNIX */
4. char sun_path[]; /* pathname */
5. }
6.
7. struct sockaddr_in
8. {
9. short int sin_family; /* AF_INET */
10. unsigned short int sin_port; /* port number */
11. struct in_addr sin_addr; /* internet address */
12. }
从上面的定义我们可以看出,sun_path存放socket的本地文件名,sin_addr存放socket的ip地址,sin_port存放socket的端口号。
创建完一个socket后,我们需要使用bind将其绑定:
int bind(int socket, const struct sockaddr * address, size_t address_len)
如果我们使用AF_UNIX来创建socket,相应的地址格式是sockaddr_un,而如果我们使用AF_INET来创建socket,相应的地址格式是sockaddr_in,因此我们需要将其强制转换为sockaddr这一通用的地址格式类型,而sockaddr_un中的sun_family和sockaddr_in中的sin_family分别说明了它的地址格式类型,因此bind()函数就知道它的真实的地址格式。第三个参数address_len则指明了真实的地址格式的长度。
bind()函数正确返回0,出错返回-1
接下来我们需要开始监听了:
int listen(int socket, int backlog)
backlog:等待连接的最大个数,如果超过了这个数值,则后续的请求连接将被拒绝
listen()函数正确返回0,出错返回
接受连接:
int accept(int socket, struct sockaddr * address, size_t * address_len)
同样,第二个参数也是一个通用地址格式类型,这意味着我们需要进行强制类型转化
这里需要注意的是,address是一个传出参数,它保存着接受连接的客户端的地址,如果我们不需要,将address置为NULL即可。
address_len:我们期望的地址结构的长度,注意,这是一个传入和传出参数,传入时指定我们期望的地址结构的长度,如果多于这个值,则会被截断,而当accept()函数返回时,address_len会被设置为客户端连接的地址结构的实际长度。
另外如果没有客户端连接时,accept()函数会阻塞
accept()函数成功时返回新创建的socket描述符,出错时返回-1
客户端通过connect()函数与服务器连接:
int connect(int socket, const struct sockaddr * address, size_t address_len)
对于第二个参数,我们同样需要强制类型转换
address_len指明了地址结构的长度
connect()函数成功时返回0,出错时返回-1
通信完成后,我们需要关闭socket:
int close(int fd)
close是一个通用函数(和read,write一样),不仅可以关闭文件描述符,还可以关闭socket描述符