linux实现 TCP
强调一下,TCP属于传输层的协议,由操作系统来完成,这个传输层的协议是一个可靠的传输协议,主要完成对数据的传输,对数据内容不关心。
对于linux来说:对TCP的实现主要在linux-5.11.11/net/ipv4包下的 tcp_input.c,tcp_output.c 两个文件下。
这些文件提供了tcp_connect() tcp_send_synack()等 方法,用于创建tcp连接
/* Build a SYN and send it off. */
//三次握手的第一个阶段:发送一个SYN
int tcp_connect(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *buff;
int err;
tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);
if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
return -EHOSTUNREACH; /* Routing failure or similar. */
tcp_connect_init(sk);
if (unlikely(tp->repair)) {
tcp_finish_connect(sk, NULL);
return 0;
}
buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
if (unlikely(!buff))
return -ENOBUFS;
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
tcp_mstamp_refresh(tp);
tp->retrans_stamp = tcp_time_stamp(tp);
tcp_connect_queue_skb(sk, buff);
tcp_ecn_send_syn(sk, buff);
tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);
/* Send off SYN; include data in Fast Open. */
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
if (err == -ECONNREFUSED)
return err;
/* We change tp->snd_nxt after the tcp_transmit_skb() call
* in order to make this packet get counted in tcpOutSegs.
*/
WRITE_ONCE(tp->snd_nxt, tp->write_seq);
tp->pushed_seq = tp->write_seq;
buff = tcp_send_head(sk);
if (unlikely(buff)) {
WRITE_ONCE(tp->snd_nxt, TCP_SKB_CB(buff)->seq);
tp->pushed_seq = TCP_SKB_CB(buff)->seq;
}
TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);
/* Timer for repeating the SYN until an answer. */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
return 0;
}
EXPORT_SYMBOL(tcp_connect);
// 三次握手第二个阶段:发送一个SYN_ACK
/* Send a crossed SYN-ACK during socket establishment.
* WARNING: This routine must only be called when we have already sent
* a SYN packet that crossed the incoming SYN that caused this routine
* to get called. If this assumption fails then the initial rcv_wnd
* and rcv_wscale values will not be correct.
*/
int tcp_send_synack(struct sock *sk)
{
struct sk_buff *skb;
skb = tcp_rtx_queue_head(sk);
if (!skb || !(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) {
pr_err("%s: wrong queue state\n", __func__);
return -EFAULT;
}
if (!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_ACK)) {
if (skb_cloned(skb)) {
struct sk_buff *nskb;
tcp_skb_tsorted_save(skb) {
nskb = skb_copy(skb, GFP_ATOMIC);
} tcp_skb_tsorted_restore(skb);
if (!nskb)
return -ENOMEM;
INIT_LIST_HEAD(&nskb->tcp_tsorted_anchor);
tcp_highest_sack_replace(sk, skb, nskb);
tcp_rtx_queue_unlink_and_free(skb, sk);
__skb_header_release(nskb);
tcp_rbtree_insert(&sk->tcp_rtx_queue, nskb);
sk_wmem_queued_add(sk, nskb->truesize);
sk_mem_charge(sk, nskb->truesize);
skb = nskb;
}
TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ACK;
tcp_ecn_send_synack(sk, skb);
}
return tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
}
//三次握手第三个阶段:发送ACK
/* This routine sends an ack and also updates the window. */
void __tcp_send_ack(struct sock *sk, u32 rcv_nxt)
{
struct sk_buff *buff;
/* If we have been reset, we may not send again. */
if (sk->sk_state == TCP_CLOSE)
return;
/* We are not putting this on the write queue, so
* tcp_transmit_skb() will set the ownership to this
* sock.
*/
buff = alloc_skb(MAX_TCP_HEADER,
sk_gfp_mask(sk, GFP_ATOMIC | __GFP_NOWARN));
if (unlikely(!buff)) {
struct inet_connection_sock *icsk = inet_csk(sk);
unsigned long delay;
delay = TCP_DELACK_MAX << icsk->icsk_ack.retry;
if (delay < TCP_RTO_MAX)
icsk->icsk_ack.retry++;
inet_csk_schedule_ack(sk);
icsk->icsk_ack.ato = TCP_ATO_MIN;
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, delay, TCP_RTO_MAX);
return;
}
/* Reserve space for headers and prepare control bits. */
skb_reserve(buff, MAX_TCP_HEADER);
tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);
/* We do not want pure acks influencing TCP Small Queues or fq/pacing
* too much.
* SKB_TRUESIZE(max(1 .. 66, MAX_TCP_HEADER)) is unfortunately ~784
*/
skb_set_tcp_pure_ack(buff);
/* Send it off, this clears delayed acks for us. */
__tcp_transmit_skb(sk, buff, 0, (__force gfp_t)0, rcv_nxt);
}
EXPORT_SYMBOL_GPL(__tcp_send_ack);
HTTP实现
HTTP协议属于应用层的协议,这个协议是由应用程序实现的,许多的应用都实现了HTTP协议,如:tomcat,浏览器等等。与TCP不同,HTTP协议关注数据的内容,它规定了数据的格式,以方便各个应用解析数据 ,如请求的 请求头,请求行,请求体等。
Socket
如果我们我们使用java实现一个浏览器,这个浏览器要支持HTTP,我们怎么用java来实现HTTP协议呢?大致的一个流程为:
- 首先,我们要根据HTTP协议个规定,解析请求的网址字符串
- 之后与目标主机建立TCP连接
- 发送数据
那我们如何建立TCP连接呢呢?我们的应用是是在操作系统之上的,所以我们可以使用java调用linux实现的tcp_connetct() 等方法就可以方便的建立TCP连接。
但是tcp_connetct() 等方法是linux的核心方法,linux操作系统肯定不会直接暴露这些方法,linux暴露给我们一个类——socket,通过这socket这个类我们就可以调用操作系统的这些方法。
在linux下,socket类的路径为: linux-XX.XXX.XXX/net/socket.c
也就是说socket是对linux这些方法的封装, Socket是应用层与TCP/IP协议族通信的中间软件抽象层,****它是一组接口****。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。
在socket的__sys_connect方法中会真正的调用 tcp_input.c,tcp_output.c中的方法。
int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
int ret = -EBADF;
struct fd f;
f = fdget(fd);
if (f.file) {
struct sockaddr_storage address;
ret = move_addr_to_kernel(uservaddr, addrlen, &address);
if (!ret)
ret = __sys_connect_file(f.file, &address, addrlen, 0);
fdput(f);
}
return ret;
}
所以我们使用java,python….来建立TCP连接,我们都是面向socket,建立的socket连接就是TCP连接/UDP连接。
在java中如何调用到linux暴露的socket类
在java中我们使用socket非常简单,只需要下面代码就可以建立TCP连接:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
public class SokectTest {
private void test() throws IOException {
Socket socket = new Socket();//tcp
//UDP是DatagramSocket类
socket.connect(new InetSocketAddress("localhost",8080));
}
}
通过对上面代码的debug,可以知道最终将调用本地方法socketCreate()【window是调用socket0()】
native void socketCreate(boolean isServer) throws IOException
这时我们就需要到openJDK中去找到这个方法:
#include <errno.h> //这种include方式在本工程里面找不到的,这种写法时引入操作系统的头文件
//下面include方式,这些文件在本工程内找的到
#include "jvm.h"
#include "net_util.h"
#include "java_net_SocketOptions.h"
#include "java_net_PlainSocketImpl.h"
JNIEXPORT void JNICALL
Java_java_net_PlainSocketImpl_socketCreate(JNIEnv *env, jobject this,
jboolean stream, jboolean isServer) {
jobject fdObj, ssObj;
int fd;
int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
int domain = ipv6_available() ? AF_INET6 : AF_INET;
if (socketExceptionCls == NULL) {
jclass c = (*env)->FindClass(env, "java/net/SocketException");
CHECK_NULL(c);
socketExceptionCls = (jclass)(*env)->NewGlobalRef(env, c);
CHECK_NULL(socketExceptionCls);
}
fdObj = (*env)->GetObjectField(env, this, psi_fdID);
if (fdObj == NULL) {
(*env)->ThrowNew(env, socketExceptionCls, "null fd object");
return;
}
//这里创建一个真正的socket连接
if ((fd = socket(domain, type, 0)) == -1) {
/* note: if you run out of fds, you may not be able to load
* the exception class, and get a NoClassDefFoundError
* instead.
*/
NET_ThrowNew(env, errno, "can't create socket");
return;
}
我们发现 真正创建socket的是 socket(domain, type, 0) 。这个方法在openJDK中时找不到的,看一下投文件,我们需要在我们mac提供的SDK中找errno.h:
不是这个:
/Library/Developer/CommandLineTools/usr/include/c++/v1/errno.h
是这个:
/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/errno.h
windows的路径为:/Windows©/Program Files(x86)/Microsoft SDKs/
但是这个文件里的内容被apple隐藏了,读得不出任何信息。
Tomcat BIO/NIO
tomcat需要从socket中读数据,这就是一个网络IO,有两种IO模型:NIO,BIO。 tomcat7之前只支持BIO,在tomcat7之后两个都支持,并且在tomcat8后,将NIO作为默认。