我们都知道,TCP是个面向连接的、可靠的、基于字节流的传输层通信协议。
图片
TCP是什么
那这里面提到的"面向连接",意味着需要 建立连接,使用连接,释放连接。
建立连接是指我们熟知的TCP三次握手。
而使用连接,则是通过一发送、一确认的形式,进行数据传输。
还有就是释放连接,也就是我们常见的TCP四次挥手。
TCP四次挥手大家应该比较了解了,但大家见过三次挥手吗?还有两次挥手呢?
都见过?那四次握手呢?
今天这个话题,不想只是猎奇,也不想搞冷知识。
我们从四次挥手开始说起,搞点实用的知识点。
简单回顾下TCP四次挥手。
图片
TCP四次挥手
正常情况下。只要数据传输完了,不管是客户端还是服务端,都可以主动发起四次挥手,释放连接。
就跟上图画的一样,假设,这次四次挥手是由客户端主动发起的,那它就是主动方。服务器是被动接收客户端的挥手请求的,叫被动方。
客户端和服务器,一开始,都是处于ESTABLISHED
状态。
第一次挥手:一般情况下,主动方执行close()
或 shutdown()
方法,会发个FIN报文
出来,表示"我不再发送数据了"。
第二次挥手:在收到主动方的FIN
报文后,被动方立马回应一个ACK
,意思是"我收到你的FIN了,也知道你不再发数据了"。
上面提到的是主动方不再发送数据了。但如果这时候,被动方还有数据要发,那就继续发。注意,虽然第二次和第三次挥手之间,被动方是能发数据到主动方的,但主动方能不能正常收就不一定了,这个待会说。
第三次挥手:在被动方在感知到第二次挥手之后,会做了一系列的收尾工作,最后也调用一个 close()
, 这时候就会发出第三次挥手的 FIN-ACK
。
第四次挥手:主动方回一个ACK
,意思是收到了。
其中第一次挥手和第三次挥手,都是我们在应用程序中主动触发的(比如调用close()
方法),也就是我们平时写代码需要关注的地方。
第二和第四次挥手,都是内核协议栈自动帮我们完成的,我们写代码的时候碰不到这地方,因此也不需要太关心。
另外不管是主动还是被动,每方发出了一个 FIN
和一个ACK
。也收到了一个 FIN
和一个ACK
。这一点大家关注下,待会还会提到。
不一定。一般情况下,通过对socket
执行 close()
或 shutdown()
方法会发出FIN
。但实际上,只要应用程序退出,不管是主动退出,还是被动退出(因为一些莫名其妙的原因被kill
了), 都会发出 FIN
。
FIN 是指"我不再发送数据",因此
shutdown()
关闭读不会给对方发FIN, 关闭写才会发FIN。
根据上面的四次挥手图,可以看出,FIN-WAIT-2
是主动方那边的状态。
处于这个状态的程序,一直在等第三次挥手的FIN
。而第三次挥手需要由被动方在代码里执行close()
发出。
因此当机器上FIN-WAIT-2
状态特别多,那一般来说,另外一台机器上会有大量的 CLOSE_WAIT
。需要检查有大量的 CLOSE_WAIT
的那台机器,为什么迟迟不愿调用close()
关闭连接。
所以,如果机器上FIN-WAIT-2
状态特别多,一般是因为对端一直不执行close()
方法发出第三次挥手。
图片
FIN-WAIT-2特别多的原因
之前写的一篇文章《代码执行send成功后,数据就发出去了吗?》中,从源码的角度提到了,一般情况下,程序主动执行close()
的时候;
•如果当前连接对应的socket
的接收缓冲区有数据,会发RST
。•如果发送缓冲区有数据,那会等待发送完,再发第一次挥手的FIN
。
大家知道,TCP是全双工通信,意思是发送数据的同时,还可以接收数据。
Close()
的含义是,此时要同时关闭发送和接收消息的功能。
也就是说,虽然理论上,第二次和第三次挥手之间,被动方是可以传数据给主动方的。
但如果 主动方的四次挥手是通过 close()
触发的,那主动方是不会去收这个消息的。而且还会回一个 RST
。直接结束掉这次连接。
图片
触发TCP四次挥手")
close()触发TCP四次挥手
也不是。前面提到Close()
的含义是,要同时关闭发送和接收消息的功能。
那如果能做到只关闭发送消息,不关闭接收消息的功能,那就能继续收消息了。这种 half-close
的功能,通过调用shutdown()
方法就能做到。
int shutdown(int sock, int howto);
其中 howto 为断开方式。有以下取值:
•SHUT_RD:关闭读。这时应用层不应该再尝试接收数据,内核协议栈中就算接收缓冲区收到数据也会被丢弃。 •SHUT_WR:关闭写。如果发送缓冲区中还有数据没发,会将将数据传递到目标主机。 •SHUT_RDWR:关闭读和写。相当于
close()
了。
图片
shutdown触发的TCP四次挥手
不管主动关闭方调用的是close()
还是shutdown()
,对于被动方来说,收到的就只有一个FIN
。
被动关闭方就懵了,"我怎么知道对方让不让我继续发数据?" 其实,大可不必纠结,该发就发。
第二次挥手和第三次挥手之间,如果被动关闭方想发数据,那么在代码层面上,就是执行了 send()
方法。
int send( SOCKET s,const char* buf,int len,int flags);
send()
会把数据拷贝到本机的发送缓冲区。如果发送缓冲区没出问题,都能拷贝进去,所以正常情况下,send()
一般都会返回成功。
![tcp_sendmsg 逻辑](https://cdn.jsdelivr.net/gh/xiaobaiTech/image/tcp\_sendmsg 逻辑.png)
然后被动方内核协议栈会把数据发给主动关闭方。
•如果上一次主动关闭方调用的是shutdown(socket_fd, SHUT_WR)
。那此时,主动关闭方不再发送消息,但能接收被动方的消息,一切如常,皆大欢喜。
•如果上一次主动关闭方调用的是close()
。那主动方在收到被动方的数据后会直接丢弃,然后回一个RST
。
针对第二种情况。
被动方内核协议栈收到了RST
,会把连接关闭。但内核连接关闭了,应用层也不知道(除非被通知)。
此时被动方应用层接下来的操作,无非就是读或写。
•如果是读,则会返回RST
的报错,也就是我们常见的Connection reset by peer
。
•如果是写,那么程序会产生SIGPIPE
信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。
总结一下,当被动关闭方 recv()
返回EOF
时,说明主动方通过 close()
或 shutdown(fd, SHUT_WR)
发起了第一次挥手。
如果此时被动方执行两次send()
。
•第一次send()
, 一般会成功返回。
•第二次send()
时。如果主动方是通过 shutdown(fd, SHUT_WR)
发起的第一次挥手,那此时send()
还是会成功。如果主动方通过 close()
发起的第一次挥手,那此时会产生SIGPIPE
信号,进程默认会终止,异常退出。不想异常退出的话,记得捕获处理这个信号。
第三次挥手,是由被动方主动触发的,比如调用close()
。
如果由于代码错误或者其他一些原因,被动方就是不执行第三次挥手。
这时候,主动方会根据自身第一次挥手的时候用的是 close()
还是 shutdown(fd, SHUT_WR)
,有不同的行为表现。
•如果是 shutdown(fd, SHUT_WR)
,说明主动方其实只关闭了写,但还可以读,此时会一直处于 FIN-WAIT-2
, 死等被动方的第三次挥手。
•如果是 close()
, 说明主动方读写都关闭了,这时候会处于 FIN-WAIT-2
一段时间,这个时间由 net.ipv4.tcp_fin_timeout
控制,一般是 60s
,这个值正好跟2MSL
一样 。超过这段时间之后,状态不会变成 TIME-WAIT
,而是直接变成CLOSED
。
# cat /proc/sys/net/ipv4/tcp_fin_timeout
60
图片
一直不发第三次挥手的情况
四次挥手聊完了,那有没有可能出现三次挥手?
是可能的。
我们知道,TCP四次挥手里,第二次和第三次挥手之间,是有可能有数据传输的。第三次挥手的目的是为了告诉主动方,"被动方没有数据要发了"。
所以,在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的。这样就出现了三次挥手。
图片
TCP三次挥手
上面提到的是没有数据要发的情况,如果第二、第三次挥手之间有数据要发,就不可能变成三次挥手了吗?
并不是。TCP中还有个特性叫延迟确认。可以简单理解为:接收方收到数据以后不需要立刻马上回复ACK确认包。
在此基础上,不是每一次发送数据包都能对应收到一个 ACK
确认包,因为接收方可以合并确认。
而这个合并确认,放在四次挥手里,可以把第二次挥手、第三次挥手,以及他们之间的数据传输都合并在一起发送。因此也就出现了三次挥手。
图片
TCP三次挥手延迟确认
前面在四次挥手中提到,关闭的时候双方都发出了一个FIN和收到了一个ACK。
正常情况下TCP连接的两端,是不同IP+端口的进程。
但如果TCP连接的两端,IP+端口是一样的情况下,那么在关闭连接的时候,也同样做到了一端发出了一个FIN,也收到了一个 ACK,只不过正好这两端其实是同一个socket
。
图片
TCP两次挥手
而这种两端IP+端口都一样的连接,叫TCP自连接。
是的,你没看错,我也没打错别字。同一个socket确实可以自己连自己,形成一个连接。
上面提到了,同一个客户端socket,自己对自己发起连接请求。是可以成功建立连接的。这样的连接,叫TCP自连接。
下面我们尝试下复现。
注意我是在以下系统进行的实验。在mac
上多半无法复现。
# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
通过nc
命令可以很简单的创建一个TCP自连接
# nc -p 6666 127.0.0.1 6666
上面的 -p
可以指定源端口号。也就是指定了一个端口号为6666
的客户端去连接 127.0.0.1:6666
。
# netstat -nt | grep 6666
tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
整个过程中,都没有服务端参与。可以抓个包看下。
图片
image-20210810093309117
可以看到,相同的socket,自己连自己的时候,握手是三次的。挥手是两次的。
图片
TCP自连接
上面这张图里,左右都是同一个客户端,把它画成两个是为了方便大家理解状态的迁移。
我们可以拿自连接的握手状态对比下正常情况下的TCP三次握手。
图片
正常情况下的TCP三次握手
看了自连接的状态图,再看看下面几个问题。
第一次握手过后,连接状态就变成了SYN_SENT
状态。如果此时又收到了第一次握手的SYN包,那么连接状态就会从SYN_SENT
状态变成SYN_RCVD
。
// net/ipv4/tcp_input.c
static int tcp_rcv_synsent_state_process()
{
// SYN_SENT状态下,收到SYN包
if (th->syn) {
// 状态置为 SYN_RCVD
tcp_set_state(sk, TCP_SYN_RECV);
}
}
第二握手过后,连接状态就变为SYN_RCVD
了,此时如果再收到第二次握手的SYN+ACK
包。连接状态会变为ESTABLISHED
。
// net/ipv4/tcp_input.c
int tcp_rcv_state_process()
{
// 前面省略很多逻辑,能走到这就认为肯定有ACK
if (true) {
// 判断下这个ack是否合法
int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) > 0;
switch (sk->sk_state) {
case TCP_SYN_RECV:
if (acceptable) {
// 状态从 SYN_RCVD 转为 ESTABLISHED
tcp_set_state(sk, TCP_ESTABLISHED);
}
}
}
}
第一次挥手过后,一端状态就会变成 FIN-WAIT-1
。正常情况下,是要等待第二次挥手的ACK
。但实际上却等来了 一个第一次挥手的 FIN
包, 这时候连接状态就会变为CLOSING
。
// net/
static void tcp_fin(struct sock *sk)
{
switch (sk->sk_state) {
case TCP_FIN_WAIT1:
tcp_send_ack(sk);
// FIN-WAIT-1状态下,收到了FIN,转为 CLOSING
tcp_set_state(sk, TCP_CLOSING);
break;
}
}
这可以说是隐藏剧情了。
CLOSING
很少见,除了出现在自连接关闭外,一般还会出现在TCP两端同时关闭连接的情况下。
处于CLOSING
状态下时,只要再收到一个ACK
,就能进入 TIME-WAIT
状态,然后等个2MSL
,连接就彻底断开了。这跟正常的四次挥手还是有些差别的。大家可以滑到文章开头的TCP四次挥手再对比下。
可能大家会产生怀疑,这是不是nc
这个软件本身的bug
。
那我们可以尝试下用strace
看看它内部都做了啥。
# strace nc -p 6666 127.0.0.1 6666
// ...
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
// ...
无非就是以创建了一个客户端socket
句柄,然后对这个句柄执行 bind
, 绑定它的端口号是6666
,然后再向 127.0.0.1:6666
发起connect
方法。
我们可以尝试用C语言
去复现一遍。
下面的代码,只用于复现问题。直接跳过也完全不影响阅读。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
int main()
{
int lfd, cfd;
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
char buf[BUFSIZ];
int n = 0, i = 0, ret = 0 ;
printf("This is a client \n");
/*Step 1: 创建客户端端socket描述符cfd*/
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket error");
exit(1);
}
int flag=1,len=sizeof(int);
if( setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1)
{
perror("setsockopt");
exit(1);
}
bzero(&clie_addr, sizeof(clie_addr));
clie_addr.sin_family = AF_INET;
clie_addr.sin_port = htons(6666);
inet_pton(AF_INET,"127.0.0.1", &clie_addr.sin_addr.s_addr);
/*Step 2: 客户端使用bind绑定客户端的IP和端口*/
ret = bind(cfd, (struct sockaddr* )&clie_addr, sizeof(clie_addr));
if(ret != 0)
{
perror("bind error");
exit(2);
}
/*Step 3: connect链接服务器端的IP和端口号*/
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
inet_pton(AF_INET,"127.0.0.1", &serv_addr.sin_addr.s_addr);
ret = connect(cfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr));
if(ret != 0)
{
perror("connect error");
exit(3);
}
/*Step 4: 向服务器端写数据*/
while(1)
{
fgets(buf, sizeof(buf), stdin);
write(cfd, buf, strlen(buf));
n = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);//写到屏幕上
}
/*Step 5: 关闭socket描述符*/
close(cfd);
return 0;
}
保存为 client.c
文件,然后执行下面命令,会发现连接成功。
# gcc client.c -o client && ./client
This is a client
tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
说明,这不是nc的bug。事实上,这也是内核允许的一种情况。
#### 自连接的解决方案
自连接一般不太常见,但遇到了也不难解决。
解决方案比较简单,只要能保证客户端和服务端的端口不一致就行。
事实上,我们写代码的时候一般不会去指定客户端的端口,系统会随机给客户端分配某个范围内的端口。而这个范围,可以通过下面的命令进行查询
32768 60999
也就是只要我们的服务器端口不在`32768-60999`这个范围内,比如设置为`8888`。就可以规避掉这个问题。
另外一个解决方案,可以参考`golang`标准网络库的实现,在连接建立完成之后判断下IP和端口是否一致,如果遇到自连接,则断开重试。
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { // 如果是自连接,这里会重试 for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ { if err == nil { fd.Close() } fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) } // ... }
func selfConnect(fd *netFD, err error) bool { // 判断是否端口、IP一致 return l.Port == r.Port && l.IP.Equal(r.IP) }
四次握手
----
前面提到的`TCP`自连接是一个客户端自己连自己的场景。那不同客户端之间是否可以互联?
答案是**可以的**,有一种情况叫**TCP同时打开**。
![图片](https://mmbiz.qpic.cn/mmbiz_png/AnAgeMhDIian6HJics3mtJEayjn3gtKefbXlteXPkmIriaCVRXxmmVDHCB1DZLpMQllesqrWPMMMxtPOOd4WgdkWw/640?wx_fmt=png "TCP同时打开")
TCP同时打开
大家可以对比下,**TCP同时打开**在握手时的状态变化,跟TCP自连接是非常的像。
比如`SYN_SENT`状态下,又收到了一个`SYN`,其实就相当于自连接里,在发出了第一次握手后,又收到了第一次握手的请求。结果都是变成 `SYN_RCVD`。
在 `SYN_RCVD` 状态下收到了 `SYN+ACK`,就相当于自连接里,在发出第二次握手后,又收到第二次握手的请求,结果都是变成 `ESTABLISHED`。**他们的源码其实都是同一块逻辑。**
#### 复现TCP同时打开
分别在**两个控制台**下,分别执行下面两行命令。
while true; do nc -p 2224 127.0.0.1 2223 -v;done
while true; do nc -p 2223 127.0.0.1 2224 -v;done
上面两个命令的含义也比较简单,两个客户端互相请求连接对方的端口号,如果失败了则不停重试。
执行后看到的现象是,一开始会疯狂失败,重试。一段时间后,连接建立完成。
Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:2224 127.0.0.1:2223 ESTABLISHED tcp 0 0 127.0.0.1:2223 127.0.0.1:2224 ESTABLISHED
期间抓包获得下面的结果。
图片
可以看到,这里面建立连接用了四次交互。因此可以说这是通过**"四次握手"**建立的连接。
而且更重要的是,这里面只涉及两个客户端,没有服务端。
看到这里,不知道大家有没有跟我一样,被刷新了一波认知,对socket
有了重新的认识。
在以前的观念里,建立连接,必须要有一个客户端和一个服务端,并且服务端还要执行一个listen()
和一个accept()
。而实际上,这些都不是必须的。
那么下次,面试官问你**"没有listen()
, TCP能建立连接吗?"**, 我想大家应该知道该怎么回答了。
但问题又来了,只有两个客户端,没有listen()
,为什么能建立TCP
连接?
如果大家感兴趣,我们以后有机会再填上这个坑。
•四次挥手中,不管是程序主动执行close()
,还是进程被杀,都有可能发出第一次挥手FIN
包。如果机器上FIN-WAIT-2
状态特别多,一般是因为对端一直不执行close()
方法发出第三次挥手。
•Close()
会同时关闭发送和接收消息的功能。shutdown()
能单独关闭发送或接受消息。
•第二、第三次挥手,是有可能合在一起的。于是四次挥手就变成三次挥手了。
•同一个socket自己连自己,会产生TCP自连接,自连接的挥手是两次挥手。
• 没有listen
,两个客户端之间也能建立连接。这种情况叫TCP同时打开,它由四次握手产生。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/U0_kldMWChgCZxoYKyVRZQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。