bilibili 就业班视频搬运 p55
1.作用
本地进程通信使用。
2. 类型
2.1 面向连接的,类似于TCP
(但不是TCP 呀!这里不需要什么协议了!)
socket函数的第二个参数填写 SOCK_STREAM
int sfd =socket(AF_UNIX, SOCK_STREAM, 0)
2.2 面向无连接的 类似UDP
socket函数的第二个参数填写 SOCK_DGRAM
man 7 unix自己查看一下
3. 结构体解析
A UNIX domain socket address is represented in the following structure:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Pathname */
};
地址结构体, 第一个成员sun_familiy 就填写af_unix
第二个成员你填写你的socket文件的路径,一般都是当前文件夹下 “./你起的名字” 就行了
千万别事先创建
4.第一次有瑕疵的代码
4.1 有瑕疵的服务器代码 (后面改)
//本地socket通信 服务端代码
//local1SERFile 是我自己写的结构体地址的文件路径
//你可以随意命名 但记住这个名字#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>int main(){//创建socket描述符int sfd =socket(AF_UNIX, SOCK_STREAM, 0);if (sfd<0){perror(" socket wrong\n");return -1;}//建立服务器的地址结构体/* struct sockaddr_un {sa_family_t sun_family; AF_UNIX char sun_path[108]; Pathname }; */struct sockaddr_un serad; serad.sun_family = AF_UNIX; strcpy(serad.sun_path, "./local1SERFile"); //绑定 注意结构体的成员不一样bind(sfd,(struct sockaddr*)&serad, sizeof(serad));//listenlisten(sfd,128);//建立客户端地址clienad;struct sockaddr_un clienad; //注意,客户端的地址成员不用你写了socklen_t clilen = sizeof(clienad);//接受客户端的连接/这里应该不是高并发。 int newfd = accept(sfd, (struct sockaddr*)&clienad, &clilen);if (newfd<0) {perror("accept error\n");}char buf[1024]; while (1){//read writememset (buf, 0x00, sizeof(buf));int n =read (newfd, buf, sizeof(buf));if (n<=0 ){printf("读到0个字符,客户断线\n"); break;}//大小写转换,并且返回给客户端for (int i=0;i<n;i++){buf[i] = toupper(buf[i]);}write (newfd, buf, n);}
close(newfd);
close (sfd);
return 0;
}
4.2 如何测试
gcc -o local1 local1.c
我把这个服务器代码编译成可执行文件local1
./local1 回车,服务端开始执行,
开启一个新终端, nc -U ./local1SERFile 注意这个最后的文件名,是我写进代码里的,结构体的自定义的socket文件的名字,你写你自己的
然后就可以通信了
命令netstat -anp | grep local (local换成你自己编译的最终可执行文件名)查看网络状态
5. 改进
上面代码是不完善的,现在展示出错:
现在我们ctrl+C 退出客户端,过去服务端那边终端一看,服务端也自动退出了。符合预期。
但是重启客户端./local1 的时候,反复报错
accept error
: Invalid argument
读到0个字符,客户断线
???这是什么问题?因为前面说了
bind函数的地址参数里,如果文件已经存在那么久报错
看来是bind 函数报错,不是accept函数报错。
#include
int unlink(const char *pathname);
参数:
pathname:指向需解除连接的文件名。
返回说明:
成功执行时,返回0。失败返回-1,errno被设置。
注意只能对文件起作用!!!
这个函数不是直接删除文件!是删除硬链接!直到指向这个文件的硬链接=1 的时候,再执行这个函数就是真的 删掉了文件内容了
那么就在你的服务端代码第一行添上这句吧
经过添加unlink后,服务端和客户端又可以工作了
此时 用命令netstat -anp | grep local
(这个Local是因为我的最后编译成的可执行文件就叫local2 这样就能找到我的进程了,你写你自己的文件名字
看到客户端和服务器端的状态是;
unix 3 [ ] STREAM CONNECTED 70827 6223/./local2 ./local2FILE
unix 2 [ ACC ] STREAM LISTENING 70826 6223/./local2 ./local2FILE
6. 客户端代码
这样就不用nc 命令测试了
6.1 客户端代码里没有bind
这里服务器代码又小改了一下,所以都展示上
//本地socket通信 服务端代码
//ser.sock 是结构体地址的文件路径
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>int main(){unlink("./ser.sock");//创建socket描述符int sfd =socket(AF_UNIX, SOCK_STREAM, 0);if (sfd<0){perror(" socket wrong\n");return -1;}//建立服务器的地址结构体struct sockaddr_un serad; serad.sun_family = AF_UNIX; strcpy(serad.sun_path, "./ser.sock"); //绑定 注意结构体的成员不一样bind(sfd,(struct sockaddr*)&serad, sizeof(serad));//listenlisten(sfd,128);//建立客户端地址clienad;struct sockaddr_un clienad; //注意,客户端的地址成员不用你写了socklen_t clilen = sizeof(clienad);int newfd = accept(sfd, (struct sockaddr*)&clienad, &clilen);if (newfd<0) {perror("accept error\n");}char buf[1024]; while (1){memset (buf, 0x00, sizeof(buf));int n =read (newfd, buf, sizeof(buf));printf("this is server,n=%d,receive %s\n", n, buf); if (n<=0 ){printf("读到0个字符,客户断线\n"); break;}//大小写转换,并且返回给客户端for (int i=0;i<n;i++){buf[i] = toupper(buf[i]);}write (newfd, buf, n);}close(newfd);close (sfd);return 0;
}
客户端代码;客户端没有建立自己的客户socket文件;也能正常建立连接,获得变成了大写的字母
//本地socket通信 客户端代码
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>int main(){//创建socket描述符clientfd ,cfdint cfd =socket(AF_UNIX, SOCK_STREAM, 0);if (cfd<0){perror("client socket wrong\n");return -1;}//想要连接的服务器的地址结构体struct sockaddr_un toserad; toserad.sun_family = AF_UNIX;//注意,socket文件,需要跟服务端的一样 strcpy(toserad.sun_path, "./ser.sock"); connect(cfd,(struct sockaddr*)&toserad, sizeof(toserad));char buf[1024];int n =0; while (1){memset (buf, 0x00, sizeof(buf));//STDIN_FILENOprintf("please type in:\n");n = read (STDIN_FILENO,buf,sizeof(buf));//发送数据 write (cfd, buf, n);memset(buf,0x00, sizeof(buf));//接收服务器的回馈n = read (cfd, buf,sizeof(buf)); if (n<=0 ){printf("this is client, receive no data\n"); break;}printf("this is client, receive %s\n",buf);}
close (cfd);
return 0;
}
执行:
千万先启动服务端、再启动客户端!!!
客户端
please type in:
abc (你敲的abc进去)
this is client, receive ABC
服务端:
this is server,n=4,receive abc
abc后面还有一个回车,所以服务端接受的是 4个字符。
6.2 客户端代码里bind了自己的客户套接字文件
服务代码跟6.1 里面一模一样,不写了
客户端代码展示。编译后,启动也是先服务端、再客户端、
//本地socket通信 客户端代码
//含有客户socket文件cli.sock
//这篇客户代码使用了bind函数。
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>int main(){//创建socket描述符clientfd ,cfdint cfd =socket(AF_UNIX, SOCK_STREAM, 0);if (cfd<0){perror("client socket wrong\n");return -1;}//想要连接的服务器的地址结构体struct sockaddr_un toserad; toserad.sun_family = AF_UNIX;//注意,socket文件,需要跟服务端的一样 strcpy(toserad.sun_path, "./ser.sock"); unlink ("./cli.sock");struct sockaddr_un cliad;cliad.sun_family = AF_UNIX;strcpy(cliad.sun_path, "./cli.sock"); int ret = bind(cfd,(struct sockaddr*)&cliad, sizeof(cliad));if (ret<0){perror("client bind wrong\n");return -1;}connect(cfd,(struct sockaddr*)&toserad, sizeof(toserad));char buf[1024];int n =0; while (1){memset (buf, 0x00, sizeof(buf));//STDIN_FILENOprintf("please type in:\n");n = read (STDIN_FILENO,buf,sizeof(buf));//发送数据 write (cfd, buf, n);memset(buf,0x00, sizeof(buf));//接收服务器的回馈n = read (cfd, buf,sizeof(buf)); if (n<=0 ){printf("this is client, receive no data\n"); break;}printf("this is client, receive %s\n",buf);}
close (cfd);
return 0;
}
执行结果跟刚才一样的
6.3 如何打印客户端地址
客户端有了自己的套接字文件的话,服务端就能够找到客户端的socket文件路径。
换句话说服务端现在知道是谁(哪个socket文件) 在跟自己通信了
服务端的代码,在accept函数后面 加一行代码
//打印客户端地址
printf("客户端地址%s\n",clienad.sun_path);