Linux GSO邏輯分析

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

Linux GSO邏輯分析 ——lvyilong316 (註:kernel版本linux 2.6.32) GSO用來擴展之前的TSO,目前已經併入upstream內核。TSO只能支持tcp協議,而GSO可以支持tcpv4, tcpv6, udp等協議。在GSO之前,skb_shinfo(skb)有兩 ...


Linux GSO邏輯分析

——lvyilong316

(註:kernel版本linux 2.6.32)

    GSO用來擴展之前的TSO,目前已經併入upstream內核。TSO只能支持tcp協議,而GSO可以支持tcpv4, tcpv6, udp等協議。在GSO之前,skb_shinfo(skb)有兩個成員ufo_size, tso_size,分別表示udp fragmentation offloading支持的分片長度,以及tcp segmentation offloading支持的分段長度,現在都用skb_shinfo(skb)->gso_size代替。

skb_shinfo(skb)->ufo_segs, skb_shinfo(skb)->tso_segs也被替換成了skb_shinfo(skb)->gso_segs,表示分片的個數。

gso用來delay 大包的分片,所以一直到dev_hard_start_xmit函數才會調用到。

l   dev_hard_start_xmit

 1 int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,
 2 
 3             struct netdev_queue *txq)
 4 
 5 {
 6 
 7     const struct net_device_ops *ops = dev->netdev_ops;
 8 
 9     int rc;
10 
11  
12 
13     if (likely(!skb->next)) {
14 
15         if (!list_empty(&ptype_all))
16 
17             dev_queue_xmit_nit(skb, dev);
18 
19         //判斷網卡是否需要協議棧負責gso
20 
21         if (netif_needs_gso(dev, skb)) {
22 
23 //真正負責GSO操作的函數
24 
25             if (unlikely(dev_gso_segment(skb)))
26 
27                 goto out_kfree_skb;
28 
29             if (skb->next)
30 
31                 goto gso;
32 
33         }
34 
35 //……
36 
37 gso:
38 
39     do {
40 
41 //指向GSO分片後的一個skb
42 
43         struct sk_buff *nskb = skb->next;
44 
45         skb->next = nskb->next;
46 
47         nskb->next = NULL;
48 
49         if (dev->priv_flags & IFF_XMIT_DST_RELEASE)
50 
51             skb_dst_drop(nskb);
52 
53 //將通過GSO分片後的包逐個發出
54 
55         rc = ops->ndo_start_xmit(nskb, dev);
56 
57         if (unlikely(rc != NETDEV_TX_OK)) {
58 
59             nskb->next = skb->next;
60 
61             skb->next = nskb;
62 
63             return rc;
64 
65         }
66 
67         txq_trans_update(txq);
68 
69         if (unlikely(netif_tx_queue_stopped(txq) && skb->next))
70 
71             return NETDEV_TX_BUSY;
72 
73     } while (skb->next);
74 
75  
76 
77     skb->destructor = DEV_GSO_CB(skb)->destructor;
78 
79  
80 
81 out_kfree_skb:
82 
83     kfree_skb(skb);
84 
85     return NETDEV_TX_OK;
86 
87 }

    那是不是所有skb在發送時都要經過GSO的邏輯呢?顯然不是,只有通過netif_needs_gso判斷才會進入GSO的邏輯,下麵我們看下netif_needs_gso是如何判斷的。

 1 static inline int netif_needs_gso(struct net_device *dev, struct sk_buff *skb)
 2 
 3 {
 4 
 5     return skb_is_gso(skb) &&
 6 
 7            (!skb_gso_ok(skb, dev->features) ||
 8 
 9         unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
10 
11 }

註意這裡最後用了一個unlikely,因為如果通過前面的判斷,說明網卡是支持GSO的,而一般網卡支持GSO也就會支持CHECKSUM_PARTIAL。進入GSO處理的第一個前提是skb_is_gso函數返回真,看下skb_is_gso的邏輯:

1 static inline int skb_is_gso(const struct sk_buff *skb)
2 
3 {   
4 
5     return skb_shinfo(skb)->gso_size;
6 
7 }

skb_is_gso的邏輯很簡單,返回skb_shinfo(skb)->gso_size,所以進入GSO處理邏輯的必要條件之一是skb_shinfo(skb)->gso_size不為0,那麼這個欄位的含義是什麼呢?gso_size表示生產GSO大包時的數據包長度,一般時mss的整數倍。下麵看skb_gso_ok,如果這個函數返回False,就可以進入GSO處理邏輯。

1 static inline int skb_gso_ok(struct sk_buff *skb, int features)
2 
3 {
4 
5     return net_gso_ok(features, skb_shinfo(skb)->gso_type) &&
6 
7            (!skb_has_frags(skb) || (features & NETIF_F_FRAGLIST));
8 
9 }

skb_shinfo(skb)->gso_type包括SKB_GSO_TCPv4, SKB_GSO_UDPv4,同時NETIF_F_XXX的標誌也增加了相應的bit,標識設備是否支持TSO, GSO, e.g. 

1 NETIF_F_TSO = SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT
2 
3 NETIF_F_UFO = SKB_GSO_UDPV4 << NETIF_F_GSO_SHIFT
4 
5 #define NETIF_F_GSO_SHIFT 16

通過以上三個函數分析,以下三個情況需要協議棧負責GSO。

    下麵看GSO的協議棧處理邏輯,入口就是dev_gso_segment。

l   dev_gso_segment

    協議棧的GSO邏輯是在dev_gso_segment中進行的。這個函數主要完成對skb的分片,並將分片存放在原始skb的skb->next中,這也是GSO的主要工作

 1 static int dev_gso_segment(struct sk_buff *skb)
 2 
 3 {
 4 
 5     struct net_device *dev = skb->dev;
 6 
 7     struct sk_buff *segs;
 8 
 9     int features = dev->features & ~(illegal_highdma(dev, skb) ?
10 
11                      NETIF_F_SG : 0);
12 
13  
14 
15     segs = skb_gso_segment(skb, features);
16 
17  
18 
19     /* Verifying header integrity only. */
20 
21     if (!segs)
22 
23         return 0;
24 
25  
26 
27     if (IS_ERR(segs))
28 
29         return PTR_ERR(segs);
30 
31  
32 
33     skb->next = segs;
34 
35     DEV_GSO_CB(skb)->destructor = skb->destructor;
36 
37     skb->destructor = dev_gso_skb_destructor;
38 
39  
40 
41     return 0;
42 
43 }

    主要分片邏輯由skb_gso_segment來處理,這裡我們主要看下析構過程,此時skb經過分片之後已經是一個skb list,通過skb->next串在一起,此時把初始的skb->destructor函數存到skb->cb中,然後把skb->destructor變更為dev_gso_skb_destructor。dev_gso_skb_destructor會把skb->next一個個通過kfree_skb釋放掉,最後調用DEV_GSO_CB(skb)->destructor,即skb初始的析構函數做最後的清理。

l   skb_gso_segment

這個函數將skb分片,並返回一個skb list。如果skb不需要分片則返回NULL。

 1 struct sk_buff *skb_gso_segment(struct sk_buff *skb, int features)
 2 
 3 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
 6 
 7     struct packet_type *ptype;
 8 
 9     __be16 type = skb->protocol;
10 
11     int err;
12 
13  
14 
15     skb_reset_mac_header(skb);
16 
17     skb->mac_len = skb->network_header - skb->mac_header;
18 
19     __skb_pull(skb, skb->mac_len);
20 
21     //如果skb->ip_summed 不是 CHECKSUM_PARTIAL,那麼報個warning,因為GSO類型的skb其ip_summed一般都是CHECKSUM_PARTIAL
22 
23     if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
24 
25         struct net_device *dev = skb->dev;
26 
27         struct ethtool_drvinfo info = {};
28 
29         WARN(……);
30 
31         if (skb_header_cloned(skb) &&
32 
33             (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
34 
35             return ERR_PTR(err);
36 
37     }
38 
39     rcu_read_lock();
40 
41     list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
42 
43         if (ptype->type == type && !ptype->dev && ptype->gso_segment) {
44 
45             if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
46 
47                 // 如果ip_summed != CHECKSUM_PARTIAL,則調用上層協議的gso_send_check
48 
49                 err = ptype->gso_send_check(skb);
50 
51                 segs = ERR_PTR(err);
52 
53                 if (err || skb_gso_ok(skb, features))
54 
55                     break;
56 
57                 __skb_push(skb, (skb->data -
58 
59                 skb_network_header(skb)));
60 
61         }
62 
63            //把skb->data指向network header,調用上層協議的gso_segment完成分片
64 
65             segs = ptype->gso_segment(skb, features);
66 
67             break;
68 
69         }
70 
71     }
72 
73     rcu_read_unlock();
74 
75 //把skb->data再次指向mac header
76 
77     __skb_push(skb, skb->data - skb_mac_header(skb));
78 
79  
80 
81     return segs;
82 
83 }

 

  最終追調用上層協議的gso處理函數,對於IP協議,在註冊IP的packet_type時,其gso處理函數被初始化為inet_gso_segment。下麵我們看inet_gso_segment的處理流程。

