技術系列之 網路模型(一)基礎篇
全文針對linux環境。tcp/udp兩種server種,tcp相對較複雜也相對比較常用。本文就從tcp server開始講起。先從基本說起,看一個單執行緒的網路模型,處理流程如下:
socket-->bind-->listen-->[accept-->read-->write-->close]-->close[]中程式碼迴圈執行,[]外的是對監聽socket的處理,[]內的是對accept返回的客戶socket的處理。這些系統呼叫的引數以及需要的標頭檔案等,只需要在linux下man就好。
一、注意事項。
(1)包裹巨集使用。這些系統呼叫返回-1表示失敗。檢測系統呼叫的返回值是個好習慣,應該說必須檢測,如果系統呼叫總是成功的話,它為何又要有返回值呢?。每次檢查的話,程式碼寫起來又很是羅唆,並且容易遺漏檢測。使用巨集包裹系統呼叫或者使用包裹函式是不錯的方案。下面給出幾個預定義包裹巨集:
{ /
printf("Line[%d] error[%d:%s]/n",__LINE__,errno,strerror(errno)); /
opt; /
}
#define NOERROR_FUNC_1(func) NOERROR_FUNC(func,return -1)
#define NOERROR_FUNC_NULL(func) NOERROR_FUNC(func,return NULL)
不知道strerror?,剛說了,去linux下:man strerror
以後使用就可以類似於這樣:
NOERROR_FUNC_1(bind(fd,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in)));
(2)不能返回失敗的錯誤。大多數阻塞式系統呼叫要處理EINTR錯誤,另accept還要處理ECONNABORTED。與(1)同樣道理,預定義巨集如下:
{ /
printf("Line[%d] error[%d:%s]/n",
if(errno==err) { erropt;} /
else {opt;} /
}
#define NOERROR_FUNC_BUT_ERR_2(func,opt,err1,err2,erropt) if((func)<0) /
{ /
printf("Line[%d] error[%d:%s]/n",__LINE__,errno,strerror(errno)); /
if(errno==err1||errno==err2) { erropt;} /
else {opt;} /
}
呼叫accept的程式碼就可以如此寫:
while(1){
client_sockfd=accept(fd,(struct sockaddr *)&clientAddr,&lenAddr);
NOERROR_FUNC_BUT_ERR_2(client_sockfd,retun -1,EINTR,ECONNABORTED,continue);
(3)涉及到系統呼叫分兩類:從使用者態到核心態,該類系統呼叫使用值引數,有:bind/setsockopt/connect;從核心態到使用者態,該類系統呼叫使用值-結果引數,有:accept/getsockopt。
看下兩者函式原型,從使用者態到核心態: int setsockopt(int s, int level, int optname, constvoid*optval, socklen_t optlen);
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
int bind(int sockfd,struct sockaddr *Addr,socklen_t addrlen);
從核心態到使用者態:
int getsockopt(int s, int level, int optname, void*optval, socklen_t *optlen);int accept(int sockfd,struct sockaddr *Addr,socklen_t *addrlen);
看最後一個引數,從使用者態到核心態只要告訴核心引數長度的值就可以了,因此是值方式。從核心態到使用者態,要事先準備好變數儲存核心態返回的結果長度值,因此是指標方式,稱之為值-結果引數。
二、系統呼叫
(1)socket建立一個ipv4的tcp socket
(2)bind
把socket繫結到一個地址,首先要指明地址,如下:
NOERROR_FUNC_1(fd=socket(AF_INET,SOCKET_STREAM,0)); struct sockaddr_in addr;
addr.sin_family=AF_INET;//協議型別
addr.sin_port=htons(5000);//埠地址
addr.sin_addr.s_addr=htonl(INADDR_ANY);//此處表示任意ip(主機有多個網絡卡,則將環路地址127.0.0.1以及各網絡卡ip都指定)。
NOERROR_FUNC_1(bind(fd,(struct sockaddr *)addr,sizeof(struct sockaddr_in)));
建立ipv4協議的地址,使用5000埠,接收任何地址的connect,把該地址和fd繫結。
注意:
1、地址宣告的時候使用struct sockaddr_in,使用的時候總是強制轉化為struct sockaddr。
2、struct sockaddr_in結構中埠和ip都必須是網路序。htons把主機序的short int轉化為網路序,htonl把主機序的long int轉化為網路序。
3、除任意ip地址為常量外,一般習慣用點分字串表示ip地址,而addr.sin_addr.s_addr要使用網路序整型。
因此有兩個函式可以在字串和網路序ip地址之間做轉換:
int inet_pton(int af, constchar*src, void*dst);
這裡是需要網路序,因此使用ton(to net)那個函式,比如:
NOERROR_FUNC_1(inet_pton(AF_INET,"172.168.0.45", &addr.sin_addr.s_addr));(3)setsockopt
long val;socklen_t len=sizeof(val);
NOERROR_FUNC_1(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&(val=1),len));
給socket設定選項,常用的不多,SO_REUSEADDR是一個,伺服器一般使用,其它還有SO_RCVBUF,SO_SNDBUF。accept返回的對端socket繼承監聽socket的傳送快取、接收快取選項。一般也不需要設定SO_RCVBUF,SO_SNDBUF,預設的足夠了,頻寬很大的情況下,需要設定,以免其稱為瓶頸,貌似預設的是8092位元組。哦,還有要在listen前設定。
(4)listen把fd從主動埠變為被動埠,等待client connect。第二個引數是表示三次握手中佇列以及完成了三次握手等待accept系統函式來取的佇列的相加值,有的系統不是簡單相加,還有一個係數,也就是如果設定5,係數是2,那麼兩個佇列的和就是10。如果佇列滿,而accept沒來取(很忙的情況下,來不及呼叫accept),再有連線來就會被拒絕掉,要想系統能處理超大爆發的連線,就加大這個引數值,加快accept的處理。SOMAXCONN表示取系統允許的最大值。
(5)accept
前面已經舉例了,這裡就不再列例子了。
阻塞式呼叫,需要處理EINTR(被訊號終止),ECONNABORTED(返回前client異常終止),處理的方式就是重新accept。
(6)read這是針對檔案描述符的一個系統呼叫,socket也屬於檔案描述符。tcp協議中傳輸的資料都是流位元組,沒有什麼結束符的標誌,只能由協議提供結束方式,比如http協議使用"/r/n/r/n"或者"/n/n"標識一條信令結束,這樣的話,我們只能一個位元組一個位元組的讀取,然後結合已經讀取的位元組,判斷是否應該結束讀。而網路模型中要提高效能,一個重要方面就是要減少系統呼叫的次數。因此tcp中都要使用快取區一次讀取儘可能多的資料,然後再從該快取區一個位元組一個位元組的讀取,快取區資料被讀完而沒有到結束位置的時候,再次呼叫系統呼叫read。
返回值為0表示對端正常關閉,大於0表示讀取到的位元組數。示例見最後例子。
(7)write兩個需要注意的地方:
1、對EINTR處理。防止被訊號中斷,沒有正確寫入需求的字元數。
2、signal(SIGPIPE, SIG_IGN);這句程式碼的意思是忽略SIGPIPE訊號。
write寫被重置(對端意外關閉)的套介面,產生SIGPIPE訊號,不處理的話程式被終止。忽略的話,繼續寫會產生EPIPE錯誤,檢查write系統呼叫的返回結果就好了。示例見最後例子。
signal的使用,man下就看到了,回撥函式的原型等都有,SIG_IGN也會出現,呵呵。
(8)close就不說了
(9)fcntl
要對socket設定為非阻塞方式,setsockopt沒有提供相應的選項,只能用fcntl函式設定。
NOERROR_FUNC_1(flags=fcntl(client_sockfd,F_GETFL,0));
NOERROR_FUNC_1(fcntl(client_sockfd,F_SETFL,flags|O_NONBLOCK));
多路分離I/O(select/poll/epoll)通常設定為非阻塞方式。
設定為阻塞方式(預設方式)程式碼:
NOERROR_FUNC_1(flags=fcntl(client_sockfd,F_GETFL,0));
NOERROR_FUNC_1(fcntl(client_sockfd,F_SETFL,flags&~O_NONBLOCK));
對於阻塞方式的套介面,如果要避免read write永遠阻塞,設定等待時間的方式有3種:訊號方式,不推薦,不說了;select方式,每次呼叫read前呼叫select監視該套介面是否在指定時間內可寫,超時select返回0,這樣每次執行read都要呼叫兩個系統呼叫,不推薦;最後就是設定套介面選項SO_RECVTIMEO和SO_SNDTIMEO,其實這個也不推薦,總之不推薦阻塞式的方式,呵呵。實用的網路模型都是多路分離的。
非阻塞方式下的connect函式要說下,當然是就客戶端而言,connect後如果沒有立即返回連線成功的話,把這個socket加入select的 fd_set(poll的pollfd,epoll的EPOLL_CTL_ADD操作),要監視是否可寫事件,可寫的時候用getsockopt獲取SO_ERROR選項,如果非負(其實就是0值)就標示connect成功,否則就是失敗。EPOLL中測試結果是connect失敗的返回事件是EPOLLERR|EPOLLHUP,並不是加入時的EPOLLOUT,成功的時候是EPOLLOUT。
三、示例
最後給個單執行緒的伺服器,雖說沒什麼實用意義,不過就象“hello world!”,入門第一課。
這個例子,讀取資料,回寫response,關閉clientfd。不管read write是否出錯,都執行close,因此程式碼很簡單。
先來main函式:
{
int server_sockfd;
int client_sockfd;
struct sockaddr_in serverAddr;
struct sockaddr_in clientAddr;
size_t lenAddr;
int val;
memset(&serverAddr,0,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(5000);
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
NOERROR_FUNC_1((server_sockfd=socket(AF_INET,SOCK_STREAM,0)));
NOERROR_FUNC_1(setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&(val=1),sizeof(val)));
NOERROR_FUNC_1(bind(server_sockfd,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in)));
NOERROR_FUNC_1(listen(server_sockfd,SOMAXCONN));
conststaticchar* response="HTTP/1.1 200 OK/r/n/r/n";
char buf[BUF_LEN];
signal(SIGPIPE, SIG_IGN);
while(1)
{
client_sockfd=accept(server_sockfd,(struct sockaddr *)&clientAddr,&lenAddr);
NOERROR_FUNC_BUT_ERR_2(client_sockfd,return-1,EINTR,ECONNABORTED,continue);
BuffCache cache;
if(read_double_enter(client_sockfd,buf,BUF_LEN,&cache)>0)
writen(client_sockfd,response,19);
close(client_sockfd);
}
close(server_sockfd);
return0;
}
下面是包含的標頭檔案和巨集:
#include <unistd.h>#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define NOERROR_FUNC(func,opt) if((func)<0) /
{ /
printf("Line[%d] error[%d:%s]/n",__LINE__,errno,strerror(errno)); /
opt; /
}
#define NOERROR_FUNC_BUT_ERR(func,opt,err,erropt) if((func)<0) /
{ /
printf("Line[%d] error[%d:%s]/n",__LINE__,errno,strerror(errno)); /
if(errno==err) { erropt;} /
else{opt;} /
}
#define NOERROR_FUNC_BUT_ERR_2(func,opt,err1,err2,erropt) if((func)<0) /
{ /
printf("Line[%d] error[%d:%s]/n",__LINE__,errno,strerror(errno)); /
if(errno==err1||errno==err2) { erropt;} /
else{opt;} /
}
#define NOERROR_FUNC_1(func) NOERROR_FUNC(func,return-1)
#define NOERROR_FUNC_NULL(func) NOERROR_FUNC(func,return NULL)
#define BUF_LEN 1024
下面是快取區和讀寫程式碼:
{
public:
BuffCache():count(0){}
int read_socket(int fd,char* pCh)
{
if(count<=0)
{
again:
if((count=read(fd,buf,BUF_LEN))<0)
{
if(errno==EINTR)
goto again;
*pCh='/0';
return-1;
}
elseif(count==0)
{
*pCh='/0';
return0;
}
ptrBuf=buf;
}
count--;
*pCh=*(ptrBuf++);
return1;
}
private:
char buf[BUF_LEN];
相關推薦
技術系列之 網路模型(一)基礎篇
全文針對linux環境。tcp/udp兩種server種,tcp相對較複雜也相對比較常用。本文就從tcp server開始講起。先從基本說起,看一個單執行緒的網路模型,處理流程如下: socket-->bind-->listen-->[accept-->
技術系列之 網路模型(二)
作者:CppExplore 網址:http://www.cppblog.com/CppExplore/本章主要列舉伺服器程式的各種網路模型,示例程式以及效能對比後面再寫。一、分類依據。伺服器的網路模型分類主要依據以下幾點(1)是否阻塞方式處理請求,是否多路複用,使用哪種多路複
【原創】技術系列之 執行緒(一)
作者:CppExplore 網址:http://www.cppblog.com/CppExplore/廢話不多說,詳細介紹使用執行緒的優點好處請參考baidu、google。一、執行緒使用場景。使用執行緒的方式大致有兩種:(1)流水線方式。根據業務特點,將一個流程的處理分割成多個執行緒,形成流水線的處
【原創】技術系列之 狀態機(一)
作者:CppExplore 網址:http://www.cppblog.com/CppExplore/一、狀態機描述狀態機理論最初的發展在數位電路設計領域。在數位電路方面,根據輸出是否與輸入訊號有關,狀態機可以劃分為Mealy型和Moore型狀態機;根據輸出是否與輸入訊號同步,狀態機可以劃分為非同步和
技術系列之 定時器(一)
一、 基礎知識1、時間型別。Linux下常用的時間型別有4個:time_t,struct timeval,struct timespec,struct tm。(1)time_t是一個長整型,一般用來表示用1970年以來的秒數。(2)Struct timeval有兩個成員,一個
入門到放棄node系列之網路模組(一)
前言 本文首發公眾號【一名打字員】 上一次相信大家都基本瞭解node的用法了,有做功課的童鞋肯定回去溫習了一下js的語法。這些年來js發展很快,出了很多類似許多vue、react、node等等眾所周知的玩意兒,對應的社群配套也越來越完善。好的,接下來我們補充
反向教學系列之——PHP入門(一)
water oui 版本 名稱 令行 完全 技術 地址 安裝目錄 php是什麽?其實就是html的功能加強版。網頁本來在服務器上,如果客戶端問服務器索取網頁文件(xxx.html),那麽服務器就會把客戶端指定的網頁發回去。(根據我的理解,)php是因“表單”而誕生的,所謂表
反向教學系列之——Django入門(一)【不需知道web框架】
Django 教程 反向教學 一派胡言 用這東西最終是建網站的,或者是更一般意義的服務器。服務器麽,就是如果用別的電腦(“客戶機”)給它發請求,它會返回一些東西——如果給隨便某個機器發信息,它自然未必理你。要想某機器回應你,得滿足這些條件——它不處在關機狀態它能收到你的信息,你也能收到它的信息
C/S+P2P網路模型(一)--聊天
原文地址 從今天開始我們來實現一個C/S+P2P網路模型,主要功能包括:聊天和傳輸檔案。 聊天分群聊和私聊,雖然不是真的像QQ那樣有個QQ群,但是這樣類比容易理解,在後面我們會仔細說明。 前面我寫過一篇文章講過組播,我們就用組播來實現p2p網路模型。首先而且是最關鍵的一點是:每一個程式例
【原創】技術系列之 記憶體管理(三)
作者:CppExplore 地址:http://www.cppblog.com/CppExplore/(2)boost::pool系列。boost的記憶體池最低層是simple_segregated_storage,類似於Loki中的chunk,在其中申請釋放block(boost中把block稱為c
【原創】技術系列之 狀態機(二)
與常規狀態機相比,它的FSM_STATE結構沒有default_func,多了 FSM_STATE_ID parent; FSM_STATE_ID default_child;兩個結構。狀態機初始化的時候可以指定預設狀態,為了防止指定的狀態非葉結點,增加fsm_init方法。該狀態機的事件處理演算
細說網路那些事兒之網路基本功(一):細說網路傳輸
網路基本功(一):細說網路傳輸 常言道:欲練神功,必先練好基本功。之前做了一個關於IP路由,預設閘道器和掩碼的問答貼,做完這個帖子覺得如果對網路知識點做一個系統的闡述,應該會很有幫助。 本系列文章著重於講解網路管理實際應用中常常涉及的重要知識點,儘量以實用為主。準備
技術系列之 記憶體管理(二)
2、定長記憶體池。典型的實現有LOKI、BOOST。特點是為不同型別的資料結構分別建立記憶體池,需要記憶體的時候從相應的記憶體池中申請記憶體,優點是可以在使用完畢立即把記憶體歸還池中,可以更為細粒度的控制記憶體塊。 與變長的相比,這種型別的記憶體池更加通用,另一方面對於
技術系列之 記憶體管理(三)
(2)boost::pool系列。boost的記憶體池最低層是simple_segregated_storage,類似於Loki中的chunk,在其中申請釋放block(boost中把block稱為chunk,暈死,這裡還是稱其為block)採用了和loki的chunk中同樣
計算機網路複習總結之網路層(一)
最近也準備臨近考試月了,抽時間精簡總結一下《計算機網路》。這是一篇關於計算機網路的第三層,網路層相關的知識。 在計算機網路的分層中,網路層的作用就是將分組從源主機沿網路路徑發到目的主機上,所以網路層裡最核心的功能就是:分組轉發 和 路由選擇。 分組轉發和路由
C語言之網路程式設計(一)域名解析
在網路程式設計時,知道域名是不能直接訪問一個主機的,需要轉換成相應的IP地址。有時在程式中需要將一個IP地址轉換成一個域名。本節將講解C程式中的IP地址與域名的轉換問題。 提示:在TCP/IP網路中,通訊雙方的主機必須知道彼此的IP地址方可進行正常的通訊,如果給出的主機的域
OJ 系列之常規練習題(一)
1、求M的n次方最後三位 【問題描述】 正整數M 的N次方有可能是一個非常大的數字,我們只求該數字的最後三位 例1:比如輸入5和3 ,5的3次方為125,則輸出為125 例2:比如輸入2和10 2的10次方為1024 ,則輸出結果為24 例3:比如
技術系列之 執行緒(二)
為了後面寫的《網路模型(二)》,多寫一篇關於執行緒的。執行緒使用涉及的主要資料結構以及應用框架可以參考http://www.cppblog.com/CppExplore/archive/2008/01/15/41175.html。本文的主要目的是給出linux下實用的執行緒訊
springboot原始碼解析-管中窺豹系列之總體結構(一)
# 一、簡介 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.
網路管理(一)基礎知識
1、使用ipconfig命令檢視計算機中所有介面卡的TCP/IP配置資訊 ipconfig命令的作用是顯示所有當前的TCP/IP網路配置值、重新整理動態主機配置協議情況(DHCP)和域名設定(DNS)。 引數說明如下: