frameworks 之Socket


Socket是应用层与TCP/IP协议族通信的中间软件抽象层。他作为Android 进程间的通信。
涉及到的类如下

  • frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

Socket

创建一个socket 需要几个步骤
服务端: socket -> bind -> listen -> accept -> read/write -> close
客户端: socket -> connect -> read/write -> close

服务端

1.创建Socket。

第一个参数为协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL**(或称AF_UNIX,Unix域socket,这个是我们的讲解重点)**、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址

第二个参数 指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET

第三个参数为 指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

int socketFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);

2.绑定socket

bind()函数就是将给这个描述字绑定一个名字。addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。addrlen:对应的是地址的长度。

int bindResult = bind(socketFd,  (struct sockaddr *)&serviceAddr, serviceLen);

地址为抽象和文件类型。文件类型会生成一个文件在本地,抽象需要在地址前面加0,对应会生成@开头。可以用如下命令查询查看

netstat -an | grep 地址名称

3.监听socket

设置监听的最大连接个数

int listenerResult = listen(socketFd, SOCKET_NUMBER);

4.等待客户端连接

传入客户端的对象 和长度 由内核写入。等待客户端的连接。

int connectFd = accept(socketFd, (struct sockaddr *)&clientAddr, &clintLen);

5.读取或者写入给客户端

通过 read 或者 write 函数跟客户端通信

		   int dataSize = read(connectFd, &buffer, sizeof(buffer));
           if (dataSize == 0)
           {
                printf("客户端关闭连接\n");
           } else if (dataSize == -1)
           {
                printf("读取异常\n");
                exit(1);
           } else {
                printf("客户端发送了: %s\n" ,buffer);
                for (int i = 0; i < sizeof(buffer); i++)
                {
                    buffer[i] = toupper(buffer[i]);
                }
                write(connectFd, buffer, sizeof(buffer));
           }

客户端

1.创建Socket。

int serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);

2.连接服务端Socket

发起对服务端的连接,连接成功后 服务端 accept 方法将继续执行

int connectResult = connect(serviceFd, (struct sockaddr *)&clientAddr, clientLen);

3.读取或者写入给客户端

		int wirteResult = write(serviceFd, winBuffer, sizeof(winBuffer));
        printf("写入服务端数据 %i", wirteResult);
        // 阻塞读取服务端数据
        int readResult =read(serviceFd, winBuffer, sizeof(winBuffer));

4.关闭socket

close(serviceFd);

演示代码

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>


// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "natvie_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80

/**
 * 主文件入口
*/
int main (void) {
    // socket 文件描述符
    int socketFd;
    // 客户端的socket 文件描述符
    int clintSocetFd;
    // socket 实体类
    struct sockaddr_un serviceAddr, clientAddr;
    // 地址长度
    socklen_t serviceLen; 
    socklen_t clintLen;
    // 数据读取
    char buffer[BUFFER_SZIE];

    // 创建socket
    socketFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);
    if (socketFd == -1)
    {
        printf("创建 socket 失败\n");
        exit(1);
    }
    // 清空
    memset(&serviceAddr, 0 ,sizeof(sockaddr_un));
    serviceAddr.sun_family = AF_UNIX;
    // 设置地址
    strncpy(serviceAddr.sun_path, SOCKET_SERVICE_ADDRESS, sizeof(serviceAddr.sun_path) - 1);
    // 计算长度
    serviceLen = sizeof(serviceAddr.sun_family) + sizeof(serviceAddr.sun_path);
    // 绑定地址
    int bindResult = bind(socketFd,  (struct sockaddr *)&serviceAddr, serviceLen);
    if (bindResult == -1)
    {
        printf("绑定 socket 失败\n");
        exit(1);
    }
    // 绑定成功后 需要监听
    int listenerResult = listen(socketFd, SOCKET_NUMBER);
    if (listenerResult == -1)
    {
        printf("监听 socket 失败\n");
        exit(1);
    }
    printf("服务端 socket建立 等待连接\n");
    // 死循环等待连接
    while (1)
    {
        // 等待接收客户端连接, 同时客户端的信息会写入该类, 会阻塞在这里
        clintLen = sizeof(struct sockaddr_un);
        int connectFd = accept(socketFd, (struct sockaddr *)&clientAddr, &clintLen);
        if (connectFd == -1) 
        {
            printf("监听 socket 失败\n");
            exit(1);
        }
        // 创建成功,循环读取消息
        while (1)
        {
            // 0 为关闭 -1 为异常
           int dataSize = read(connectFd, &buffer, sizeof(buffer));
           if (dataSize == 0)
           {
                printf("客户端关闭连接\n");
           } else if (dataSize == -1)
           {
                printf("读取异常\n");
                exit(1);
           } else {
                printf("客户端发送了: %s\n" ,buffer);
                for (int i = 0; i < sizeof(buffer); i++)
                {
                    buffer[i] = toupper(buffer[i]);
                }
                write(connectFd, buffer, sizeof(buffer));
           }
           
        }
        
    }
    // 退出for循环 关闭socket
    printf("服务端关闭连接\n");
    close(socketFd);
    return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "natvie_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80