l   inet_gso_segment

   ./net/ipv4/af_inet.c

IP層GSO操作只是提供介面給鏈路層來訪問傳輸層(TCP、UDP),因此IP層實現的介面只是根據分段數據報獲取對應的傳輸層介面,並對完成GSO分段後的IP數據報重新計算校驗和。

static struct sk_buff *inet_gso_segment(struct sk_buff *skb, int features)

{

    struct sk_buff *segs = ERR_PTR(-EINVAL);

    struct iphdr *iph;

    const struct net_protocol *ops;

    int proto;

    int ihl;

    int id;

    unsigned int offset = 0;

 

    if (!(features & NETIF_F_V4_CSUM))

        features &= ~NETIF_F_SG;

   //校驗待軟GSO分段的的skb,其gso_tpye是否存在其他非法值

    if (unlikely(skb_shinfo(skb)->gso_type &

             ~(SKB_GSO_TCPV4 |

               SKB_GSO_UDP |

               SKB_GSO_DODGY |

               SKB_GSO_TCP_ECN |

               0)))

        goto out;

    //分段數據至少大於IP首部長度

    if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))

        goto out;

    //檢驗首部中的長度欄位是否有效

    iph = ip_hdr(skb);

    ihl = iph->ihl * 4;

    if (ihl < sizeof(*iph))

        goto out;

   //再次通過首部中的長度欄位檢測skb長度是否有效

  if (unlikely(!pskb_may_pull(skb, ihl)))

        goto out;

    //註意:這裡已經將data偏移到了傳送層頭部了,去掉了IP頭

    __skb_pull(skb, ihl);

    skb_reset_transport_header(skb);//設置傳輸層頭部位置

    iph = ip_hdr(skb);

    id = ntohs(iph->id);//取出首部中的id欄位

    proto = iph->protocol & (MAX_INET_PROTOS - 1);//取出IP首部的協議值,用於定位與之對應的傳輸層介面(tcp還是udp)

    segs = ERR_PTR(-EPROTONOSUPPORT);

 

    rcu_read_lock();

    ops = rcu_dereference(inet_protos[proto]);//根據協議欄位取得上層的協議介面

    if (likely(ops && ops->gso_segment))

        segs = ops->gso_segment(skb, features);//調用上冊協議的GSO處理函數

    rcu_read_unlock();

 

    if (!segs || IS_ERR(segs))

        goto out;

    //開始處理分段後的skb

    skb = segs;

    do {

        iph = ip_hdr(skb);

        if (proto == IPPROTO_UDP) {//對於UDP進行的IP分片的頭部處理邏輯

            iph->id = htons(id);//所有UDP的IP分片id都相同

            iph->frag_off = htons(offset >> 3);//ip頭部偏移欄位單位為8位元組

            if (skb->next != NULL)

                iph->frag_off |= htons(IP_MF);//設置分片標識

            offset += (skb->len - skb->mac_len - iph->ihl * 4);

        } else

        iph->id = htons(id++);//對於TCP報,分片後IP頭部中id加1

        iph->tot_len = htons(skb->len - skb->mac_len);

        iph->check = 0;

        //計算校驗和,只是IP頭部的

        iph->check = ip_fast_csum(skb_network_header(skb), iph->ihl);

    } while ((skb = skb->next));

 

out:

    return segs;

}

這裡有個問題,UDP經過GSO分片後每個分片的IP頭部id是一樣的,這個符合IP分片的邏輯,但是為什麼TCP的GSO分片,IP頭部的id會依次加1呢?原因是: tcp建立三次握手的過程中產生合適的mss(具體的處理機制參見TCP/IP詳解P257),這個mss肯定是<=網路層的最大路徑MTU,然後tcp數據封裝成ip數據包通過網路層發送,當伺服器端傳輸層接收到tcp數據之後進行tcp重組。所以正常情況下tcp產生的ip數據包在程中是不會發生分片的!由於GSO應該保證對外透明,所以其效果應該也和在TCP層直接分片的效果是一樣的,所以這裡UDP的處理是IP分片邏輯,但對TCP的處理是構造新的skb邏輯

小結:對於GSO

    UDP:所有分片ip頭部id都相同,設置IP_MF分片標誌(除最後一片) (等同於IP分片)

    TCP:分片後,每個分片IP頭部中id1, (等同於TCP分段)

 

下麵分別看對於TCP和UDP調用不通的GSO處理函數。對於TCP其GSO處理函數為tcp_tso_segment。

l   tcp_tso_segment

./net/ipv4/tcp.c

  1 struct sk_buff *tcp_tso_segment(struct sk_buff *skb, int features)
  2 
  3 {
  4 
  5     struct sk_buff *segs = ERR_PTR(-EINVAL);
  6 
  7     struct tcphdr *th;
  8 
  9     unsigned thlen;
 10 
 11     unsigned int seq;
 12 
 13     __be32 delta;
 14 
 15     unsigned int oldlen;
 16 
 17     unsigned int mss;
 18 
 19     //檢測報文長度至少由tcp頭部長度
 20 
 21     if (!pskb_may_pull(skb, sizeof(*th)))
 22 
 23         goto out;
 24 
 25  
 26 
 27     th = tcp_hdr(skb);
 28 
 29     thlen = th->doff * 4;//TCP頭部的長度欄位單位為4位元組
 30 
 31     if (thlen < sizeof(*th))
 32 
 33         goto out;
 34 
 35    //再次通過首部中的長度欄位檢測skb長度是否有效
 36 
 37     if (!pskb_may_pull(skb, thlen))
 38 
 39         goto out;
 40 
 41     //把tcp header移到skb header里,把skb->len存到oldlen中,此時skb->len就只有ip payload的長度(包含TCP首部)
 42 
 43     oldlen = (u16)~skb->len;
 44 
 45     __skb_pull(skb, thlen); //data指向tcp payload
 46 
 47     //這裡可以看出gso_size的含義就是mss
 48 
 49     mss = skb_shinfo(skb)->gso_size;
 50 
 51     if (unlikely(skb->len <= mss))//如果skb長度小於mss就不需要GSO分片處理了
 52 
 53         goto out;
 54 
 55     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
 56 
 57         /* Packet is from an untrusted source, reset gso_segs. */
 58 
 59         int type = skb_shinfo(skb)->gso_type;
 60 
 61         //校驗待軟GSO分段的的skb,其gso_tpye是否存在其他非法值
 62 
 63         if (unlikely(type &
 64 
 65                  ~(SKB_GSO_TCPV4 |
 66 
 67                    SKB_GSO_DODGY |
 68 
 69                    SKB_GSO_TCP_ECN |
 70 
 71                    SKB_GSO_TCPV6 |
 72 
 73                    0) ||
 74 
 75                  !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
 76 
 77             goto out;
 78 
 79         //計算出skb按照mss的長度需要分多少片,賦值給gso_segs
 80 
 81         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
 82 
 83  
 84 
 85         segs = NULL;
 86 
 87         goto out;
 88 
 89     }
 90 
 91     //skb_segment是真正的分段實現,後面再分析
 92 
 93     segs = skb_segment(skb, features);
 94 
 95     if (IS_ERR(segs))
 96 
 97         goto out;
 98 
 99  
100 
101     delta = htonl(oldlen + (thlen + mss));
102 
103  
104 
105     skb = segs;
106 
107     th = tcp_hdr(skb);
108 
109     seq = ntohl(th->seq);
110 
111     //下麵是設置每個分片的tcp頭部信息
112 
113     do {
114 
115         th->fin = th->psh = 0;
116 
117         //計算每個分片的校驗和
118 
119         th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
120 
121                        (__force u32)delta));
122 
123         if (skb->ip_summed != CHECKSUM_PARTIAL)
124 
125             th->check =csum_fold(csum_partial(skb_transport_header(skb),
126 
127                             thlen, skb->csum));
128 
129         //重新初始化每個分片的序列號
130 
131         seq += mss;
132 
133         skb = skb->next;
134 
135         th = tcp_hdr(skb);
136 
137  
138 
139         th->seq = htonl(seq);
140 
141         th->cwr = 0;
142 
143     } while (skb->next);
144 
145  
146 
147     delta = htonl(oldlen + (skb->tail - skb->transport_header) +
148 
149               skb->data_len);
150 
151     th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
152 
153                 (__force u32)delta));
154 
155     if (skb->ip_summed != CHECKSUM_PARTIAL)
156 
157         th->check = csum_fold(csum_partial(skb_transport_header(skb),
158 
159                            thlen, skb->csum));
160 
161  
162 
163 out:
164 
165     return segs;
166 
167 }

 

    從上面可以看出,每個TCP的GSO分片是包含了TCP頭部信息的,這也符合TCP層的分段邏輯。另外註意這裡傳遞給skb_segment做分段時是不帶TCP首部的。對於UDP,其GSO處理函數為udp4_ufo_fragment。

