程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> static,extern,tcp

static,extern,tcp

編輯:關於C語言

1、static

全局變量(外部變量)的說明之前再冠以static 就構成了靜態的全局變量。全局變量本身就是靜態存儲方式, 靜態全局變量當然也是靜態存儲方式。

這兩者在存儲方式上並無不同。這兩者的區別雖在於非靜態全局變量的作用域是整個源程序, 當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。 而靜態全局變量則限制了其作用域, 即只在定義該變量的源文件內有效, 在同一源程序的其它源文件中不能使用它。由於靜態全局變量的作用域局限於一個源文件內,只能為該源文件內的函數公用, 因此可以避免在其它源文件中引起錯誤。從以上分析可以看出, 把局部變量改變為靜態變量後是改變了它的存儲方式即改變了它的生存期。把全局變量改變為靜態變量後是改變了它的作用域, 限制了它的使用范圍。


static函數與普通函數有什麼區別?

只在當前源文件中使用的函數應該說明為內部函數(static),內部函數應該在當前源文件中說明和定義。對於可在當前源文件以外使用的函數,應該在一個頭文件中說明,要使用這些函數的源文件要包含這個頭文件。


2、extern

extern可以置於變量或者函數前,以標示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。此外extern也可用來進行鏈接指定。

     也就是說extern有兩個作用,第一個,當它與"C"一起連用時,如: extern "C" void fun(int a, int b);則告訴編譯器在編譯fun這個函數名時按著C的規則去翻譯相應的函數名而不是C++的,C++的規則在翻譯這個函數名時會把fun這個名字變得面目全非,可能是fun@aBc_int_int#%$也可能是別的,這要看編譯器的"脾氣"了(不同的編譯器采用的方法不一樣),為什麼這麼做呢,因為C++支持函數的重載啊,在這裡不去過多的論述這個問題,如果你有興趣可以去網上搜索,相信你可以得到滿意的解釋!
   當extern不與"C"在一起修飾變量或函數時,如在頭文件中: extern int g_Int; 它的作用就是聲明函數或全局變量的作用范圍的關鍵字,其聲明的函數和變量可以在本模塊活其他模塊中使用,記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。

3、 extern 和 static

(1) extern 表明該變量在別的地方已經定義過了,在這裡要使用那個變量.
(2) static 表示靜態的變量,分配內存的時候, 存儲在靜態區,不存儲在棧上面.

   static 作用范圍是內部連接的關系, 和extern有點相反.它和對象本身是分開存儲的,extern也是分開存儲的,但是extern可以被其他的對象用extern 引用,而static 不可以,只允許對象本身用它. 具體差別首先,static與extern是一對“水火不容”的家伙,也就是說extern和static不能同時修飾一個變量;其次,static修飾的全局變量聲明與定義同時進行,也就是說當你在頭文件中使用static聲明了全局變量後,它也同時被定義了;最後,static修飾全局變量的作用域只能是本身的編譯單元,也就是說它的“全局”只對本編譯單元有效,其他編譯單元則看不到它,如:
   (1) test1.h:
   #ifndef TEST1H
   #define TEST1H
   static char g_str[] = "123456";
   void fun1();
   #endif

   (2) test1.cpp:
   #include "test1.h"
   void fun1()  {   cout << g_str << endl;  }
   (3) test2.cpp
   #include "test1.h"
   void fun2()  {   cout << g_str << endl;  }
   以上兩個編譯單元可以連接成功, 當你打開test1.obj時,你可以在它裡面找到字符串"123456",同時你也可以在test2.obj中找到它們,它們之所以可以連接成功而沒有報重復定義的錯誤是因為雖然它們有相同的內容,但是存儲的物理地址並不一樣,就像是兩個不同變量賦了相同的值一樣,而這兩個變量分別作用於它們各自的編譯單元。 也許你比較較真,自己偷偷的跟蹤調試上面的代碼,結果你發現兩個編譯單元test1,test2)的g_str的內存地址相同,於是你下結論static修飾的變量也可以作用於其他模塊,但是我要告訴你,那是你的編譯器在欺騙你,大多數編譯器都對代碼都有優化功能,以達到生成的目標程序更節省內存,執行效率更高,當編譯器在連接各個編譯單元的時候,它會把相同內容的內存只拷貝一份,比如上面的"123456", 位於兩個編譯單元中的變量都是同樣的內容,那麼在連接的時候它在內存中就只會存在一份了,如果你把上面的代碼改成下面的樣子,你馬上就可以拆穿編譯器的謊言:
   (1) test1.cpp:
   #include "test1.h"
   void fun1()
   {
       g_str[0] = ''a'';
       cout << g_str << endl;
   }

   (2) test2.cpp
   #include "test1.h"
   void fun2()  {  cout << g_str << endl;  }
   (3) void main()     {
       fun1(); // a23456
       fun2(); // 123456
   }
   這個時候你在跟蹤代碼時,就會發現兩個編譯單元中的g_str地址並不相同,因為你在一處修改了它,所以編譯器被強行的恢復內存的原貌,在內存中存在了兩份拷貝給兩個模塊中的變量使用。正是因為static有以上的特性,所以一般定義static全局變量時,都把它放在原文件中而不是頭文件,這樣就不會給其他模塊造成不必要的信息污染,同樣記住這個原則吧!

