(1)bind 函數(shù)如何選擇綁定地址:bind 函數(shù)的基本用法如下:
? ? ? ? ? ? ? ?? struct sockaddr_in? bindaddr;
? ? ? ? ? ? ? ?? bindaddr.sin_family?=?AF_INET;
? ? ? ? ? ? ? ?? bindaddr.sin_addr.s_addr?=?htonl(INADDR_ANY); //
? ? ? ? ? ? ? ?? bindaddr.sin_port?=?htons(3000);
? ? ? ? ? ? ? ? if?(bind(listenfd,?(struct?sockaddr?*)&bindaddr,?sizeof(bindaddr))?==?-1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? std::cout?<<?"bind?listen?socket?error."?<<?std::endl;
? ? ? ? ? ? ? ? ? ? ? ? return?-1;
? ? ? ? ? ? ? ? }
? ? 如上所示,INADDR_ANY是一個宏,相當于地址0.0.0.0厨幻,如果應(yīng)用程序不關(guān)心bind綁定的ip地址愕掏,可以使用INADDR_ANY(如果是IPv6妇汗,則對應(yīng)in6addr_any)陋桂,這樣底層的(協(xié)議棧)服務(wù)會自動選擇
一個合適的ip地址蜓竹,這樣使在一個有多個網(wǎng)卡機器上選擇ip地址問題變得簡單耗绿。bind函數(shù)可以綁定多個可選的IP苹支,如果指定本機訪問,可設(shè)置127.0.0.1误阻,如果是局域網(wǎng)內(nèi)部訪問债蜜,設(shè)置內(nèi)網(wǎng)IP晴埂,如果是外
網(wǎng)訪問,設(shè)置0.0.0.0或?INADDR_ANY寻定。
(2)bind函數(shù)綁定端口號:如果將 bind 函數(shù)中的端口號設(shè)置成0儒洛,那么操作系統(tǒng)會隨機給程序分配一個可用的偵聽端口,windows的端口號是0~65535狼速,實際可用的更少琅锻,此外,除了服務(wù)器可以調(diào)
用bind綁定指定端口外向胡,客戶端也可以調(diào)用以指定的端口號去連接服務(wù)器恼蓬。另外,Linux 的?nc?命令有個?-p?選項(字母?p?是小寫)僵芹,這個選項的作用就是?nc?在模擬客戶端程序時处硬,可以使用指定端口
號連接到服務(wù)器程序上去,比如 命令nc?-v?-p?9999?127.0.0.1?3000 則指定9999區(qū)連接127.0.0.1 的3000端口拇派。
(3)socket的阻塞和非阻塞模式:所謂阻塞模式郁油,就當某個函數(shù)“執(zhí)行成功的條件”當前不能滿足時,該函數(shù)會阻塞當前執(zhí)行線程攀痊,程序執(zhí)行流在超時時間到達或“執(zhí)行成功的條件”滿足后恢復(fù)繼續(xù)執(zhí)
行桐腌。而非阻塞模式恰恰相反,即使某個函數(shù)的“執(zhí)行成功的條件”不當前不能滿足苟径,該函數(shù)也不會阻塞當前執(zhí)行線程案站,而是立即返回,繼續(xù)運行執(zhí)行程序流棘街。無論是 Windows 還是 Linux 平臺蟆盐,默認創(chuàng)建
的 socket 都是阻塞模式的匀哄。
? ? ? ? ? ? windows下使用ioctlsocket() 函數(shù)?將 socket 設(shè)置成非阻塞模式吻氧,ioctlsocket()?簽名如下:
? ? ? ? ? ? ? ? ? ? ? int ioctlsocket(SOCKET?s,long cmd,?u_long?*argp); ? //cmd?參數(shù)設(shè)置為?FIONBIO关斜,argp設(shè)置為0即可將 socket 設(shè)置成阻塞模式在塔,而將argp?設(shè)置成非?0?即可設(shè)置成非阻塞模式
? ? ? ? ?? linux下有三種方式可將socket設(shè)置成 非阻塞模式:
? ? ? ? ?? 1.使用?fcntl() 函數(shù)或?ioctl() 函數(shù)給創(chuàng)建的 socket 增加?O_NONBLOCK?標志來將 socket 設(shè)置成非阻塞模式:
? ? ? ? ? ? ? ? ? ?? int?oldSocketFlag?=?fcntl(sockfd,?F_GETFL,?0);
? ? ? ? ? ? ? ? ? ? int?newSocketFlag?=?oldSocketFlag?|?O_NONBLOCK;
? ? ? ? ? ? ? ? ? ? fcntl(sockfd,?F_SETFL,??newSocketFlag);
? ? ? ? ? 2.socket()?創(chuàng)建函數(shù)時直接設(shè)置非阻塞,函數(shù)簽名如:int socket(int domain,int type,int protocol);給參數(shù)type?參數(shù)增加一個?SOCK_NONBLOCK?標志即可蒸痹,如下:
? ? ? ? ? ? ? ? ? ?? int?s?=?socket(AF_INET,?SOCK_STREAM?|?SOCK_NONBLOCK,?IPPROTO_TCP);
? ? ? ?? 3.使用擴展函數(shù)?accept4()仅仆,直接將 accept 函數(shù)返回的 socket 設(shè)置成非阻塞的入篮,函數(shù)簽名:int accept4(int sockfd,?struct?sockaddr?*addr,socklen_t* addrlen,int flags); 例如:
? ? ? ? ? ? ? ? ? ? int?clientfd?=?accept4(listenfd,?&clientaddr,?&addrlen,?SOCK_NONBLOCK);
? ? ? ?? send 函數(shù)本質(zhì)上并不是往網(wǎng)絡(luò)上發(fā)送數(shù)據(jù)蛔糯,而是將應(yīng)用層發(fā)送緩沖區(qū)的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)中去拯腮,至于什么時候數(shù)據(jù)會從網(wǎng)卡緩沖區(qū)中真正地發(fā)到網(wǎng)絡(luò)中去要根據(jù) TCP/IP 協(xié)議棧的行為來確
定,這種行為涉及到一個叫 nagel 算法和 TCP_NODELAY 的 socket 選項蚁飒。recv 函數(shù)本質(zhì)上也并不是從網(wǎng)絡(luò)上收取數(shù)據(jù)动壤,而只是將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到應(yīng)用程序的緩沖區(qū)中,當然拷貝完成以
后會將內(nèi)核緩沖區(qū)中該部分數(shù)據(jù)移除淮逻。
? ? ? ? 當 socket 是阻塞模式的琼懊,繼續(xù)調(diào)用 send/recv 函數(shù)會導(dǎo)致程序阻塞在 send/recv 調(diào)用處阁簸;當 socket 是非阻塞模式,繼續(xù)調(diào)用 send/recv 函數(shù)哼丈,send/recv 函數(shù)不會阻塞程序執(zhí)行流启妹,而是會立即
出錯返回-1,我們會得到一個相關(guān)的錯誤碼削祈,Linux 平臺上該錯誤碼為 EWOULDBLOCK 或 EAGAIN(這兩個錯誤碼值相同),Windows 平臺上錯誤碼為 WSAEWOULDBLOCK脑漫。
(4)nagle算法:nagle算法的是操作系統(tǒng)網(wǎng)絡(luò)通信層的一種發(fā)送數(shù)據(jù)包機制髓抑,如果開啟,則一次放入網(wǎng)卡緩沖區(qū)中的數(shù)據(jù)(利用send或write等)較小時优幸,可能不會立即發(fā)出去吨拍,只要當多次send或者
write之后,網(wǎng)卡緩沖區(qū)中的數(shù)據(jù)足夠多時网杆,才會一次性被協(xié)議棧發(fā)送出去羹饰,操作系統(tǒng)利用這個算法減少網(wǎng)絡(luò)通信次數(shù),提高網(wǎng)絡(luò)利用率碳却。對于實時性要求比較高的應(yīng)用來說队秩,可以禁用nagle算法。這
樣send或write的小數(shù)據(jù)包會立刻發(fā)出去昼浦。系統(tǒng)默認是開啟的馍资,禁用方法如下:
? ? ? ? long noDelay = 1; ?
? ? ?? setsockopt(m_hSocket, IPPROTO_TCP, TCP_NODELAY,(LPSTR)&noDelay, sizeof(long)); ? ? ? //noDelay為1-禁用,0-啟用
(5)linux epoll模型:Linux 從內(nèi)核 2.6引入epoll模型,首先創(chuàng)建fd函數(shù):
? ? ?? #include<sys/epoll.h>
? ? ?? int epoll_create(int size);? //size?從 Linux 2.6.8 以后就不再使用关噪,但是必須設(shè)置一個大于 0 的值 ,返回-1表示創(chuàng)建失敗
? ? ?? int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event); // 綁定事件,返回-1表示失敗鸟蟹,參數(shù)?epfd?即上文提到的 epollfd,參數(shù)?op表示操作類型使兔,EPOLL_CTL_ADD(添加)建钥、
EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(刪除)虐沥,當操作類型為NULL時熊经,第四個參數(shù) 可以設(shè)置NULL,參數(shù)?fd欲险,即需要被操作的 fd奈搜,參數(shù)?event,一個?epoll_event?結(jié)構(gòu)體的地址盯荤,
epoll_event?結(jié)構(gòu)體定義如下:
? ? ? ?? struct epoll_event
? ? ? ? {
? ? ? ? ? ? ?? uint32_t?????events;??????/*?需要檢測的?fd?事件馋吗,取值與?poll?函數(shù)一樣?*/
? ? ? ? ? ? ?? epoll_data_t?data;????????/*?用戶自定義數(shù)據(jù), 本質(zhì)上是一個 Union 對象秋秤,8個字節(jié)*/
? ? ? ?? };?
? ?? int epoll_wait(int epfd,struct epoll_event*?events,int maxevents,int timeout); //檢測事件 宏粤,參數(shù)?events?是一個?epoll_event?結(jié)構(gòu)數(shù)組的首地址脚翘,這是一個輸出參數(shù),函數(shù)調(diào)用成功后绍哎,events?中存放?
的是與就緒事件相關(guān)?epoll_event?結(jié)構(gòu)體數(shù)組来农;參數(shù)?maxevents?是數(shù)組元素的個數(shù);timeout?是超時時間崇堰,單位是毫秒沃于,如果設(shè)置為 0,epoll_wait?會立即返回海诲。當?epoll_wait?調(diào)用成功會返回有事件
的 fd 數(shù)目繁莹;如果返回 0 表示超時;調(diào)用失敗返回 -1特幔。如下例子所示:
while?(true)
{
????epoll_event?epoll_events[1024];
????int?n?=?epoll_wait(epollfd,?epoll_events,?1024,?1000);
????if?(n?<?0)
????{
????????//被信號中斷
????????if?(errno?==?EINTR)
????????????continue;
????????//出錯咨演,退出
????????break;
????}
????else?if?(n?==?0)
????{
????????//超時,繼續(xù)
????????continue;
????}
????for?(size_t?i?=?0;?i?<?n;?++i)
????{
????????//?處理可讀事件
????????if?(epoll_events[i].events?&?POLLIN)
????????{
????????}
????????//?處理可寫事件
????????else?if?(epoll_events[i].events?&?POLLOUT)
????????{
????????}
????????//處理出錯事件
????????else?if?(epoll_events[i].events?&?POLLERR)
????????{
????????}
????}
}
? ? ? ? ? epoll_wait 與 poll 的區(qū)別:epoll_wait?函數(shù)調(diào)用完之后蚯斯,我們可以直接在?event?參數(shù)中拿到所有有事件就緒的 fd薄风,直接處理即可(event?參數(shù)僅僅是個出參);而?poll?函數(shù)的事件集合調(diào)用前后
數(shù)量都未改變拍嵌,只不過調(diào)用前我們通過?pollfd?結(jié)構(gòu)體的?events?字段設(shè)置待檢測事件遭赂,調(diào)用后我們需要通過?pollfd?結(jié)構(gòu)體的?revents?字段去檢測就緒的事件( 參數(shù)?fds?既是入?yún)⒁彩浅鰠ⅲ?/p>
? ? ? ? ?? 與 poll 的事件宏相比,epoll 新增了一個事件宏EPOLLET横辆,這就是所謂的邊緣觸發(fā)模式(EdgeTrigger嵌牺,ET),而默認的模式我們稱為水平觸發(fā)模式(LevelTrigger龄糊,LT)逆粹。這兩種模式的區(qū)別在于:
? ? ? ? ? 對于水平觸發(fā)模式,一個事件只要有炫惩,就會一直觸發(fā)僻弹;(讀取數(shù)據(jù)時可選擇讀取字節(jié)數(shù)目)
? ? ? ? ? 對于邊緣觸發(fā)模式,只有一個事件從無到有才會觸發(fā)他嚷。(因此讀取數(shù)據(jù)時要一次性全部讀完蹋绽,循環(huán)調(diào)用 recv 函數(shù)直到 recv 出錯,錯誤碼是EWOULDBLOCK(EAGAIN?一樣))