必不可少需要掌握的嵌入式知識(1) -- 網絡編程

TCP(Transmission Control Protocol, 傳輸控制協議)/UDP(User Data Protocol, 用戶數據協議) 的區別與聯繫:

TCP協議與UDP協議的區別
首先咱們弄清楚,TCP協議和UDP協議與TCP/IP協議的聯繫,很多人犯糊塗了,一直都是說TCP/IP協議與UDP協議的區別,我覺得這是沒有從本質上弄清楚網絡通信!

TCP/IP協議是一個協議簇。裏面包括很多協議的。UDP只是其中的一個。之所以命名爲TCP/IP協議,因爲TCP,IP協議是兩個很重要的協議,就用他兩命名了。
TCP/IP協議集包括應用層,傳輸層,網絡層,網絡訪問層。
其中應用層包括:
超文本傳輸協議(HTTP):萬維網的基本協議.
文件傳輸(TFTP簡單文件傳輸協議):
遠程登錄(Telnet),提供遠程訪問其它主機功能,它允許用戶登錄
internet主機,並在這臺主機上執行命令.
網絡管理(SNMP簡單網絡管理協議),該協議提供了監控網絡設備的方法,以及配置管理,統計信息收集,性能管理及安全管理等.
域名系統(DNS),該系統用於在internet中將域名及其公共廣播的網絡節點轉換成IP地址.
其次網絡層包括:
Internet協議(IP)
Internet控制信息協議(ICMP)
地址解析協議(ARP)
反向地址解析協議(RARP)
最後說網絡訪問層:網絡訪問層又稱作主機到網絡層(host-to-network).網絡訪問層的功能包括IP地址與物理地址硬件的映射,以及將IP封裝成幀.基於不同硬件類型的網絡接口,網絡訪問層定義了和物理介質的連接.
當然我這裏說得不夠完善,TCP/IP協議本來就是一門學問,每一個分支都是一個很複雜的流程,但我相信每位學習軟件開發的同學都有必要去仔細瞭解一番。
下面我着重講解一下TCP協議和UDP協議的區別:
TCP(Transmission Control Protocol,傳輸控制協議)是面向連接的協議,也就是說,在收發數據前,必須和對方建立可靠的連接。一個TCP連接必須要經過三次“對話”才能建立起來,其中的過程非常複雜,只簡單的描述下這三次對話的簡單過程:主機A向主機B發出連接請求數據包:“我想給你發數據,可以嗎?”,這是第一次對話;主機B向主機A發送同意連接和要求同步(同步就是兩臺主機一個在發送,一個在接收,協調工作)的數據包:“可以,你什麼時候發?”,這是第二次對話;主機A再發出一個數據包確認主機B的要求同步:“我現在就發,你接着吧!”,這是第三次對話。三次“對話”的目的是使數據包的發送和接收同步,經過三次“對話”之後,主機A才向主機B正式發送數據。
詳細點說就是:(文章部分轉載http://zhangjiangxing-gmail-com.iteye.com,主要是這個人講解得很到位,的確很容易使人理解!)
TCP三次握手過程:
1, 主機A通過向主機B 發送一個含有同步序列號的標誌位的數據段給主機B ,向主機B 請求建立連接,通過這個數據段,主機A告訴主機B 兩件事:我想要和你通信;你可以用哪個序列號作爲起始數據段來回應我.
2, 主機B 收到主機A的請求後,用一個帶有確認應答(ACK)和同步序列號(SYN)標誌位的數據段響應主機A,也告訴主機A兩件事:我已經收到你的請求了,你可以傳輸數據了;你要用哪個序列號作爲起始數據段來回應我
3, 主機A收到這個數據段後,再發送一個確認應答,確認已收到主機B 的數據段:”我已收到回覆,我現在要開始傳輸實際數據了這樣3次握手就完成了,主機A和主機B 就可以傳輸數據了.
3次握手的特點:
沒有應用層的數據
SYN這個標誌位只有在TCP建產連接時纔會被置1
握手完成後SYN標誌位被置0

TCP建立連接要進行3次握手,而斷開連接要進行4次:

1, 當主機A完成數據傳輸後,將控制位FIN置1,提出停止TCP連接的請求
2,  主機B收到FIN後對其作出響應,確認這一方向上的TCP連接將關閉,將ACK置1
3, 由B 端再提出反方向的關閉請求,將FIN置1
4, 主機A對主機B的請求進行確認,將ACK置1,雙方向的關閉結束.
由TCP的三次握手和四次斷開可以看出,TCP使用面向連接的通信方式,大大提高了數據通信的可靠性,使發送數據端和接收端在數據正式傳輸前就有了交互,爲數據正式傳輸打下了可靠的基礎
名詞解釋:
ACK  TCP報頭的控制位之一,對數據進行確認.確認由目的端發出,用它來告訴發送端這個序列號之前的數據段都收到了.比如,確認號爲X,則表示前X-1個數據段都收到了,只有當ACK=1時,確認號纔有效,當ACK=0時,確認號無效,這時會要求重傳數據,保證數據的完整性.

SYN 同步序列號,TCP建立連接時將這個位置1
FIN 發送端完成發送任務位,當TCP完成數據傳輸需要斷開時,提出斷開連接的一方將這位置1
TCP的包頭結構:
源端口 16位
目標端口 16位
序列號 32位
迴應序號 32位
TCP頭長度 4位
reserved 6位
控制代碼 6位
窗口大小 16位
偏移量 16位
校驗和 16位
選項 32位(可選)
這樣我們得出了TCP包頭的最小長度,爲20字節。

UDP(User Data Protocol, 用戶數據協議):
(1) UDP是一個非連接的協議,傳輸數據之前源端和終端不建立連接,當它想傳送時就簡單地去抓取來自應用程序的數據,並儘可能快地把它扔到網絡上。在發送端,UDP傳送數據的速度僅僅是受應用程序生成數據的速度、計算機的能力和傳輸帶寬的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。
(2) 由於傳輸數據不建立連接,因此也就不需要維護連接狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的消息。
(3) UDP信息包的標題很短,只有8個字節,相對於TCP的20個字節信息包的額外開銷很小。
(4) 吞吐量不受擁擠控制算法的調節,只受應用軟件生成數據的速率、傳輸帶寬、源端和終端主機性能的限制。
(5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的鏈接狀態表(這裏面有許多參數)。
(6)UDP是面向報文的。發送方的UDP對應用程序交下來的報文,在添加首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程序需要選擇合適的報文大小。

我們經常使用“ping”命令來測試兩臺主機之間TCP/IP通信是否正常,其實“ping”命令的原理就是向對方主機發送UDP數據包,然後對方主機確認收到數據包,如果數據包是否到達的消息及時反饋回來,那麼網絡就是通的。
UDP的包頭結構:
源端口 16位
目的端口 16位
長度 16位
校驗和 16位

小結TCP與UDP的區別:
1.基於連接與無連接;
2.對系統資源的要求(TCP較多,UDP少);
3.UDP程序結構較簡單;
4.流模式與數據報模式 ;
5.TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。

TCP\UDP編程步驟:

    TCP編程的服務器端一般步驟是: 

  1、創建一個socket,用函數socket();
  2、設置socket屬性,用函數setsockopt(); * 可選
  3、綁定IP地址、端口等信息到socket上,用函數bind();
  4、開啓監聽,用函數listen();
  5、接收客戶端上來的連接,用函數accept();
  6、收發數據,用函數send()和recv(),或者read()和write();
  7、關閉網絡連接;
  8、關閉監聽;

  TCP編程的客戶端一般步驟是:
  1、創建一個socket,用函數socket();
  2、設置socket屬性,用函數setsockopt();* 可選
  3、綁定IP地址、端口等信息到socket上,用函數bind();* 可選
  4、設置要連接的對方的IP地址和端口等屬性;
  5、連接服務器,用函數connect();
  6、收發數據,用函數send()和recv(),或者read()和write();
  7、關閉網絡連接;

  與之對應的UDP編程步驟要簡單許多,分別如下:
  UDP編程的服務器端一般步驟是:
  1、創建一個socket,用函數socket();
  2、設置socket屬性,用函數setsockopt();* 可選
  3、綁定IP地址、端口等信息到socket上,用函數bind();
  4、循環接收數據,用函數recvfrom();
  5、關閉網絡連接;

  UDP編程的客戶端一般步驟是:
  1、創建一個socket,用函數socket();
  2、設置socket屬性,用函數setsockopt();* 可選
  3、綁定IP地址、端口等信息到socket上,用函數bind();* 可選
  4、設置對方的IP地址和端口等屬性;
  5、發送數據,用函數sendto();
  6、關閉網絡連接;

linux 進程間通信方式:

進程間通信方式有7種,分別是:

1,管道(pipe):管道是一種半雙工的通信方式,數據只能單向流動,且只能在有親緣關係(父子)的進程間使用。

2,有名管道(name pipe):是一種半雙工的通信方式,但不需要有親緣關係也可以使用。

3,信號量(semophore):信號量其實是一個計數器,可以用來控制多個進程對共享資源的訪問。它常常作爲一種鎖機制,用來防止某個進程正在訪問共享資源時,其他進程也訪問該資源。因此,常用來作爲不同進程間或同一進程內不同線程之間的同步方式。

4,消息隊列(message queue):消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。

5,信號(signal):信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。

6,共享內存(shared memory):共享內存就是映射一段其他進程能夠訪問的內存,這段共享內存被一個進程所創建,但是可以被其他多個進程訪問。共享內存是最快的IPC方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往和其他進程間通信方式如“信號量”配合使用,來實現進程間的同步和通信。

7,套接字(socket):套接字與其他進程間通信方式的區別是,可用於不同機器間的進程通信。

linux 線程間的同步方式:

各個線程可以訪問進程中的公共變量,資源。所以使用多線程的過程中需要注意的問題是如何防止兩個或兩個以上的進程同時訪問同一數據,破壞數據的完整性。數據之間的相互制約包括:

1,直接制約關係:一個線程的處理結果,爲另一個線程的輸入,因此線程之間直接制約着,這種關係可以稱之爲同步關係。

2,間接制約關係:兩個線程需要訪問同一資源,該資源在同一時刻只能被一個線程訪問,這種關係稱之爲線程間對資源的互斥訪問,某種意義上說互斥是一種制約關係更小的同步。

線程之間同步方式有3種:

互斥鎖:

初始化鎖。在Linux下,線程的互斥量數據類型是pthread_mutex_t。在使用前,要對它進行初始化。
靜態分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
動態分配:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
加鎖。對共享資源的訪問,要對互斥量進行加鎖,如果互斥量已經上了鎖,調用線程會阻塞,直到互斥量被解鎖。
int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
解鎖。在完成了對共享資源的訪問後,要對互斥量進行解鎖。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
銷燬鎖。鎖在是使用完成後,需要進行銷燬以釋放資源。
int pthread_mutex_destroy(pthread_mutex *mutex);
條件變量:

    與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生爲止。通常條件變量和互斥鎖同時使用。條件變量分爲兩部分: 條件和變量。條件本身是由互斥量保護的。線程在改變條件狀態前先要鎖住互斥量。條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。條件的檢測是在互斥鎖的保護下進行的。如果一個條件爲假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。

初始化條件變量。
靜態態初始化,pthread_cond_t cond = PTHREAD_COND_INITIALIER;
動態初始化,int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
等待條件成立。釋放鎖,同時阻塞等待條件變量爲真才行。timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
激活條件變量。pthread_cond_signal,pthread_cond_broadcast(激活所有等待線程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有線程的阻塞
清除條件變量。無線程等待,否則返回EBUSY
int pthread_cond_destroy(pthread_cond_t *cond);
信號量:

    如同進程一樣,線程也可以通過信號量來實現通信,雖然是輕量級的。信號量函數的名字都以"sem_"打頭。線程使用的基本信號量函數有四個。

信號量初始化。
int sem_init (sem_t *sem , int pshared, unsigned int value);
這是對由sem指定的信號量進行初始化,設置好它的共享選項(linux 只支持爲0,即表示它是當前進程的局部信號量),然後給它一個初始值VALUE。
等待信號量。給信號量減1,然後等待直到信號量的值大於0。
int sem_wait(sem_t *sem);
釋放信號量。信號量值加1。並通知其他等待線程。
int sem_post(sem_t *sem);
銷燬信號量。我們用完信號量後都它進行清理。歸還佔有的一切資源。
int sem_destroy(sem_t *sem);

Linux五種I/O模型:

阻塞I/O:

應用程序調用一個I/O函數,導致應用程序阻塞,等待數據準備好。如果數據沒有準備好,一直等待......數據準備好了,從內核拷貝到用戶空間,I/O函數返回成功指示。

非阻塞I/O:

我們把一個套接口設置爲非阻塞就是告訴內核,當所請求的I/O操作無法完成時,不要將進程睡眠,而是返回一個錯誤。這樣我們的I/O操作函數將不斷的測試數據是否已經準備好。如果沒有準備好,繼續測試,直到數據準備好爲止。在這個不斷測試的過程中,會大量佔用CPU的資源。

I/O複用(select、poll和epoll):

I/O複用通過一種機制,可以監視多個描述符,一旦某個描述符準備就緒(一般是讀就緒或寫就緒),能夠通知程序進行相應的讀寫操作。I/O複用模型會用到select、poll函數,這兩個函數也會使進程阻塞,但是和阻塞I/O所不同的,這兩個函數可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操作函數。epoll是Linux內核爲處理大批量文件描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

select、poll、epoll的區別:

select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒多次交替。但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程,雖然都要睡眠和喚醒多次交替,但是select和poll在醒着的時候需要遍歷整個fd集合,而epoll在醒着的時候只需要判斷一下就緒鏈表是否爲空就行了,這節省了大量CPU時間。這就是回調機制帶來的性能提升。

select和poll函數每次調用都要把fd集合從用戶態向內核態拷貝一次,並且要把current往設備等待隊列裏掛一次,而epoll只需要一次拷貝,而且也只需要把current往設備等待隊列裏掛一次,(在epoll_wait的開始,注意這裏的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少開銷。

信號驅動I/O(SIGIO):

首先我們允許套接口進行信號驅動I/O,並安裝一個信號處理函數,進程繼續運行並不阻塞。當數據準備好時,進程會收到一個SIGIO信號,可以在信號處理函數中調用I/O操作函數處理數據。

異步I/O(Posix.1的aio_系列函數):

當一個異步過程調用發生後,調用者不能立刻得到結果。實際處理調用這個部件在完成後,通過狀態、通知和回調來通知調用者的輸入輸出操作。
發佈了31 篇原創文章 · 獲贊 19 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章