linux tcp GSO和TSO實現

来源:http://www.cnblogs.com/lvyilong316/archive/2017/05/06/6818231.html
-Advertisement-
Play Games

linux tcp GSO和TSO實現 ——lvyilong316 (註:kernel版本:linux 2.6.32) 概念 TSO(TCP Segmentation Offload): 是一種利用網卡來對大數據包進行自動分段,降低CPU負載的技術。 其主要是延遲分段。 GSO(Generic Se ...


linux tcp GSO和TSO實現

——lvyilong316

(註:kernel版本:linux 2.6.32)

概念

TSO(TCP Segmentation Offload): 是一種利用網卡來對大數據包進行自動分段,降低CPU負載的技術。 其主要是延遲分段。

GSO(Generic Segmentation Offload): GSO是協議棧是否推遲分段,在發送到網卡之前判斷網卡是否支持TSO,如果網卡支持TSO則讓網卡分段,否則協議棧分完段再交給驅動。 如果TSO開啟,GSO會自動開啟。

以下是TSO和GSO的組合關係:

l  GSO開啟, TSO開啟: 協議棧推遲分段,並直接傳遞大數據包到網卡,讓網卡自動分段

l  GSO開啟, TSO關閉: 協議棧推遲分段,在最後發送到網卡前才執行分段

l  GSO關閉, TSO開啟: 同GSO開啟, TSO開啟

l  GSO關閉, TSO關閉: 不推遲分段,在tcp_sendmsg中直接發送MSS大小的數據包

開啟GSO/TSO

驅動程式在註冊網卡設備的時候預設開啟GSO: NETIF_F_GSO

驅動程式會根據網卡硬體是否支持來設置TSO: NETIF_F_TSO

可以通過ethtool -K來開關GSO/TSO

 1 #define NETIF_F_SOFT_FEATURES           (NETIF_F_GSO | NETIF_F_GRO)
 2 
 3 int register_netdevice(struct net_device *dev)
 4 
 5 {
 6 
 7               ...
 8 
 9               /* Transfer changeable features to wanted_features and enable
10 
11                * software offloads (GSO and GRO).
12 
13                */
14 
15               dev->hw_features |= NETIF_F_SOFT_FEATURES;
16 
17               dev->features |= NETIF_F_SOFT_FEATURES;         //預設開啟GRO/GSO
18 
19               dev->wanted_features = dev->features & dev->hw_features;
20 
21               ...
22 
23 }
24 
25 static int ixgbe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
26 
27 {
28 
29               ...
30 
31               netdev->features = NETIF_F_SG |
32 
33                                 NETIF_F_TSO |
34 
35                                 NETIF_F_TSO6 |
36 
37                                 NETIF_F_RXHASH |
38 
39                                 NETIF_F_RXCSUM |
40 
41                                 NETIF_F_HW_CSUM;
42 
43               register_netdev(netdev);
44 
45               ...
46 
47 }

是否推遲分段

從上面我們知道GSO/TSO是否開啟是保存在dev->features中,而設備和路由關聯,當我們查詢到路由後就可以把配置保存在sock中。

比如在tcp_v4_connect和tcp_v4_syn_recv_sock都會調用sk_setup_caps來設置GSO/TSO配置。

需要註意的是,只要開啟了GSO,即使硬體不支持TSO,也會設置NETIF_F_TSO,使得sk_can_gso(sk)在GSO開啟或者TSO開啟的時候都返回true

 l  sk_setup_caps

 1 #define NETIF_F_GSO_SOFTWARE            (NETIF_F_TSO | NETIF_F_TSO_ECN | NETIF_F_TSO6)
 2 
 3 #define NETIF_F_TSO                    (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
 4 
 5 void sk_setup_caps(struct sock *sk, struct dst_entry *dst)
 6 
 7 {
 8 
 9        __sk_dst_set(sk, dst);
10 
11        sk->sk_route_caps = dst->dev->features;
12 
13        if (sk->sk_route_caps & NETIF_F_GSO)   /*GSO預設都會開啟*/
14 
15               sk->sk_route_caps |= NETIF_F_GSO_SOFTWARE;  /*打開TSO*/
16 
17        if (sk_can_gso(sk)) {  /*對於tcp這裡會成立*/
18 
19               if (dst->header_len) {
20 
21                     sk->sk_route_caps &= ~NETIF_F_GSO_MASK;
22 
23               } else {
24 
25                    sk->sk_route_caps |= NETIF_F_SG | NETIF_F_HW_CSUM;
26 
27                    sk->sk_gso_max_size = dst->dev->gso_max_size;  /*GSO_MAX_SIZE=65536*/
28 
29              }
30  
31      }
32 
33 }

從上面可以看出,如果設備開啟了GSO,sock都會將TSO標誌打開,但是註意這和硬體是否開啟TSO無關,硬體的TSO取決於硬體自身特性的支持。下麵看下sk_can_gso的邏輯。

sk_can_gso

1 static inline int sk_can_gso(const struct sock *sk)
2 
3 {
4 
5     /*對於tcp,在tcp_v4_connect中被設置:sk->sk_gso_type = SKB_GSO_TCPV4*/
6 
7     return net_gso_ok(sk->sk_route_caps, sk->sk_gso_type);
8 
9 }

net_gso_ok

1 static inline int net_gso_ok(int features, int gso_type)
2 
3 {
4 
5       int feature = gso_type << NETIF_F_GSO_SHIFT;
6 
7       return (features & feature) == feature;
8 
9 }

由於對於tcp 在sk_setup_caps中sk->sk_route_caps也被設置有SKB_GSO_TCPV4,所以整個sk_can_gso成立。

GSO的數據包長度

對緊急數據包或GSO/TSO都不開啟的情況,才不會推遲發送, 預設使用當前MSS
開啟GSO後,tcp_send_mss返回mss和單個skb的GSO大小,為mss的整數倍。

tcp_send_mss

 1 static int tcp_send_mss(struct sock *sk, int *size_goal, int flags)
 2 
 3 {
 4 
 5        int mss_now;
 6 
 7  
 8 
 9        mss_now = tcp_current_mss(sk);/*通過ip option,SACKs及pmtu確定當前的mss*/
10 
11        *size_goal = tcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB));
12 
13  
14 
15        return mss_now;
16 
17 }

 