l   udp4_ufo_fragment

./net/ipv4/udp.c

 1 struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, int features)
 2 
 3 {
 4 
 5     struct sk_buff *segs = ERR_PTR(-EINVAL);
 6 
 7     unsigned int mss;
 8 
 9     int offset;
10 
11     __wsum csum;
12 
13  
14 
15     mss = skb_shinfo(skb)->gso_size;
16 
17     if (unlikely(skb->len <= mss))
18 
19         goto out;
20 
21  
22 
23     if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
24 
25         /* Packet is from an untrusted source, reset gso_segs. */
26 
27         int type = skb_shinfo(skb)->gso_type;
28 
29  
30 
31         if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
32 
33                  !(type & (SKB_GSO_UDP))))
34 
35             goto out;
36 
37  
38 
39         skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
40 
41  
42 
43         segs = NULL;
44 
45         goto out;
46 
47     }
48 
49  
50 
51     /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
52 
53      * do checksum of UDP packets sent as multiple IP fragments.
54 
55 */
56 
57     //計算udp的checksum
58 
59     offset = skb->csum_start - skb_headroom(skb);
60 
61     csum = skb_checksum(skb, offset, skb->len - offset, 0);
62 
63     offset += skb->csum_offset;
64 
65     *(__sum16 *)(skb->data + offset) = csum_fold(csum);
66 
67     skb->ip_summed = CHECKSUM_NONE;
68 
69     //這裡傳遞給skb_segment做分片時是沒有將UDP首部去除的
70 
71     segs = skb_segment(skb, features);
72 
73 out:
74 
75     return segs;
76 
77 }

 

