当前位置: 首页 > news >正文

【计算机网络】网络编程接口 Socket API 解读(5)

   Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。

        本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。


connect

connect()           遵循 POSIX.1 - 2008

1.库

标准 c 库,libc, -lc

2.头文件

<sys/socket.h>

3.接口定义

        int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

4.接口描述

        connect() 系统调用在 sockfd 指定的 socket 上连接 addr 指定的地址,addrlen 参数指定了 addr 的大小,addr 地址格式取决于 socket 的地址空间,可以参考 socket(2)。

        如果 socket 是 SOCK_DGRAM 类型,那么 addr 是发送报文的默认地址,也是唯一接收报文的地址。如果 socket 类型是 SOCK_STREAM 或者 SOCK_SEQPACKET,那么这个调用就是尝试和绑定了 addr 地址的 socket 建立连接。

        一些协议套接字(比如 UNIX 流套接字)只能成功连接一次。

        一些协议套接字(比如 UNIX TCP 套接字和网络数据报套接字)可以多次 connect() 来修改连接。

        一些协议套接字(比如 UNIX TCP 套接字和网络数据报套接字)可以通过将 sockaddr 的 sa_family 设置为 AF_UNSPEC 来消除连接,之后 socket 就可以连接到其他地址了。(AF_UNSPEC 在 Linux 2.2 之后支持)。

5.返回值

        如果连接或者绑定成功,那么返回 0。

        发生错误时,返回 -1,并设置errno 来指示错误类型。

        错误值定义如下(这里指示普通 socket 的错误,还可能存在 domain-specific 错误码):

EACCESUNIX 域套接字通过路径名唯一标识,并且是套接字文件是没有写权限的,路径中任何一级的搜索权限也是没有的,可以参考 path_resolution(7)
EACCES/EPERM用户尝试连接到一个广播地址,却没有设置套接字的广播标记,或者请求被防火墙规则拦截了
EACCES如果开启了 SELinux 策略,也可能会导致连接被拒绝(比如策略规定 HTTP 代理只能连接到 HTTP 服务器关联的端口,而 HTTP 代理却连接了其他端口)
EADDRINUSE本地地址已经在用了
EADDRNOTAVAIL(网络域套接字)sockfd 指定的套接字没有绑定到地址,并且在尝试将其绑定到临时端口时,临时端口用尽了
EAFNOSUPPORT地址家族不正确
EAGAIN对于非阻塞的 UNIX 域套接字,套接字是非阻塞的,连接无法立即完成。对于其他套接字家族,这个错误标识路由缓存没有足够的条目了
EALREADY套接字是非阻塞的,并且之前的连接尝试还没有完成
EBADFsockfd 不是一个打开的文件描述符
ECONNREFUSEDconnect() 操作的流套接字发现没有人在监听对应的远程地址
EFAULT套接字结构地址超出用户地址空间
EINPROGRESS套接字是非阻塞的,连接不能立即完成。(UNIX 域套接字会返回 EAGAIN)。可以通过 select(2) 或者 poll(2) 查看套接字的可写事件,来确定连接完成。select(2) 指示可写后,使用 getsockopt(2) 来读取 SOL_SOCKET 级的 SO_ERROR 选项,来确定连接完全成功(SO_ERROR 为 0)或者未成功(SO_ERROR 为这里列出来的普通错误)。
EINTR系统调用被信号打断
EISCONN套接字已经连接
ENETUNREACH网络不可达
ENOTSOCK文件描述符并没有指向一个套接字
EPROTOTYPE该套接字不支持指定的通信协议。这个错误可能在出现在连接一个 UNIX 域报文套接字到一个流套接字
ETIMEDOUT连接超时。可能是服务器太忙了以至于无法接收新的连接。注意:当服务器开启 syncookies 时,IP 套接字的超时可能会非常长。

6.注意

       如果 connect() 失败,那么套接字的状态是未知的。一个易于移植的程序应该关闭该套接字应该再创建一个新套接字,重新连接。

7.代码

        这里我们展示下 select() 的用法示例,来将最近几篇内容串起来:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>#define SERVER_PORT  12345#define TRUE             1