tcp_xmit_size_goal

 1 static unsigned int tcp_xmit_size_goal(struct sock *sk, u32 mss_now, int large_allowed)
 2 {
 3     struct tcp_sock *tp = tcp_sk(sk);
 4     u32 xmit_size_goal, old_size_goal;
 5 
 6     xmit_size_goal = mss_now;
 7     /*這裡large_allowed表示是否是緊急數據*/
 8     if (large_allowed && sk_can_gso(sk)) {  /*如果不是緊急數據且支持GSO*/
 9         xmit_size_goal = ((sk->sk_gso_max_size - 1) -
10                   inet_csk(sk)->icsk_af_ops->net_header_len -
11                   inet_csk(sk)->icsk_ext_hdr_len -
12                   tp->tcp_header_len);/*xmit_size_goal為gso最大分段大小減去tcp和ip頭部長度*/
13 
14         xmit_size_goal = tcp_bound_to_half_wnd(tp, xmit_size_goal);/*最多達到收到的最大rwnd視窗通告的一半*/
15 
16         /* We try hard to avoid divides here */
17         old_size_goal = tp->xmit_size_goal_segs * mss_now;
18 
19         if (likely(old_size_goal <= xmit_size_goal &&
20                old_size_goal + mss_now > xmit_size_goal)) {
21             xmit_size_goal = old_size_goal; /*使用老的xmit_size*/
22         } else {
23             tp->xmit_size_goal_segs = xmit_size_goal / mss_now;
24             xmit_size_goal = tp->xmit_size_goal_segs * mss_now; /*使用新的xmit_size*/
25         }
26     }
27 
28     return max(xmit_size_goal, mss_now);
29 }

tcp_sendmsg

