Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
阿啄debugIT
libfastcommon
提交
28d17552
L
libfastcommon
项目概览
阿啄debugIT
/
libfastcommon
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
L
libfastcommon
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
28d17552
编写于
9月 26, 2019
作者:
Y
YuQing
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
sockopt.[hc] support IPv6
上级
82eee4dd
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
324 addition
and
74 deletion
+324
-74
HISTORY
HISTORY
+2
-1
src/sockopt.c
src/sockopt.c
+164
-63
src/sockopt.h
src/sockopt.h
+158
-10
未找到文件。
HISTORY
浏览文件 @
28d17552
Version 1.41 2019-09-2
2
Version 1.41 2019-09-2
7
* change CIDR network_bits range from [16, 32) to [10, 32)
* ini_file_reader.c: fix empty string compare
* multi_socket_client.c: code refine
* sockopt.[hc] support IPv6
Version 1.40 2018-11-09
* add function conn_pool_parse_server_info and conn_pool_load_server_info
...
...
src/sockopt.c
浏览文件 @
28d17552
...
...
@@ -462,35 +462,35 @@ int tcpsenddata_nb(int sock, void* data, const int size, const int timeout)
return
0
;
}
int
setsockaddrbyip
(
const
char
*
ip
,
const
short
port
,
struct
sockaddr_in
*
addr
,
struct
sockaddr_in6
*
addr6
,
void
**
output
,
int
*
size
)
int
setsockaddrbyip
(
const
char
*
ip
,
const
short
port
,
sockaddr_convert_t
*
convert
)
{
int
domain
;
int
af
;
void
*
dest
;
if
(
is_ipv6_addr
(
ip
))
{
*
output
=
addr6
;
*
size
=
sizeof
(
*
addr6
);
dest
=
&
addr6
->
sin6_addr
;
convert
->
len
=
sizeof
(
convert
->
sa
.
addr6
);
dest
=
&
convert
->
sa
.
addr6
.
sin6_addr
;
domain
=
AF_INET6
;
addr6
->
sin6_family
=
PF_INET6
;
addr6
->
sin6_port
=
htons
(
port
);
af
=
AF_INET6
;
convert
->
sa
.
addr6
.
sin6_family
=
PF_INET6
;
convert
->
sa
.
addr6
.
sin6_port
=
htons
(
port
);
}
else
//ipv4
{
*
output
=
addr
;
*
size
=
sizeof
(
*
addr
);
dest
=
&
addr
->
sin_addr
;
convert
->
len
=
sizeof
(
convert
->
sa
.
addr4
);
dest
=
&
convert
->
sa
.
addr4
.
sin_addr
;
domain
=
AF_INET
;
addr
->
sin_family
=
PF_INET
;
addr
->
sin_port
=
htons
(
port
);
af
=
AF_INET
;
convert
->
sa
.
addr4
.
sin_family
=
PF_INET
;
convert
->
sa
.
addr4
.
sin_port
=
htons
(
port
);
}
if
(
inet_pton
(
domain
,
ip
,
dest
)
==
0
)
if
(
inet_pton
(
af
,
ip
,
dest
)
==
0
)
{
logError
(
"file: "
__FILE__
", line: %d, "
"invalid %s ip address: %s"
,
__LINE__
,
(
af
==
AF_INET
?
"IPv4"
:
"IPv6"
),
ip
);
return
EINVAL
;
}
return
0
;
...
...
@@ -499,18 +499,14 @@ int setsockaddrbyip(const char *ip, const short port, struct sockaddr_in *addr,
int
connectserverbyip
(
int
sock
,
const
char
*
server_ip
,
const
short
server_port
)
{
int
result
;
struct
sockaddr_in
addr
;
struct
sockaddr_in6
addr6
;
void
*
dest
;
int
size
;
sockaddr_convert_t
convert
;
if
((
result
=
setsockaddrbyip
(
server_ip
,
server_port
,
&
addr
,
&
addr6
,
&
dest
,
&
size
))
!=
0
)
if
((
result
=
setsockaddrbyip
(
server_ip
,
server_port
,
&
convert
))
!=
0
)
{
return
result
;
}
if
(
connect
(
sock
,
(
const
struct
sockaddr
*
)
dest
,
size
)
<
0
)
if
(
connect
(
sock
,
&
convert
.
sa
.
addr
,
convert
.
len
)
<
0
)
{
return
errno
!=
0
?
errno
:
EINTR
;
}
...
...
@@ -535,13 +531,9 @@ int connectserverbyip_nb_ex(int sock, const char *server_ip, \
struct
pollfd
pollfds
;
#endif
struct
sockaddr_in
addr
;
struct
sockaddr_in6
addr6
;
void
*
dest
;
int
size
;
sockaddr_convert_t
convert
;
if
((
result
=
setsockaddrbyip
(
server_ip
,
server_port
,
&
addr
,
&
addr6
,
&
dest
,
&
size
))
!=
0
)
if
((
result
=
setsockaddrbyip
(
server_ip
,
server_port
,
&
convert
))
!=
0
)
{
return
result
;
}
...
...
@@ -576,7 +568,7 @@ int connectserverbyip_nb_ex(int sock, const char *server_ip, \
do
{
if
(
connect
(
sock
,
(
const
struct
sockaddr
*
)
dest
,
size
)
<
0
)
if
(
connect
(
sock
,
&
convert
.
sa
.
addr
,
convert
.
len
)
<
0
)
{
result
=
errno
!=
0
?
errno
:
EINPROGRESS
;
if
(
result
!=
EINPROGRESS
)
...
...
@@ -634,16 +626,99 @@ int connectserverbyip_nb_ex(int sock, const char *server_ip, \
return
result
;
}
int
socketClientEx2
(
int
af
,
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
const
char
*
bind_ipaddr
,
int
*
err_no
)
{
int
sock
;
bool
auto_detect
;
if
(
af
==
AF_UNSPEC
)
{
af
=
is_ipv6_addr
(
server_ip
)
?
AF_INET6
:
AF_INET
;
}
sock
=
socket
(
af
,
SOCK_STREAM
,
0
);
if
(
sock
<
0
)
{
*
err_no
=
errno
!=
0
?
errno
:
EMFILE
;
logError
(
"file: "
__FILE__
", line: %d, "
\
"socket create failed, errno: %d, error info: %s"
,
\
__LINE__
,
errno
,
STRERROR
(
errno
));
return
-
1
;
}
if
(
flags
!=
0
)
{
*
err_no
=
fd_add_flags
(
sock
,
flags
);
if
(
*
err_no
!=
0
)
{
close
(
sock
);
return
-
2
;
}
}
if
(
bind_ipaddr
!=
NULL
&&
*
bind_ipaddr
!=
'\0'
)
{
*
err_no
=
socketBind2
(
af
,
sock
,
bind_ipaddr
,
0
);
if
(
*
err_no
!=
0
)
{
close
(
sock
);
return
-
3
;
}
}
auto_detect
=
((
flags
&
O_NONBLOCK
)
==
0
);
*
err_no
=
connectserverbyip_nb_ex
(
sock
,
server_ip
,
server_port
,
timeout
,
auto_detect
);
if
(
*
err_no
!=
0
)
{
close
(
sock
);
return
-
4
;
}
return
sock
;
}
const
char
*
fc_inet_ntop
(
const
struct
sockaddr
*
addr
,
char
*
buff
,
const
int
bufferSize
)
{
void
*
sin_addr
;
const
char
*
output
;
if
(
addr
->
sa_family
==
AF_INET
)
{
sin_addr
=
&
((
struct
sockaddr_in
*
)
addr
)
->
sin_addr
;
}
else
if
(
addr
->
sa_family
==
AF_INET6
)
{
sin_addr
=
&
((
struct
sockaddr_in6
*
)
addr
)
->
sin6_addr
;
}
else
{
*
buff
=
'\0'
;
logWarning
(
"file: "
__FILE__
", line: %d, "
"unkown family: %d"
,
__LINE__
,
addr
->
sa_family
);
return
NULL
;
}
if
((
output
=
inet_ntop
(
addr
->
sa_family
,
sin_addr
,
buff
,
bufferSize
))
==
NULL
)
{
*
buff
=
'\0'
;
logWarning
(
"file: "
__FILE__
", line: %d, "
"call inet_ntop fail, "
"errno: %d, error info: %s"
,
__LINE__
,
errno
,
STRERROR
(
errno
));
}
return
output
;
}
in_addr_t
getIpaddr
(
getnamefunc
getname
,
int
sock
,
\
char
*
buff
,
const
int
bufferSize
)
{
struct
sockaddr
_in
addr
;
struct
sockaddr
addr
;
socklen_t
addrlen
;
memset
(
&
addr
,
0
,
sizeof
(
addr
));
addrlen
=
sizeof
(
addr
);
if
(
getname
(
sock
,
(
struct
sockaddr
*
)
&
addr
,
&
addrlen
)
!=
0
)
if
(
getname
(
sock
,
&
addr
,
&
addrlen
)
!=
0
)
{
*
buff
=
'\0'
;
return
INADDR_NONE
;
...
...
@@ -651,31 +726,29 @@ in_addr_t getIpaddr(getnamefunc getname, int sock, \
if
(
addrlen
>
0
)
{
if
(
inet_ntop
(
AF_INET
,
&
addr
.
sin_addr
,
buff
,
bufferSize
)
==
NULL
)
{
*
buff
=
'\0'
;
}
fc_inet_ntop
(
&
addr
,
buff
,
bufferSize
);
}
else
{
*
buff
=
'\0'
;
}
return
addr
.
sin_addr
.
s_addr
;
return
((
struct
sockaddr_in
*
)
&
addr
)
->
sin_addr
.
s_addr
;
//DO NOT support IPv6
}
char
*
getHostnameByIp
(
const
char
*
szIpAddr
,
char
*
buff
,
const
int
bufferSize
)
{
struct
in_addr
ip_addr
;
struct
hostent
*
ent
;
sockaddr_convert_t
convert
;
if
(
inet_pton
(
AF_INET
,
szIpAddr
,
&
ip_addr
)
!=
1
)
{
if
(
setsockaddrbyip
(
szIpAddr
,
0
,
&
convert
)
!=
0
)
{
*
buff
=
'\0'
;
return
buff
;
}
}
ent
=
gethostbyaddr
((
char
*
)
&
ip_addr
,
sizeof
(
ip_addr
),
AF_INET
);
ent
=
gethostbyaddr
(
&
convert
.
sa
.
addr
,
convert
.
len
,
convert
.
sa
.
addr
.
sa_family
);
if
(
ent
==
NULL
||
ent
->
h_name
==
NULL
)
{
*
buff
=
'\0'
;
...
...
@@ -839,32 +912,40 @@ int nbaccept(int sock, const int timeout, int *err_no)
return
result
;
}
int
socketBind
(
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
)
int
socketBind
2
(
int
af
,
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
)
{
struct
sockaddr_in
bindaddr
;
sockaddr_convert_t
convert
;
int
result
;
bindaddr
.
sin_family
=
AF_INET
;
bindaddr
.
sin_port
=
htons
(
port
);
convert
.
sa
.
addr
.
sa_family
=
af
;
if
(
bind_ipaddr
==
NULL
||
*
bind_ipaddr
==
'\0'
)
{
bindaddr
.
sin_addr
.
s_addr
=
INADDR_ANY
;
if
(
af
==
AF_INET
)
{
convert
.
len
=
sizeof
(
convert
.
sa
.
addr4
);
convert
.
sa
.
addr4
.
sin_port
=
htons
(
port
);
convert
.
sa
.
addr4
.
sin_addr
.
s_addr
=
INADDR_ANY
;
}
else
{
convert
.
len
=
sizeof
(
convert
.
sa
.
addr6
);
convert
.
sa
.
addr6
.
sin6_port
=
htons
(
port
);
convert
.
sa
.
addr6
.
sin6_addr
=
in6addr_any
;
}
}
else
{
if
(
inet_pton
(
AF_INET
,
bind_ipaddr
,
&
bindaddr
.
sin_addr
)
==
0
)
{
logError
(
"file: "
__FILE__
", line: %d, "
\
"invalid ip addr %s"
,
\
__LINE__
,
bind_ipaddr
);
return
EINVAL
;
}
}
{
if
((
result
=
setsockaddrbyip
(
bind_ipaddr
,
port
,
&
convert
))
!=
0
)
{
return
result
;
}
}
if
(
bind
(
sock
,
(
struct
sockaddr
*
)
&
bindaddr
,
sizeof
(
bindaddr
)
)
<
0
)
if
(
bind
(
sock
,
&
convert
.
sa
.
addr
,
convert
.
len
)
<
0
)
{
logError
(
"file: "
__FILE__
", line: %d, "
\
"bind port %d failed, "
\
"errno: %d, error info: %s."
,
\
logError
(
"file: "
__FILE__
", line: %d, "
"bind port %d failed, "
"errno: %d, error info: %s."
,
__LINE__
,
port
,
errno
,
STRERROR
(
errno
));
return
errno
!=
0
?
errno
:
ENOMEM
;
}
...
...
@@ -872,12 +953,22 @@ int socketBind(int sock, const char *bind_ipaddr, const int port)
return
0
;
}
int
socketServer
(
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
)
int
socketBind
(
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
)
{
return
socketBind2
(
AF_INET
,
sock
,
bind_ipaddr
,
port
);
}
int
socketBindIPv6
(
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
)
{
return
socketBind2
(
AF_INET6
,
sock
,
bind_ipaddr
,
port
);
}
int
socketServer2
(
int
af
,
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
)
{
int
sock
;
int
result
;
sock
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
sock
=
socket
(
af
,
SOCK_STREAM
,
0
);
if
(
sock
<
0
)
{
*
err_no
=
errno
!=
0
?
errno
:
EMFILE
;
...
...
@@ -900,7 +991,7 @@ int socketServer(const char *bind_ipaddr, const int port, int *err_no)
return
-
2
;
}
if
((
*
err_no
=
socketBind
(
sock
,
bind_ipaddr
,
port
))
!=
0
)
if
((
*
err_no
=
socketBind
2
(
af
,
sock
,
bind_ipaddr
,
port
))
!=
0
)
{
close
(
sock
);
return
-
3
;
...
...
@@ -921,6 +1012,16 @@ int socketServer(const char *bind_ipaddr, const int port, int *err_no)
return
sock
;
}
int
socketServer
(
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
)
{
return
socketServer2
(
AF_INET
,
bind_ipaddr
,
port
,
err_no
);
}
int
socketServerIPv6
(
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
)
{
return
socketServer2
(
AF_INET6
,
bind_ipaddr
,
port
,
err_no
);
}
int
tcprecvfile
(
int
sock
,
const
char
*
filename
,
const
int64_t
file_bytes
,
\
const
int
fsync_after_written_bytes
,
const
int
timeout
,
\
int64_t
*
true_file_bytes
)
...
...
src/sockopt.h
浏览文件 @
28d17552
...
...
@@ -32,6 +32,15 @@ typedef struct ip_addr_s {
int
socket_domain
;
}
ip_addr_t
;
typedef
struct
sockaddr_convert_s
{
socklen_t
len
;
union
{
struct
sockaddr
addr
;
struct
sockaddr_in
addr4
;
struct
sockaddr_in6
addr6
;
}
sa
;
}
sockaddr_convert_t
;
#ifdef SO_NOSIGPIPE
#define SET_SOCKOPT_NOSIGPIPE(sock) \
do { \
...
...
@@ -238,7 +247,7 @@ in_addr_t getIpaddr(getnamefunc getname, int sock, \
*/
char
*
getHostnameByIp
(
const
char
*
szIpAddr
,
char
*
buff
,
const
int
bufferSize
);
/** get by
ip
address by it's hostname
/** get by
IPv4
address by it's hostname
* parameters:
* name: the hostname
* buff: buffer to store the ip address
...
...
@@ -256,7 +265,7 @@ in_addr_t getIpaddrByName(const char *name, char *buff, const int bufferSize);
*/
int
getIpaddrsByName
(
const
char
*
name
,
ip_addr_t
*
ip_addr_arr
,
const
int
ip_addr_arr_size
);
/** bind wrapper
/** bind wrapper
for IPv4
* parameters:
* sock: the socket
* bind_ipaddr: the ip address to bind
...
...
@@ -265,7 +274,26 @@ int getIpaddrsByName(const char *name, ip_addr_t *ip_addr_arr, const int ip_addr
*/
int
socketBind
(
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
);
/** start a socket server (socket, bind and listen)
/** bind wrapper for IPv6
* parameters:
* sock: the socket
* bind_ipaddr: the ip address to bind
* port: the port to bind
* return: error no, 0 success, != 0 fail
*/
int
socketBindIPv6
(
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
);
/** bind wrapper for IPv4 or IPv6
* parameters:
* af: family, AF_INET or AF_INET6
* sock: the socket
* bind_ipaddr: the ip address to bind
* port: the port to bind
* return: error no, 0 success, != 0 fail
*/
int
socketBind2
(
int
af
,
int
sock
,
const
char
*
bind_ipaddr
,
const
int
port
);
/** start a socket server for IPv4 (socket, bind and listen)
* parameters:
* sock: the socket
* bind_ipaddr: the ip address to bind
...
...
@@ -275,6 +303,130 @@ int socketBind(int sock, const char *bind_ipaddr, const int port);
*/
int
socketServer
(
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
);
/** start a socket server for IPv6 (socket, bind and listen)
* parameters:
* sock: the socket
* bind_ipaddr: the ip address to bind
* port: the port to bind
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
int
socketServerIPv6
(
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
);
/** start a socket server for IPv4 or IPv6 (socket, bind and listen)
* parameters:
* af: family, AF_INET or AF_INET6
* sock: the socket
* bind_ipaddr: the ip address to bind
* port: the port to bind
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
int
socketServer2
(
int
af
,
const
char
*
bind_ipaddr
,
const
int
port
,
int
*
err_no
);
/** connect to server
* parameters:
* af: family, AF_UNSPEC (auto dectect), AF_INET or AF_INET6
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* bind_ipaddr: the ip address to bind, NULL or empty for bind ANY
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
int
socketClientEx2
(
int
af
,
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
const
char
*
bind_ipaddr
,
int
*
err_no
);
/** connect to server
* parameters:
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* bind_ipaddr: the ip address to bind, NULL or empty for bind ANY
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
static
inline
int
socketClientExAuto
(
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
const
char
*
bind_ipaddr
,
int
*
err_no
)
{
return
socketClientEx2
(
AF_UNSPEC
,
server_ip
,
server_port
,
timeout
,
flags
,
bind_ipaddr
,
err_no
);
}
/** connect to server
* parameters:
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* bind_ipaddr: the ip address to bind, NULL or empty for bind ANY
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
static
inline
int
socketClientAuto
(
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
int
*
err_no
)
{
return
socketClientEx2
(
AF_UNSPEC
,
server_ip
,
server_port
,
timeout
,
flags
,
NULL
,
err_no
);
}
/** connect to server
* parameters:
* af: family, AF_UNSPEC (auto dectect), AF_INET or AF_INET6
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
static
inline
int
socketClient2
(
int
af
,
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
int
*
err_no
)
{
return
socketClientEx2
(
af
,
server_ip
,
server_port
,
timeout
,
flags
,
NULL
,
err_no
);
}
/** connect to server with IPv4 socket
* parameters:
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
static
inline
int
socketClient
(
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
int
*
err_no
)
{
return
socketClient2
(
AF_INET
,
server_ip
,
server_port
,
timeout
,
flags
,
err_no
);
}
/** connect to server with IPv6 socket
* parameters:
* server_ip: ip address of the server
* server_port: port of the server
* timeout: connect timeout in seconds
* flags: socket flags such as O_NONBLOCK for non-block socket
* err_no: store the error no
* return: >= 0 server socket, < 0 fail
*/
static
inline
int
socketClientIPv6
(
const
char
*
server_ip
,
const
short
server_port
,
const
int
timeout
,
const
int
flags
,
int
*
err_no
)
{
return
socketClient2
(
AF_INET6
,
server_ip
,
server_port
,
timeout
,
flags
,
err_no
);
}
#define tcprecvdata(sock, data, size, timeout) \
tcprecvdata_ex(sock, data, size, timeout, NULL)
...
...
@@ -374,18 +526,14 @@ int gethostaddrs(char **if_alias_prefixes, const int prefix_count, \
*/
int
getifconfigs
(
FastIFConfig
*
if_configs
,
const
int
max_count
,
int
*
count
);
/** set socket address by ip
/** set socket address by ip
and port
* parameters:
* ip: the ip address
* port: the port
* addr: ipv4 addr
* addr6: ipv6 addr
* output: return addr pointer
* size: return the size of addr
* convert: the convert struct for IPv4 and IPv6 compatibility
* return: error no, 0 success, != 0 fail
*/
int
setsockaddrbyip
(
const
char
*
ip
,
const
short
port
,
struct
sockaddr_in
*
addr
,
struct
sockaddr_in6
*
addr6
,
void
**
output
,
int
*
size
);
int
setsockaddrbyip
(
const
char
*
ip
,
const
short
port
,
sockaddr_convert_t
*
convert
);
static
inline
bool
is_ipv6_addr
(
const
char
*
ip
)
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录