4、extern 和const

  C++中const修飾的全局常量據有跟static相同的特性,即它們只能作用於本編譯模塊中,但是const可以與extern連用來聲明該常量可以作用於其他編譯模塊中, 如extern const char g_str[];
   然後在原文件中別忘了定義:     const char g_str[] = "123456";

   所以當const單獨使用時它就與static相同,而當與extern一起合作的時候,它的特性就跟extern的一樣了!所以對const我沒有什麼可以過多的描述,我只是想提醒你,const char* g_str = "123456" 與 const char g_str[] ="123465"是不同的, 前面那個const 修飾的是char *而不是g_str,它的g_str並不是常量,它被看做是一個定義了的全局變量可以被其他編譯單元使用), 所以如果你像讓char*g_str遵守const的全局常量的規則,最好這麼定義const char* const g_str="123456".


5、TCP協議三次握手連接協議

在TCP/IP協議中,TCP協議提供可靠的連接服務,采用三次握手建立一個連接,如圖1所示。 (SYN包表示標志位syn=1,ACK包表示標志位ack=1,SYN+ACK包表示標志位syn=1,ack=1)

(1) 第一次握手:建立連接時,客戶端A發送SYN包(SEQ_NUMBER=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。

(2) 第二次握手:服務器B收到SYN包,必須確認客戶A的SYN(ACK_NUMBER=j+1),同時自己也發送一個SYN包(SEQ_NUMBER=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。  

(3) 第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包

ACK(ACK_NUMBER=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手。

完成三次握手,客戶端與服務器開始傳送數據。


6、TCP協議四次握手斷開連接協議

由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送(報文段4)。

2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將占用一個序號。

3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A(報文段6)。 4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)


7、TCP連接狀態詳解

CLOSED: 表示初始狀態。LISTEN: 表示服務器端的某個SOCKET處於監聽狀態,可以接受連接。SYN_SENT:在服務端監聽後,客戶端SOCKET執行CONNECT連接時,客戶端發送SYN報文,此時客戶端就進入SYN_SENT狀態,等待服務端的確認SYN_RCVD: 表示服務端接受到了SYN報文,在正常情況下,這個狀態是服務器端的SOCKET在建立TCP連接時的三次握手會話過程中的一個中間狀態,很短暫,基本上用netstat你是很難看到這種狀態的,除非你特意寫了一個客戶端測試程序,故意將三次TCP握手過程中最後一個ACK報文不予發送。因此這種狀態時,當收到客戶端的ACK報文後,它會進入到ESTABLISHED狀態。ESTABLISHED:表示連接已經建立了。FIN_WAIT_1: 這個是已經建立連接之後,其中一方請求終止連接,等待對方的FIN報文。FIN_WAIT_1狀態是當SOCKET在ESTABLISHED狀態時,它想主動關閉連接,向對方發送了FIN報文,此時該SOCKET即進入到FIN_WAIT_1狀態。而當對方回應ACK報文後,則進入到FIN_WAIT_2狀態,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應ACK報文,所以FIN_WAIT_1狀態一般是比較難見到的,而FIN_WAIT_2狀態還有時常常可以用netstat看到。FIN_WAIT_2:實際上FIN_WAIT_2狀態下的SOCKET,表示半連接,也即有一方要求close連接,但另外還告訴對方,我暫時還有點數據需要傳送給你,稍後再關閉連接。TIME_WAIT: 表示收到了對方的FIN報文,並發送出了ACK報文,就等2MSL後即可回到CLOSED可用狀態了。如果FIN_WAIT_1狀態下,收到了對方同時帶FIN標志和ACK標志的報文時,可以直接進入到TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。CLOSING: 這種狀態比較特殊,實際情況中應該是很少見,屬於一種比較罕見的例外狀態。正常情況下,當你發送FIN報文後,按理來說是應該先收到(或同時收到)對方的ACK報文,再收到對方的FIN報文。但是CLOSING狀態表示你發送FIN報文後,並沒有收到對方的ACK報文,反而卻也收到了對方的FIN報文。什麼情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時close一個SOCKET的話,那麼就出現了雙方同時發送FIN報文的情況,也即會出現CLOSING狀態,表示雙方都正在關閉SOCKET連接。CLOSE_WAIT: 這種狀態的含義其實是表示在等待關閉。怎麼理解呢?當對方close一個SOCKET後發送FIN報文給自己,你系統毫無疑問地會回應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正需要考慮的事情是察看你是否還有數據發送給對方,如果沒有的話,那麼你也就可以close這個SOCKET,發送FIN報文給對方,也即關閉連接。所以你在CLOSE_WAIT狀態下,需要完成的事情是等待你去關閉連接。LAST_ACK: 這個狀態還是比較容易好理解的,它是被動關閉一方在發送FIN報文後,最後等待對方的ACK報文。當收到ACK報文後,也即可以進入到CLOSED可用狀態了。


8、查看TCP網絡連接情況

命令:netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'返回結果示例:Cmd代碼  LAST_ACK 5  SYN_RECV 30  ESTABLISHED 15  FIN_WAIT1 51  FIN_WAIT2 5  TIME_WAIT 10






  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved