本地socket通信

本文详细介绍了在Unix域中使用socket进行本地进程通信,包括面向连接和无连接两种模式的区别,以及如何正确设置socket地址结构体。通过示例展示了服务端和服务端代码,强调了bind和accept函数的作用,并解决了客户端连接错误的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值