// 输入最大行
#define MAXLINE 80

int main(void) {
    // 客户端操作符
    int serviceFd;
    sockaddr_un clientAddr;
    // 地址长度
    socklen_t clientLen;

    // 从输入框输入流
    char winBuffer[BUFFER_SZIE];

    serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);
    if (serviceFd == -1)
    {
        printf("创建 socket 失败");
        exit(0);
    }
    // 清空数据
    memset(&clientAddr, 0, sizeof(struct sockaddr_un));
    // 赋值地址
    clientAddr.sun_family = AF_UNIX;
    strncpy(clientAddr.sun_path, SOCKET_SERVICE_ADDRESS, sizeof(clientAddr.sun_path) - 1);
    clientLen = sizeof(clientAddr);
    printf("socket地址: %s\n", clientAddr.sun_path);
    int connectResult = connect(serviceFd, (struct sockaddr *)&clientAddr, clientLen);
    if (connectResult == -1)
    {
        perror("连接 socket 失败");
        exit(0);
    }
    printf("请输入对应的数据:");
    while (fgets(winBuffer, MAXLINE, stdin))
    {
        // 把读到的数据给服务端
        int wirteResult = write(serviceFd, winBuffer, sizeof(winBuffer));
        printf("写入服务端数据 %i", wirteResult);
        // 阻塞读取服务端数据
        int readResult =read(serviceFd, winBuffer, sizeof(winBuffer));
        if (readResult <= 0) {  
            printf("服务端异常或者已关闭\n");
            break;
         }else {  
            printf("接收到服务端的消息: %s \n",winBuffer);
         }
         printf("请输入对应的数据:");
    }
    close(serviceFd);
    return 0;   
}

Epoll

上面提到的socket 的 accept 和 read 等方法都是阻塞方法,等多客户端无法实现实时的读取和监听,如果每个客户端开对应的线程则比较耗资源。因此有了Epoll 等待唤醒。Epoll 有如下方法。

创建Epoll

需要传入一个数量,该数量大于0即可。并会返回一个文件描述符

// 创建 epoll, 参数size 实际没作用,但是要大于0 
int epollFd = epoll_create(EPOLL_SZIE);

添加或删除Epoll

第二参数控制 添加还是删除
第三个参数为需要监听的文件操作符
第四个参数为 epoll_event 类型
data.fd 可以存储对应的文件ID
events属性表示数据类型 EPOLLIN

 int addResult = epoll_ctl(epollFd, EPOLL_CTL_ADD, serviceFd, &epollEvent); 

等待消息返回Epoll

events参数传入事件数组,内核会对应写入的数组。可通过 events.data.fd 获取对应的客户端。
size 表示每次可以处理的最大事件数量
最后参数代表等待的事件,0为马上返回,-1为阻塞等待

 int eventNum = epoll_wait(epollFd, events, EPOLL_EVENT_MAX_SZIE, EPOLL_TIEM_OUT);