#define FALSE            0main (int argc, char *argv[])
{int    i, len, rc, on = 1;int    listen_sd, max_sd, new_sd;int    desc_ready, end_server = FALSE;int    close_conn;char   buffer[80];struct sockaddr_in6   addr;struct timeval       timeout;struct fd_set        master_set, working_set;/*************************************************************//* Create an AF_INET6 stream socket to receive incoming      *//* connections on                                            *//*************************************************************/listen_sd = socket(AF_INET6, SOCK_STREAM, 0);if (listen_sd < 0){perror("socket() failed");exit(-1);}/*************************************************************//* Allow socket descriptor to be reuseable                   *//*************************************************************/rc = setsockopt(listen_sd, SOL_SOCKET,  SO_REUSEADDR,(char *)&on, sizeof(on));if (rc < 0){perror("setsockopt() failed");close(listen_sd);exit(-1);}/*************************************************************//* Set socket to be nonblocking. All of the sockets for      *//* the incoming connections will also be nonblocking since   *//* they will inherit that state from the listening socket.   *//*************************************************************/rc = ioctl(listen_sd, FIONBIO, (char *)&on);if (rc < 0){perror("ioctl() failed");close(listen_sd);exit(-1);}/*************************************************************//* Bind the socket                                           *//*************************************************************/memset(&addr, 0, sizeof(addr));addr.sin6_family      = AF_INET6;memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));addr.sin6_port        = htons(SERVER_PORT);rc = bind(listen_sd,(struct sockaddr *)&addr, sizeof(addr));if (rc < 0){perror("bind() failed");close(listen_sd);exit(-1);}/*************************************************************//* Set the listen back log                                   *//*************************************************************/rc = listen(listen_sd, 32);if (rc < 0){perror("listen() failed");close(listen_sd);exit(-1);}/*************************************************************//* Initialize the master fd_set                              *//*************************************************************/FD_ZERO(&master_set);max_sd = listen_sd;FD_SET(listen_sd, &master_set);/*************************************************************//* Initialize the timeval struct to 3 minutes.  If no        *//* activity after 3 minutes this program will end.           *//*************************************************************/timeout.tv_sec  = 3 * 60;timeout.tv_usec = 0;/*************************************************************//* Loop waiting for incoming connects or for incoming data   *//* on any of the connected sockets.                          *//*************************************************************/do{/**********************************************************//* Copy the master fd_set over to the working fd_set.     *//**********************************************************/memcpy(&working_set, &master_set, sizeof(master_set));/**********************************************************//* Call select() and wait 3 minutes for it to complete.   *//**********************************************************/printf("Waiting on select()...\n");rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout);/**********************************************************//* Check to see if the select call failed.                *//**********************************************************/if (rc < 0){perror("  select() failed");break;}/**********************************************************//* Check to see if the 3 minute time out expired.         *//**********************************************************/if (rc == 0){printf("  select() timed out.  End program.\n");break;}/**********************************************************//* One or more descriptors are readable.  Need to         *//* determine which ones they are.                         *//**********************************************************/desc_ready = rc;for (i=0; i <= max_sd  &&  desc_ready > 0; ++i){/*******************************************************//* Check to see if this descriptor is ready            *//*******************************************************/if (FD_ISSET(i, &working_set)){/****************************************************//* A descriptor was found that was readable - one   *//* less has to be looked for.  This is being done   *//* so that we can stop looking at the working set   *//* once we have found all of the descriptors that   *//* were ready.                                      *//****************************************************/desc_ready -= 1;/****************************************************//* Check to see if this is the listening socket     *//****************************************************/if (i == listen_sd){printf("  Listening socket is readable\n");/*************************************************//* Accept all incoming connections that are      *//* queued up on the listening socket before we   *//* loop back and call select again.              *//*************************************************/do{/**********************************************//* Accept each incoming connection.  If       *//* accept fails with EWOULDBLOCK, then we     *//* have accepted all of them.  Any other      *//* failure on accept will cause us to end the *//* server.                                    *//**********************************************/new_sd = accept(listen_sd, NULL, NULL);if (new_sd < 0){if (errno != EWOULDBLOCK){perror("  accept() failed");end_server = TRUE;}break;}/**********************************************//* Add the new incoming connection to the     *//* master read set                            *//**********************************************/printf("  New incoming connection - %d\n", new_sd);FD_SET(new_sd, &master_set);if (new_sd > max_sd)max_sd = new_sd;/**********************************************//* Loop back up and accept another incoming   *//* connection                                 *//**********************************************/} while (new_sd != -1);}/****************************************************//* This is not the listening socket, therefore an   *//* existing connection must be readable             *//****************************************************/else{printf("  Descriptor %d is readable\n", i);close_conn = FALSE;/*************************************************//* Receive all incoming data on this socket      *//* before we loop back and call select again.    *//*************************************************/do{/**********************************************//* Receive data on this connection until the  *//* recv fails with EWOULDBLOCK.  If any other *//* failure occurs, we will close the          *//* connection.                                *//**********************************************/rc = recv(i, buffer, sizeof(buffer), 0);if (rc < 0){if (errno != EWOULDBLOCK){perror("  recv() failed");close_conn = TRUE;}break;}/**********************************************//* Check to see if the connection has been    *//* closed by the client                       *//**********************************************/if (rc == 0){printf("  Connection closed\n");close_conn = TRUE;break;}/**********************************************//* Data was received                          *//**********************************************/len = rc;printf("  %d bytes received\n", len);/**********************************************//* Echo the data back to the client           *//**********************************************/rc = send(i, buffer, len, 0);if (rc < 0){perror("  send() failed");close_conn = TRUE;break;}} while (TRUE);/*************************************************//* If the close_conn flag was turned on, we need *//* to clean up this active connection.  This     *//* clean up process includes removing the        *//* descriptor from the master set and            *//* determining the new maximum descriptor value  *//* based on the bits that are still turned on in *//* the master set.                               *//*************************************************/if (close_conn){close(i);FD_CLR(i, &master_set);if (i == max_sd){while (FD_ISSET(max_sd, &master_set) == FALSE)max_sd -= 1;}}} /* End of existing connection is readable */} /* End of if (FD_ISSET(i, &working_set)) */} /* End of loop through selectable descriptors */} while (end_server == FALSE);/*************************************************************//* Clean up all of the sockets that are open                 *//*************************************************************/for (i=0; i <= max_sd; ++i){if (FD_ISSET(i, &master_set))close(i);}
}

