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));
//listen
listen(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 write
memset (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));
//listen
listen(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 ,cfd
int 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_FILENO
printf("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 ,cfd
int 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_FILENO
printf("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);