註意這裡傳遞給skb_segment 做分片是帶有udp首部的,分片將udp首部作為普通數據切分,這也意味著對於udp的GSO分片,只有第一片有UDP首部。udp的分段其實和ip的分片沒什麼區別,只是多一個計算checksum的步驟,下麵看完成分片的關鍵函數skb_segment。

l   skb_segment

/net/core/skbuff.c

  1 struct sk_buff *skb_segment(struct sk_buff *skb, int features)
  2 
  3 {
  4 
  5     struct sk_buff *segs = NULL;
  6 
  7     struct sk_buff *tail = NULL;
  8 
  9     struct sk_buff *fskb = skb_shinfo(skb)->frag_list;
 10 
 11     unsigned int mss = skb_shinfo(skb)->gso_size;
 12 
 13     unsigned int doffset = skb->data - skb_mac_header(skb);//mac頭+ip頭+tcp頭 或mac頭+ip頭(對於UDP傳入時沒有將頭部偏移過去)
 14 
 15     unsigned int offset = doffset;
 16 
 17     unsigned int headroom;
 18 
 19     unsigned int len;
 20 
 21     int sg = features & NETIF_F_SG;
 22 
 23     int nfrags = skb_shinfo(skb)->nr_frags;
 24 
 25     int err = -ENOMEM;
 26 
 27     int i = 0;
 28 
 29     int pos;
 30 
 31  
 32 
 33     __skb_push(skb, doffset);
 34 
 35     headroom = skb_headroom(skb);
 36 
 37     pos = skb_headlen(skb);//pos初始化為線性區長度
 38 
 39  
 40 
 41     do {
 42 
 43         struct sk_buff *nskb;
 44 
 45         skb_frag_t *frag;
 46 
 47         int hsize;
 48 
 49         int size;
 50 
 51         // offset為分片已處理的長度,len為skb->len減去直到offset的部分。開始時,offset只是mac header + ip header + tcp header的長度,len即tcp payload的長度。隨著segment增加, offset每次都增加mss長度。因此len的定義是每個segment的payload長度(最後一個segment的payload可能小於一個mss長度)
 52 
 53         len = skb->len - offset;
 54 
 55         if (len > mss)//len為本次要創建的新分片的長度
 56 
 57             len = mss;
 58 
 59        // hsize為線性區部分的payload減去offset後的大小,如果hsize小於0,那麼說明payload在skb的frags或frag_list中。隨著offset一直增長,必定會有hsize一直<0的情況開始出現,除非skb是一個完全linearize化的skb
 60 
 61         hsize = skb_headlen(skb) - offset;
 62 
 63         //這種情況說明線性區已經沒有tcp payload的部分,需要pull數據過來
 64  
 65         if (hsize < 0)
 66 
 67 hsize = 0;
 68 
 69        //如果不支持NETIF_F_SG或者hsize大於len,那麼hsize就為len(本次新分片的長度),此時說明segment的payload還在skb 線性區中
 70 
 71         if (hsize > len || !sg)
 72 
 73             hsize = len;
 74 
 75  
 76 
 77         if (!hsize && i >= nfrags) {// hsize為0,表示需要從frags數組或者frag_list鏈表中拷貝出數據,i >= nfrags說明frags數組中的數據也拷貝完了,下麵需要從frag_list鏈表中拷貝數據了
 78 
 79             BUG_ON(fskb->len != len);
 80 
 81  
 82 
 83 pos += len;
 84 
 85             //frag_list的數據不用真的拷貝,只需要拷貝其skb描述符,就可以復用其數據區
 86 
 87             nskb = skb_clone(fskb, GFP_ATOMIC);//拷貝frag_list中的skb的描述符
 88 
 89             fskb = fskb->next;//指向frag_list的下一個skb元素
 90 
 91  
 92 
 93             if (unlikely(!nskb))
 94 
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 步驟: 1.開啟事務 start transaction 當我們開啟一個事務的時候,我們對sql的操作都發生在記憶體中,但是沒有真正的反饋到資料庫磁碟的文件中! 2.回滾 rollback 回滾,就是恢復到事務開啟之前的最原始的狀態! 註意:回滾操作會自動的關閉一個事務,如果想再次執行事務,需要重新開 ...
  • 第一招、mysql服務的啟動和停止 net stop mysql net start mysql 第二招、登陸mysql 語法如下: mysql -u用戶名 -p用戶密碼 鍵入命令mysql -uroot -p, 回車後提示你輸入密碼,輸入12345,然後回車即可進入到mysql中了,mysql的提 ...
  • 整體結構 首先創建一個名為employee的資料庫 create database employee; 然後在該資料庫下建一張表 然後就是項目和資料庫進行聯繫,然後操作資料庫(增刪改查) 如果看了上一個項目大家應該被每次操作都要重新連接資料庫而煩惱,而且代碼具有高度的重覆性,那麼我們把相同的代碼封裝 ...
  • 如何讓一個表的時間欄位,隨著用戶對該行操作修改時自動更新 顯示表的所有欄位 數值列類型取值範圍及占用位元組 字元串列類型最大尺寸及占用位元組 時間列類型取值範圍及占用位元組 ...
  • Cygwin Unable to get setup from * 錯誤 解決方案 是因為用自定義鏡像站點,比如 http://mirrors.xdlinux.info/cygwin/x86_64/ 安裝x86_64位Cygwin時,因為多加了/導致Cygwin安裝程式找不到steup 換成如下即可 ...
  • shell的性質 Linux系統的shell相當於操作系統的“一層外殼”,它是命令語言解釋器,它為用戶提供了使用操作系統的介面。它不屬於內核,而是在內核之外以用戶態方式運行。它的基本功能是解釋並執行用戶打入的各種命令,實現用戶與Linux內核的介面。 個人理解:shell就是普通的用戶態程式,能夠理 ...
  • 前言:學習通配符有點為正則表達式打基礎的感覺……之前學python有學過正則表達式,所以這篇博客學起來還是挺快的。 特殊符號 通配符 他是shell的內置功能通配符,用過DOS的應該很瞭解,也很常用。通配符,指包含這些字元的字元串“?”,“*”,“[]”,{} 通配符含義 >匹配文件名 通配符詳解 ...
  • 不同的軟體的安裝方法是並不相同的。有的軟體只能通過特定的方法來安裝。 在不同的Linux發行版本上安裝同一個軟體的方法也未必相同, 因此有的時候,你在網上找到的在Ubuntu上安裝某個軟體的方法,也許在CentOS上並不能成功。 RPM、YUM、dpkg、apt-get、aptitude ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...