演示代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// scoket相关
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
// epoll 相关
#include <sys/epoll.h>

// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// SOCKETD地址
#define SOCKET_SERVICE_ADDRESS "epoll_socket_test"
// 允许连接的长度
#define SOCKET_NUMBER 20
// 读取数据大小
#define BUFFER_SZIE 80
// epoll大小
#define EPOLL_SZIE 80
// epoll消息大小
#define EPOLL_EVENT_MAX_SZIE 20
// epoll消息超时
#define EPOLL_TIEM_OUT -1

// 采用抽象作用域
int main(void) {
    int serviceFd;
    sockaddr_un serviceAddr;
    socklen_t serviceLen, clientLen;
    char buffer[BUFFER_SZIE];

    serviceFd = socket(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL);
    if (serviceFd == -1) 
    {
        perror("创建 socket 失败");
        exit(0);
    }
    memset(&serviceAddr, 0 , sizeof(struct sockaddr) - 1);
    serviceAddr.sun_family = AF_UNIX;
    // 这里采用抽象的协议, 第一位要给0
    serviceAddr.sun_path[0] = 0;
    strcpy(serviceAddr.sun_path + 1, SOCKET_SERVICE_ADDRESS);
    // serviceLen = sizeof(serviceAddr.sun_family) + strlen(SOCKET_SERVICE_ADDRESS) + 1;
    serviceLen = sizeof(serviceAddr.sun_family) + sizeof(serviceAddr.sun_path);
    printf("绑定的地址为:%s \n", serviceAddr.sun_path);
    // 绑定
    int bindResult = bind(serviceFd, (struct sockaddr *)&serviceAddr, serviceLen);
    if (bindResult == -1) 
    {
        perror("绑定 socket 失败");
        exit(0);
    }
    int listenResult = listen(serviceFd, SOCKET_NUMBER);
    if (listenResult == -1) 
    {
        perror("监听 socket 失败");
        exit(0);
    }
    // 创建 epoll, 参数size 实际没作用,但是要大于0 
    int epollFd = epoll_create(EPOLL_SZIE);
    if (epollFd == -1)
    {
        perror("创建 epoll 失败");
        exit(0);
    }
    // 将对应的文件夹 添加 epoll中
    epoll_event epollEvent;
    /* *** 注意****
    epoll_data 是一个联合体结构成员,所有的成员公用一段内存,因此实际上只能保留一个成员
    它只会保存最后一个被赋值的成员,所以不要data.fd,data.ptr都赋值
    */
    epollEvent.data.fd = serviceFd;
    epollEvent.events = EPOLLIN;

    // 循环检测 委托内核去处理
    // 当内核检测到事件到来时会将事件写到这个结构体数组里
    struct epoll_event events[EPOLL_EVENT_MAX_SZIE];

    int addResult = epoll_ctl(epollFd, EPOLL_CTL_ADD, serviceFd, &epollEvent); 
    if (addResult == -1)
    {
        perror("添加到 epoll 失败");
        exit(0);
    }
    printf("开始接收消息\n");
    while (1)
    {
        // 等待有没消息返回
        // tiemOut 为0 马上返回 为-1 代表阻塞
        // maxevents:表示每次能处理的最大事件数,告之内核这个events有多大;这个maxevents的值不能大于创建epoll_create()时的size;
        int eventNum = epoll_wait(epollFd, events, EPOLL_EVENT_MAX_SZIE, EPOLL_TIEM_OUT);
        printf("收到消息数量: %i \n", eventNum);
        for (int i = 0; i < eventNum; i++)
        {
            // 有连接请求到来,走到这里
            if (events[i].data.fd == serviceFd)
            {
                // 客户端信息填充
                struct sockaddr_un clientAddr;
                socklen_t clientLen = sizeof(clientAddr);
                int connectFd = accept(serviceFd, (struct sockaddr *)&clientAddr, &clientLen);
                if (connectFd == -1)
                {
                    perror("接收客户端失败");
                    exit(0);
                }
                printf("接收到新的客户端\n");
                //将用于通信的文件描述符挂到epoll树上
                epollEvent.data.fd = connectFd;
                epollEvent.events = EPOLLIN;
                epoll_ctl(epollFd, EPOLL_CTL_ADD, connectFd, &epollEvent);
            } else
            {
                // 其他的为客户端发送的值
                // 通信也有可能是写事件
                if (events[i].events & EPOLLOUT)
                {
                    //这里先忽略写事件
                    continue;
                }
                char buf[1024]={0};
                int count = read(events[i].data.fd, buf, sizeof(buf));
                if (count == 0)
                {
                    // 关闭了客户端要关闭
                    printf("收到客户端关闭");
                    close(events[i].data.fd);
                    epoll_ctl(epollFd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                    continue;
                }
                if (count == -1)
                {
                   perror("接收消息异常退出");
                    exit(0);
                }
                printf("收到了消息事件:%s \n", buf);
                for (int i = 0; i < sizeof(buf); i++)
                {
                    buf[i] = toupper(buf[i]);
                }
                write(events[i].data.fd, buf, sizeof(buf));
            }
        }
        
    }
    close(serviceFd);
    return 0;
}

SocketPair

如果是单纯1对1通讯,可通过 socketPair 快速建立服务端和客户端

创建socketPair

创建一个数组存储对应的ID,创建的socket 会放在该数组里面

int socketFds[2];
// 创建socke 第一个作为服务端 第二个作为客户端
int result = socketpair(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL, socketFds);

设置对应的属性

以下代码设置对应的缓冲区

socklen_t len = sizeof(BUFFER_SZIE);
setsockopt(socketFds[0], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[0], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[1], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
setsockopt(socketFds[1], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);

演示代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <unistd.h>

// 协议域
int SOCKET_DOMIAN = AF_UNIX;
// 类型
int SOCKET_TYPE = SOCK_STREAM;
// 协议,常用tcp udp
int SOCKET_PROTOCOL = 0;
// 缓存区大小
int BUFFER_SZIE = 1024;

int main(void) {

    int socketFds[2];
    // 创建socke 第一个作为服务端 第二个作为客户端
    int result = socketpair(SOCKET_DOMIAN, SOCKET_TYPE, SOCKET_PROTOCOL, socketFds);
    if (result == -1)
    {
        perror("创建 socketPair 错误");
        exit(0);
    }
    /**
     * 第一个参数socket是套接字描述符。
     * 第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。
     * option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level,在套接字级别上(SOL_SOCKET)
     * 主要设置缓存区大小
    */
    socklen_t len = sizeof(BUFFER_SZIE);
    setsockopt(socketFds[0], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
    setsockopt(socketFds[0], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);
    setsockopt(socketFds[1], SOL_SOCKET, SO_SNDBUF, &BUFFER_SZIE, len);
    setsockopt(socketFds[1], SOL_SOCKET, SO_RCVBUF, &BUFFER_SZIE, len);
    int pid = fork();
    printf("孵化出来的进程号为 %i \n", pid);
    // 在子进程为0
    if (!pid)
    {
        printf("执行子进程\n");
        // 为子进程
        close(socketFds[0]);
        int count = 0;
        while (1)
        {
            // 休眠一秒
            sleep(1);
            // 发送给服务端端
            write(socketFds[1], &count, sizeof(count));
            int size = read(socketFds[1], &count, sizeof(count));
            printf("收到服务端数据 %i \n", count);
            ++count;
        }
        
    } else {
        printf("执行父进程\n");
        // 为父进程
        close(socketFds[1]);
        int count = 0;
        while (1)
        {   // 读取客户端数据
            int size = read(socketFds[0], &count, sizeof(count));
            if (read <= 0)
            {
                perror("客户端异常退出 \n");
                exit(0);
            }
            printf("收到客户端数据 %i \n", count);
            ++count;
            // 发送给客户端
            write(socketFds[0], &count, sizeof(count));
        }
        
    }
}
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值