應用程式send()數據後,會在tcp_sendmsg中嘗試在同一個skb,保存size_goal大小的數據,然後再通過tcp_push把這些包通過tcp_write_xmit發出去

  1 int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)
  2 {
  3     struct sock *sk = sock->sk;
  4     struct iovec *iov;
  5     struct tcp_sock *tp = tcp_sk(sk);
  6     struct sk_buff *skb;
  7     int iovlen, flags;
  8     int mss_now, size_goal;
  9     int err, copied;
 10     long timeo;
 11 
 12     lock_sock(sk);
 13     TCP_CHECK_TIMER(sk);
 14 
 15     flags = msg->msg_flags;
 16     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
 17 
 18     /* Wait for a connection to finish. */
 19     if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
 20         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
 21             goto out_err;
 22 
 23     /* This should be in poll */
 24     clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
 25     /* size_goal表示GSO支持的大小,為mss的整數倍,不支持GSO時則和mss相等 */
 26     mss_now = tcp_send_mss(sk, &size_goal, flags);/*返回值mss_now為真實mss*/
 27 
 28     /* Ok commence sending. */
 29     iovlen = msg->msg_iovlen;
 30     iov = msg->msg_iov;
 31     copied = 0;
 32 
 33     err = -EPIPE;
 34     if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
 35         goto out_err;
 36 
 37     while (--iovlen >= 0) {
 38         size_t seglen = iov->iov_len;
 39         unsigned char __user *from = iov->iov_base;
 40 
 41         iov++;
 42 
 43         while (seglen > 0) {
 44             int copy = 0;
 45             int max = size_goal; /*每個skb中填充的數據長度初始化為size_goal*/
 46             /* 從sk->sk_write_queue中取出隊尾的skb,因為這個skb可能還沒有被填滿 */
 47             skb = tcp_write_queue_tail(sk);
 48             if (tcp_send_head(sk)) { /*如果之前還有未發送的數據*/
 49                 if (skb->ip_summed == CHECKSUM_NONE)  /*比如路由變更,之前的不支持TSO,現在的支持了*/
 50                     max = mss_now; /*上一個不支持GSO的skb,繼續不支持*/
 51                 copy = max - skb->len; /*copy為每次想skb中拷貝的數據長度*/
 52             }
 53            /*copy<=0表示不能合併到之前skb做GSO*/
 54             if (copy <= 0) {
 55 new_segment:
 56                 /* Allocate new segment. If the interface is SG,
 57                  * allocate skb fitting to single page.
 58                  */
 59                  /* 記憶體不足,需要等待 */
 60                 if (!sk_stream_memory_free(sk))
 61                     goto wait_for_sndbuf;
 62                 /* 分配新的skb */
 63                 skb = sk_stream_alloc_skb(sk, select_size(sk),
 64                         sk->sk_allocation);
 65                 if (!skb)
 66                     goto wait_for_memory;
 67 
 68                 /*
 69                  * Check whether we can use HW checksum.
 70                  */
 71                 /*如果硬體支持checksum,則將skb->ip_summed設置為CHECKSUM_PARTIAL,表示由硬體計算校驗和*/
 72                 if (sk->sk_route_caps & NETIF_F_ALL_CSUM)
 73                     skb->ip_summed = CHECKSUM_PARTIAL;
 74                 /*將skb加入sk->sk_write_queue隊尾, 同時去掉skb的TCP_NAGLE_PUSH標記*/
 75                 skb_entail(sk, skb);
 76                 copy = size_goal;  /*這裡將每次copy的大小設置為size_goal,即GSO支持的大小*/
 77                 max = size_goal;
 78             }
 79 
 80             /* Try to append data to the end of skb. */
 81             if (copy > seglen)
 82                 copy = seglen;
 83 
 84             /* Where to copy to? */
 85             if (skb_tailroom(skb) > 0) { /*如果skb的線性區還有空間,則先填充skb的線性區*/
 86                 /* We have some space in skb head. Superb! */
 87                 if (copy > skb_tailroom(skb))
 88                     copy = skb_tailroom(skb);
 89                 if ((err = skb_add_data(skb, from, copy)) != 0) /*copy用戶態數據到skb線性區*/
 90                     goto do_fault;
 91             } else {  /*否則嘗試向SG的frags中拷貝*/
 92                 int merge = 0;
 93                 int i = skb_shinfo(skb)->nr_frags;
 94                 struct page *page = TCP_PAGE(sk);
 95                 int off = TCP_OFF(sk);
 96 
 97                 if (skb_can_coalesce(skb, i, page, off) &&
 98                     off != PAGE_SIZE) {/*pfrag->page和frags[i-1]是否使用相同頁,並且page_offset相同*/
 99                     /* We can extend the last page
100                      * fragment. */
101                     merge = 1; /*說明和之前frags中是同一個page,需要merge*/
102                 } else if (i == MAX_SKB_FRAGS ||
103                        (!i && !(sk->sk_route_caps & NETIF_F_SG))) {
104                     /* Need to add new fragment and cannot
105                      * do this because interface is non-SG,
106                      * or because all the page slots are
107                      * busy. */
108                      /*如果設備不支持SG,或者非線性區frags已經達到最大,則創建新的skb分段*/
109                     tcp_mark_push(tp, skb); /*標記push flag*/
110                     goto new_segment;
111                 } else if (page) {
112                     if (off == PAGE_SIZE) {
113                         put_page(page); /*增加page引用計數*/
114                         TCP_PAGE(sk) = page = NULL;
115                         off = 0;
116                     }
117                 } else
118                     off = 0;
119 
120                 if (copy > PAGE_SIZE - off)
121                     copy = PAGE_SIZE - off;
122 
123                 if (!sk_wmem_schedule(sk, copy))
124                     goto wait_for_memory;
125 
126                 if (!page) {
127                     /* Allocate new cache page. */
128                     if (!(page = sk_stream_alloc_page(sk)))
129                         goto wait_for_memory;
130                 }
131 
132                 /* Time to copy data. We are close to
133                  * the end! */
134                 err = skb_copy_to_page(sk, from, skb, page, off, copy); /*拷貝數據到page中*/
135                 if (err) {
136                     /* If this page was new, give it to the
137                      * socket so it does not get leaked.
138                      */
139                     if (!TCP_PAGE(sk)) {
140                         TCP_PAGE(sk) = page;
141                         TCP_OFF(sk) = 0;
142                     }
143                     goto do_error;
144                 }
145 
146                 /* Update the skb. */
147                 if (merge) { /*pfrag和frags[i - 1]是相同的*/
148                     skb_shinfo(skb)->frags[i - 1].size += copy;
149                 } else {
150                     skb_fill_page_desc(skb, i, page, off, copy);
151                     if (TCP_PAGE(sk)) {
152                         get_page(page);
153                     } else if (off + copy < PAGE_SIZE) {
154                         get_page(page);
155                         TCP_PAGE(sk) = page;
156                     }
157                 }
158 
159                 TCP_OFF(sk) = off + copy;
160             }
161 
162             if (!copied)
163                 TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
164 
165             tp->write_seq += copy;
166             TCP_SKB_CB(skb)->end_seq += copy;
167             skb_shinfo(skb)->gso_segs = 0; /*清零tso分段數,讓tcp_write_xmit去計算*/
168 
169             from += copy;
170             copied += copy;
171             if ((seglen -= copy) == 0 && iovlen == 0)
172                 goto out;
173             /* 還有數據沒copy,並且沒有達到最大可拷貝的大小(註意這裡max之前被賦值為size_goal,即GSO支持的大小), 嘗試往該skb繼續添加數據*/
174             if (skb->len < max || (flags & MSG_OOB))
175                 continue;
176             /*下麵的邏輯就是:還有數據沒copy,但是當前skb已經滿了,所以可以發送了(但不是一定要發送)*/
177             if (forced_push(tp)) { /*超過最大視窗的一半沒有設置push了*/
178                 tcp_mark_push(tp, skb); /*設置push標記,更新pushed_seq*/
179                 __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH); /*調用tcp_write_xmit馬上發送*/
180             } else if (skb == tcp_send_head(sk)) /*第一個包,直接發送*/
181                 tcp_push_one(sk, mss_now);
182             continue; /*說明發送隊列前面還有skb等待發送,且距離之前push的包還不是非常久*/
183 
184 wait_for_sndbuf:
185             set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
186 wait_for_memory:
187             if (copied)/*先把copied的發出去再等記憶體*/
188                 tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
189             /*阻塞等待記憶體*/
190             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
191                 goto do_error;
192 
193             mss_now = tcp_send_mss(sk, &size_goal, flags);
194         }
195     }
196 
197 out:
198     if (copied) /*所有數據都放到發送隊列中了,調用tcp_push發送*/
199         tcp_push(sk, flags, mss_now, tp->nonagle);
200     TCP_CHECK_TIMER(sk);
201     release_sock(sk);
202     return copied;
203 
204 do_fault:
205     if (!skb->len) {
206         tcp_unlink_write_queue(skb, sk);
207         /* It is the one place in all of TCP, except connection
208          * reset, where we can be unlinking the send_head.
209          */
210         tcp_check_send_head(sk, skb);
211         sk_wmem_free_skb(sk, skb);
212     }
213 
214 do_error:
215     if (copied)
216         goto out;
217 out_err:
218     err = sk_stream_error(sk, flags, err);
219     TCP_CHECK_TIMER(sk);
220     release_sock(sk);
221     return err;
222 }

     最終會調用tcp_push發送skb,而tcp_push又會調用tcp_write_xmit。tcp_sendmsg已經把數據按照GSO最大的size,放到一個個的skb中, 最終調用tcp_write_xmit發送這些GSO包。tcp_write_xmit會檢查當前的擁塞視窗,還有nagle測試,tsq檢查來決定是否能發送整個或者部分的skb, 如果只能發送一部分,則需要調用tso_fragment做切分。最後通過tcp_transmit_skb發送, 如果發送視窗沒有達到限制,skb中存放的數據將達到GSO最大值。

