简述linux实现 TCP,Socket的由来

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协议呢?大致的一个流程为:

  1. 首先,我们要根据HTTP协议个规定,解析请求的网址字符串
  2. 之后与目标主机建立TCP连接
  3. 发送数据

那我们如何建立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作为默认。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值