相关文章:

【计算机网络】网络编程接口 Socket API 解读(5)

Socket 是网络协议栈暴露给编程人员的 API&#xff0c;相比复杂的计算机网络协议&#xff0c;API 对关键操作和配置数据进行了抽象&#xff0c;简化了程序编程。 本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍&#xff0c;从而更好的理解 socket 编程。…...

手动实现一个bind函数!

原文地址&#xff1a;手动实现一个bind函数&#xff01; - 知乎 1.bind函数用法 bind()方法用于创建一个新的函数&#xff0c;这个新函数接收的第一个参数代表的就是this&#xff0c;利用bind()函数我就就可以任意改变函数内部的this指向了。 官网的解释&#xff1a; bind()…...

数据结构-时间复杂度/空间复杂度

Hello&#xff0c;好久没有更新了哦&#xff0c;已经开始学习数据结构了&#xff0c;这篇文章呢就是对刚学数据结构所接触到的时间复杂度进行一个分享哦&#xff0c;如果有错误之处&#xff0c;大家记得拍拍我哦~ 既然要讨论时间/空间复杂度&#xff0c;那我们就得知道时间/空…...

英语写作中“展示”、“表明”demonstrate、show、indicate、illustrate的用法

一、demonstrate、show、indicate在论文写作中主要用法是&#xff1a;demonstrate/show/indicate 从句&#xff1a; Sb./Sth. demonstrates/shows/indicates that ……从句中一般表达事实、观点和结论等。 例句&#xff1a; The authors demonstrated/showed/indicated that…...

Redis的java客户端

在Redis官网中提供了各种语言的客户端&#xff0c;地址&#xff1a;https://redis.io/resources/clients/ redis的java客户端 https://redis.io/resources/clients/#java 1.jedis使用 引入依赖 <dependency><groupId>redis.clients</groupId><artifac…...

Android环境配置笔记

文章目录 一、各环境文档二、参考 一、各环境文档 Gradle官方的兼容性文档&#xff1a;Java Compatibility 更新日期&#xff1a;2023.9.12 Android Gradle插件版本&#xff1a;Android Gradle Plugin 二、参考 参考文章&#xff1a;Android问题记录...

element-table 行的拖拽更改顺序(无需下载sortableJs

样例展示&#xff1a;vueelement 通过阅读element文档我们发现element并不提供拖拽相关的api 本博客通过element提供的行类名 注册函数 实现行与行的拖拽 1.设置el-table 的行样式类名 这里是用的是 function <el-table:data"outputData":row-class-name&qu…...

Docker部署jenkins

目录 一、jenkins原理二、Docker部署jenkins1.下载jenkins镜像文件2.查看下载的jenkins镜像3.创建Jenkins挂载目录并授权权限4.创建并启动Jenkins容器5.查看jenkins是否启动成功6.查看docker容器日志7.配置镜像加速8.访问Jenkins页面&#xff0c;输入ip地址加上9000端口9.获取管…...

从0到1学会Git(第三部分):Git的远程仓库链接与操作

写在前面:前面两篇文章我们已经学会了git如何在本地进行使用&#xff0c;这篇文章将讲解如何将本地的git仓库和云端的远程仓库链接起来并使用 为什么要使用远程仓库:因为我们需要拷贝我们的代码给别人以及进行协同开发&#xff0c;就需要有一个云端仓库进行代码的存储和同步&a…...

虚拟机Ubuntu操作系统常用终端命令(1)(详细解释+详细演示)

虚拟机Ubuntu操作系统常用终端命令 本篇讲述了Ubuntu操作系统常用的三个功能&#xff0c;即归档&#xff0c;软链接和用户管理方面的相关知识。希望能够得到大家的支持。 文章目录 虚拟机Ubuntu操作系统常用终端命令二、使用步骤1.归档1.1创建档案包1.2还原档案包1.3归档并压缩…...

redis实战-redis实现异步秒杀优化

秒杀优化-异步秒杀思路 未优化的思路 当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一…...

Python爬虫-IP隐藏技术与代理爬取

前言 在进行爬虫程序开发和运行时&#xff0c;常常会遇到目标网站的反爬虫机制&#xff0c;最常见的就是IP封禁&#xff0c;这时需要使用IP隐藏技术和代理爬取。 一、IP隐藏技术 IP隐藏技术&#xff0c;即伪装IP地址&#xff0c;使得爬虫请求的IP地址不被目标网站识别为爬虫。…...

二刷力扣--链表

链表 链表类型&#xff1a; 单链表&#xff08;可以访问后面的一个节点&#xff09; 双链表&#xff08;可以访问前后节点&#xff09; 循环链表&#xff08;最后一个节点指向首节点&#xff09; 在Python中定义单链表节点&#xff1a; class ListNode:def __init__(self, v…...

返回值加const ,为了不拷贝得到成员的值,但被赋值的左值也要const

1. getA 函数返回值 什么都不加&#xff0c;也改不了c里面a的指针指向 why&#xff1f;返回成员变量时&#xff0c;会复制一下。 返回成员变量时&#xff0c;一般会赋值一下没有RVO_地摊书贩的博客-CSDN博客 2. getA 函数返回值 加了引用&#xff0c; 就没有复制 3. getA 函数…...

本地如何使用HTTPS进行调试

在现代前端开发中&#xff0c;HTTPS已经成为不可或缺的一部分&#xff0c;因为它在保护用户数据和确保网站安全性方面发挥着关键作用。然而&#xff0c;有时在本地开发过程中启用HTTPS可能会变得有些复杂。在本文中&#xff0c;我们将介绍如何轻松地在本地进行HTTPS调试&#x…...

观察者模式:对象之间的订阅机制

欢迎来到设计模式系列的第十三篇文章&#xff01;在之前的文章中&#xff0c;我们学习了许多常用的设计模式&#xff0c;今天我们将介绍观察者模式&#xff0c;它是一种行为型设计模式&#xff0c;用于定义对象之间的一对多依赖关系&#xff0c;当一个对象的状态发生变化时&…...

【1462. 课程表 IV】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你想选 bi 课程&#xff0c;你…...

Kerberos 身份验证

简介 Kerberos 是一种由 MIT&#xff08;麻省理工大学&#xff09;提出的一种基于加密 Ticket 的身份认证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证&#xff0c;用于验证用户或主机的标识。。 适用范围&#xff1a;Windows Server 2022、Window…...

R语言贝叶斯METROPOLIS-HASTINGS GIBBS 吉布斯采样器估计变点指数分布分析泊松过程车站等待时间...

原文链接&#xff1a;http://tecdat.cn/?p26578 指数分布是泊松过程中事件之间时间的概率分布&#xff0c;因此它用于预测到下一个事件的等待时间&#xff0c;例如&#xff0c;您需要在公共汽车站等待的时间&#xff0c;直到下一班车到了&#xff08;点击文末“阅读原文”获取…...

通付盾入选2023年度“上市苗圃工程”重点企业

近日&#xff0c;2023年度苏州工业园区企业上市苗圃工程认定名单公示&#xff0c;江苏通付盾科技有限公司成功入选园区“上市苗圃工程”重点企业。 2023年第一批次苗圃企业认定结果&#xff1a; 企业上市苗圃工程 上市企业是衡量地方综合经济实力的重要标尺&#xff0c;也是区…...

SpringMVC之文件上传下载

SpringMVC是一个基于Java的Web框架&#xff0c;它提供了一套用于构建Web应用程序的开发模型。在SpringMVC中&#xff0c;文件上传和下载是常见的功能之一。 SpringMVC文件上传和下载的介绍&#xff1a; 介绍文件上传&#xff1a; 在SpringMVC中&#xff0c;文件上传功能可以通…...

嵌入式IDE(2):KEIL中SCF分散加载链接文件详解和实例分析

在上一篇文章IAR中ICF链接文件详解和实例分析中&#xff0c;我通过I.MX RT1170的SDK中的内存映射关系&#xff0c;分析了IAR中的ICF链接文件的语法。对于MCU编程所使用的IDE来说&#xff0c;IAR和Keil用得比较多&#xff0c;所以这一篇文章就来分析一下Keil的分散文件.scf(scat…...

Linux防火墙常用操作及端口开放

Linux防火墙常用操作及端口开放 1.查看防火墙状态 firewall-cmd --state 2.开启防火墙 systemctl start firewalld.service 3.开启指定端口 firewall-cmd --zonepublic --add-port3306/tcp --permanent firewall-cmd --zonepublic --add-port6379/tcp --permanent 显示success表…...

[JAVAee]Linux上的javax.mail报错

我们把在window写的项目部署到Linux上的Tomcat时,如果发现使用不了了,该如何找到错误呢?找到报错的地方在哪呢? 在Linux环境下来到Tomcat目录下的logs目录,输入: tail -f catalina.out -n 500 tail 就是把文件的末尾几行读取到终端上,并会持续刷新 -f 循环读取 catalina.ou…...

开学季|校园迎新哪家强?VR全景来导航

九月开学迎新季&#xff0c;各大高校的迎新活动开展的如火如荼&#xff0c;随着科技的不断进步&#xff0c;高校为了更好的开展迎新活动&#xff0c;让新生们尽快熟悉新的校园和生活&#xff0c;会利用VR全景技术带领着新生进行校园游览&#xff0c;给予新生们巨大便利的同时&a…...

el-checkbox-group限制勾选数量

<!--* Description: 视频监控 页面* Author: mhf* Date: 2023-08-15 13:26:33 --> <template><div class"videoSurveillance"><el-row :gutter"24"><el-col :span"4"><div class"videoSurveillance-left&…...

【JavaScript】WebAPI入门到实战

文章目录 一、WebAPI背景知识1. 什么是WebAPI&#xff1f;2. 什么是API&#xff1f; 二、DOM基本概念三、获取元素三、事件初识1. 点击事件2. 键盘事件 四、操作元素1. 获取/修改元素内容2. 获取/修改元素属性3. 获取/修改表单元素属性4. 获取/修改样式属性 五、操作节点1. 新增…...

奥康的高尔夫鞋,圈不住投资者的心

文 | 螳螂观察 作者 | 青月 鞋服行业终于熬过了“寒冬”&#xff0c;2023年行业景气度开始逐步回暖。 东方财富Choice数据显示&#xff0c;截至8月17日&#xff0c;已有28家鞋帽服装类上市公司发布了2023年中期业绩预告或快报&#xff0c;其中&#xff0c;9家预增&#xff0…...

vue2配置环境变量并且nginx运行成功

需求&#xff1a;我在vue项目配置了生产环境和开发环境&#xff0c;之后通过proxy代理的方式把地址转发到真实的服务器地址上用于请求接口&#xff0c;之后把项目打包后上传到nginx上&#xff0c;之后接口报错404&#xff0c;但是本地运行是可以访问的&#xff0c;找了很久终于…...

Java+Swing形成GUI图像界面

一、Swing 简介 Swing 主要用来开发 GUI 程序,GUI(Graphical User Interface)即图形用户界面。Java 中针对 GUI 设计提供了丰富的类库,这些类分别位于 java.awt 和 java.swing 中,简称 AWT 和 Swing ;其中,AWT(Abstract Window Toolkit)是抽象窗口工具包,是 Java 平…...

b2b平台如何赚钱/整站优化

##c ###算法一&#xff1a;vector做法 #include <bits/stdc.h> using namespace std;int main() {vector<int> arr;arr.reserve(10010); //将arr的容量&#xff08;capacity&#xff09;扩充至10010(放大到最大数据量)int num;cin >> num;for (int i 0; i …...

n127网推广/深圳seo优化培训

一、操作系统中线程和进程的概念 现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。 进程是指一个内存中运行的应用程序&#xff0c;每个进程都有自己独立的一块内存空间&#xff0c;一个进程中可以启动多个线程。比如在Windows系统中&#xff0c;一个运行的exe…...

建设银行交学费网站2018/seo推广优化公司哪家好

scrollLeft&#xff1a;获取匹配元素集合中第一个元素的滚动条的当前水平位置。如果滚动条位于最左边&#xff0c;或者元素不可滚动&#xff0c;则该值为0。当直接调用animate或使用动画属性时&#xff0c;如果元素不可见&#xff0c;scrollLeft将不起作用。可以接受一个参数用…...

公司做网站价格/如何做外贸网站的推广

百度智能云 云生态狂欢季 热门云产品1折起>>> 通用的&#xff23;基础库 Light Libraries 1.1.4 发布了。 新版本更新日志如下&#xff1a; 新增libposix4win&#xff0c;作为windows的基础库&#xff0c;支持visual studio 2010 nmake 原生编译 优化整理libthread…...

化妆品商城网站建设策划方案/网上推广方式

Beamit是一家专注于美国及发展中市场资金转移的移动支付创业公司&#xff0c;由TechStars所孵化&#xff0c;曾入选“西雅图十大最具潜质初创公司”。在之前的天使融资中获得75万美元投资&#xff0c;近日宣布获得新一轮240万美元的融资&#xff0c;由Founder’s Co-op领投&…...

怎么做彩票网站代理/百度知道灰色词代发收录

本节书摘来自异步社区《Adobe Illustrator CS5中文版经典教程》一书中的第0课0.13节使用“外观”面板和效果&#xff0c;作者【美】Adobe公司,更多章节内容可以访问云栖社区“异步社区”公众号查看。 0.13 使用“外观”面板和效果Adobe Illustrator CS5中文版经典教程“外观”面…...