tcp_write_xmit

 1 static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
 2               int push_one, gfp_t gfp)
 3 {
 4     struct tcp_sock *tp = tcp_sk(sk);
 5     struct sk_buff *skb;
 6     unsigned int tso_segs, sent_pkts;
 7     int cwnd_quota;
 8     int result;
 9 
10     sent_pkts = 0;
11 
12     if (!push_one) {
13         /* Do MTU probing. */
14         result = tcp_mtu_probe(sk);
15         if (!result) {
16             return 0;
17         } else if (result > 0) {
18             sent_pkts = 1;
19         }
20     }
21     /*遍歷發送隊列*/
22     while ((skb = tcp_send_head(sk))) {
23         unsigned int limit;
24 
25         tso_segs = tcp_init_tso_segs(sk, skb, mss_now); /*skb->len/mss,重新設置tcp_gso_segs,因為在tcp_sendmsg中被清零了*/
26         BUG_ON(!tso_segs);
27 
28         cwnd_quota = tcp_cwnd_test(tp, skb);
29         if (!cwnd_quota)
30             break;
31 
32         if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
33             break;
34 
35         if (tso_segs == 1) {  /*tso_segs=1表示無需tso分段*/
36             /* 根據nagle演算法,計算是否需要推遲發送數據 */
37             if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
38                              (tcp_skb_is_last(sk, skb) ? /*last skb就直接發送*/
39                               nonagle : TCP_NAGLE_PUSH))))
40                 break;
41         } else {/*有多個tso分段*/
42             if (!push_one /*push所有skb*/
43                 && tcp_tso_should_defer(sk, skb))/*/如果發送視窗剩餘不多,並且預計下一個ack將很快到來(意味著可用視窗會增加),則推遲發送*/
44                 break;
45         }
46         /*下麵的邏輯是:不用推遲發送,馬上發送的情況*/
47         limit = mss_now;
48 /*由於tso_segs被設置為skb->len/mss_now,所以開啟gso時一定大於1*/
49         if (tso_segs > 1 && !tcp_urg_mode(tp)) /*tso分段大於1且非urg模式*/
50             limit = tcp_mss_split_point(sk, skb, mss_now, cwnd_quota);/*返回當前skb中可以發送的數據大小,通過mss和cwnd*/
51         /* 當skb的長度大於限制時,需要調用tso_fragment分片,如果分段失敗則暫不發送 */
52         if (skb->len > limit &&
53             unlikely(tso_fragment(sk, skb, limit, mss_now))) /*/按limit切割成多個skb*/
54             break;
55 
56         TCP_SKB_CB(skb)->when = tcp_time_stamp;
57         /*發送,如果包被qdisc丟了,則退出迴圈,不繼續發送了*/
58         if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
59             break;
60 
61         /* Advance the send_head.  This one is sent out.
62          * This call will increment packets_out.
63          */
64          /*更新sk_send_head和packets_out*/
65         tcp_event_new_data_sent(sk, skb);
66 
67         tcp_minshall_update(tp, mss_now, skb);
68         sent_pkts++;
69 
70         if (push_one)
71             break;
72     }
73 
74     if (likely(sent_pkts)) {
75         tcp_cwnd_validate(sk);
76         return 0;
77     }
78     return !tp->packets_out && tcp_send_head(sk);
79 }

    其中tcp_init_tso_segs會設置skb的gso信息後文分析。我們看到tcp_write_xmit 會調用tso_fragment進行“tcp分段”。而分段的條件是skb->len > limit。這裡的關鍵就是limit的值,我們看到在tso_segs > 1時,也就是開啟gso的時候,limit的值是由tcp_mss_split_point得到的,也就是min(skb->len, window),即發送視窗允許的最大值。在沒有開啟gso時limit就是當前的mss。

tcp_init_tso_segs

 1 static int tcp_init_tso_segs(struct sock *sk, struct sk_buff *skb,
 2                  unsigned int mss_now)
 3 {
 4     int tso_segs = tcp_skb_pcount(skb); /*skb_shinfo(skb)->gso_seg之前被初始化為0*/
 5 
 6     if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
 7         tcp_set_skb_tso_segs(sk, skb, mss_now);
 8         tso_segs = tcp_skb_pcount(skb);
 9     }
10     return tso_segs;
11 }
12 
13 static void tcp_set_skb_tso_segs(struct sock *sk, struct sk_buff *skb,
14                  unsigned int mss_now)
15 {
16     /* Make sure we own this skb before messing gso_size/gso_segs */
17     WARN_ON_ONCE(skb_cloned(skb));
18 
19     if (skb->len <= mss_now || !sk_can_gso(sk) ||
20         skb->ip_summed == CHECKSUM_NONE) {/*不支持gso的情況*/
21         /* Avoid the costly divide in the normal
22 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前從mysql 5.6的時候,mysql 還沒有密碼策略這個東東,所以我們每個用戶的密碼都可以隨心所欲地設置,什麼123 ,abc 這些,甚至你搞個空格,那也是OK的。 而mysql.user 表裡面也保存有password 的欄位進行標識這個是密碼的位置。 但是當升級到了MySQL 5.7 的時 ...
  • 註冊ocx時出現ole初始化錯誤或OCX載入錯誤 問題原因:安裝文件不能放在包含空格的文件夾名字中 解決辦法:oracle安裝的時候最好安裝在盤符的根目錄下,不要使用中文 ora-00922(缺少或無效選項) 問題原因:配置管理員密碼時,採用了數字開頭的密碼 解決辦法:將密碼改為英文開頭(配置密碼時 ...
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重覆值時,會觸發此異常。 ORA-00017: 請求會話以設置跟蹤事件 ORA-00018: 超出最大會話數 ORA-00019: 超出最大會話許可數 ORA-00020: 超出最大進程數 () ORA-00021... ...
  • 主要是看《SQL必知必會》第四版的書,而寫的一些SQL筆記,紅色的是方便以後查詢的sql語句,工作中主要是使用mysql資料庫,所以筆記也是圍繞mysql而寫的。 下文調試的數據表sql語句,如果要嘗試的調試的話可以複製過去運行即可 1 # Host: localhost (Version: 5.5 ...
  • 一、首先創建兩張表stu,sc --說明: 外鍵必須建立索引; FOREIGN key(sid) 設置外鍵,把sid設為外鍵 REFERENCES stu(sid) 引用作用。引用stu表中的sid ON DELETE CASCADE 級聯刪除ON UPDATE CASCADE 級聯更新 二、向兩張 ...
  • Linux Unix 免費 收費 開源 不開源 硬體無要求 有要求 IBM Sun Hp 主流的Linux的髮型版本: RedHat Fedora Mandriva Ubuntu CentOS Debian Getoo ...
  • Fedora是一個Linux發行,基於Redhat,算是Redhat的測試版本。而Fedora workstation預設使用的桌面環境是Gnome,使用Xfce作為桌面環境的Fedora較之稍顯輕量,不過呢,沒有經過配置的Xfce Fedora是很朴素的。 得益於Fedora中文源的出現能讓一些w ...
  • Bash 的若幹基本問題 這裡介紹一些bash啟動前、後的問題,以及一些使用bash需要註意的基本問題。 1、Bash的介紹 Bash是一種Shell程式,它是一般的Linux系統中的預設的Shell程式,一般情況下Bash指的是/bin/bash這個軟體。 一個Linux系統中有多少個可用的She ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...