基本套接字程式設計(3) -- select篇
1. I/O複用
我們學習了I/o複用的基本知識,瞭解到目前支援I/O複用的系統呼叫有select、pselect、poll、epoll。而epoll技術以其獨特的優勢被越來越多的應用到各大企業伺服器。(後面將有poll & epoll單獨學習筆記)基本概念
IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。IO多路複用適用如下場合:(1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。
(2)當一個客戶同時處理多個套介面時,而這種情況是可能的,但很少出現。 (3)如果一個TCP伺服器既要處理監聽套介面,又要處理已連線套介面,一般也要用到I/O複用。 (4)如果一個伺服器即要處理TCP,又要處理UDP,一般要使用I/O複用。
(5)如果一個伺服器要處理多個服務或多個協議,一般要使用I/O複用。
與多程序和多執行緒技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必建立程序/執行緒,也不必維護這些程序/執行緒,從而大大減小了系統的開銷。
2. select技術
2.1函式原型
該函式准許程序指示核心等待多個事件中的任何一個傳送,並只在有一個或多個事件發生或經歷一段指定的時間後才喚醒。函式原型如下:函式引數介紹如下:#include <sys/select.h> #include <sys/time.h> int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout) 返回值:就緒描述符的數目,超時返回0,出錯返回-1
(1)第一個引數maxfdp1指定待測試的描述字個數,它的值是待測試的最大描述字加1(因此把該引數命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。因為檔案描述符是從0開始的。
(2)中間的三個引數readset、writeset和exceptset指定我們要讓核心測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設為空指標。struct fd_set可以理解為一個集合,這個集合中存放的是檔案描述符,可通過以下四個巨集進行設定:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //將一個給定的檔案描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //將一個給定的檔案描述符從集合中刪除
int FD_ISSET(int fd, fd_set *fdset); // 檢查集合中指定的檔案描述符是否可以讀寫
(3)timeout告知核心等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用於指定這段時間的秒數和微秒數。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
這個引數有三種可能:
(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此,把該引數設定為空指標NULL。
(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該引數所指向的timeval結構中指定的秒數和微秒數。
(3)根本不等待:檢查描述字後立即返回,這稱為輪詢。為此,該引數必須指向一個timeval結構,而且其中的定時器值必須為0。
有關select更加詳細的講解請參考《Unix網路程式設計 -- 卷一》第六章 Page127 ~ 142;
2.2 select原理流程圖
3. TCP回射程式例項
本例是基本套接字程式設計(1) -- tcp篇中回射程式的改寫,其中server端採用select技術,實現I/O複用,可同時為多個客戶程式服務!3.1 server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define PORT 8888
#define MAX_LINE 2048
#define LISTENQ 20
int main(int argc , char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready , client[FD_SETSIZE];
ssize_t n, ret;
fd_set rset , allset;
char buf[MAX_LINE];
socklen_t clilen;
struct sockaddr_in servaddr , cliaddr;
/*(1) 得到監聽描述符*/
listenfd = socket(AF_INET , SOCK_STREAM , 0);
/*(2) 繫結套接字*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
/*(3) 監聽*/
listen(listenfd , LISTENQ);
/*(4) 設定select*/
maxfd = listenfd;
maxi = -1;
for(i=0 ; i<FD_SETSIZE ; ++i)
{
client[i] = -1;
}//for
FD_ZERO(&allset);
FD_SET(listenfd , &allset);
/*(5) 進入伺服器接收請求死迴圈*/
while(1)
{
rset = allset;
nready = select(maxfd+1 , &rset , NULL , NULL , NULL);
if(FD_ISSET(listenfd , &rset))
{
/*接收客戶端的請求*/
clilen = sizeof(cliaddr);
printf("\naccpet connection~\n");
if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
{
perror("accept error.\n");
exit(1);
}//if
printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port);
/*將客戶連結套接字描述符新增到陣列*/
for(i=0 ; i<FD_SETSIZE ; ++i)
{
if(client[i] < 0)
{
client[i] = connfd;
break;
}//if
}//for
if(FD_SETSIZE == i)
{
perror("too many connection.\n");
exit(1);
}//if
FD_SET(connfd , &allset);
if(connfd > maxfd)
maxfd = connfd;
if(i > maxi)
maxi = i;
if(--nready < 0)
continue;
}//if
for(i=0; i<=maxi ; ++i)
{
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd , &rset))
{
/*處理客戶請求*/
printf("\nreading the socket~~~ \n");
bzero(buf , MAX_LINE);
if((n = read(sockfd , buf , MAX_LINE)) <= 0)
{
close(sockfd);
FD_CLR(sockfd , &allset);
client[i] = -1;
}//if
else{
printf("clint[%d] send message: %s\n", i , buf);
if((ret = write(sockfd , buf , n)) != n)
{
printf("error writing to the sockfd!\n");
break;
}//if
}//else
if(--nready <= 0)
break;
}//if
}//for
}//while
}
3.2 client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#define PORT 8888
#define MAX_LINE 2048
int max(int a , int b)
{
return a > b ? a : b;
}
/*readline函式實現*/
ssize_t readline(int fd, char *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = read(fd, &c,1)) == 1) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else
return(-1); /* error, errno set by read() */
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
/*普通客戶端訊息處理函式*/
void str_cli(int sockfd)
{
/*傳送和接收緩衝區*/
char sendline[MAX_LINE] , recvline[MAX_LINE];
while(fgets(sendline , MAX_LINE , stdin) != NULL)
{
write(sockfd , sendline , strlen(sendline));
bzero(recvline , MAX_LINE);
if(readline(sockfd , recvline , MAX_LINE) == 0)
{
perror("server terminated prematurely");
exit(1);
}//if
if(fputs(recvline , stdout) == EOF)
{
perror("fputs error");
exit(1);
}//if
bzero(sendline , MAX_LINE);
}//while
}
/*採用select的客戶端訊息處理函式*/
void str_cli2(FILE* fp , int sockfd)
{
int maxfd;
fd_set rset;
/*傳送和接收緩衝區*/
char sendline[MAX_LINE] , recvline[MAX_LINE];
FD_ZERO(&rset);
while(1)
{
/*將檔案描述符和套接字描述符新增到rset描述符集*/
FD_SET(fileno(fp) , &rset);
FD_SET(sockfd , &rset);
maxfd = max(fileno(fp) , sockfd) + 1;
select(maxfd , &rset , NULL , NULL , NULL);
if(FD_ISSET(fileno(fp) , &rset))
{
if(fgets(sendline , MAX_LINE , fp) == NULL)
{
printf("read nothing~\n");
close(sockfd); /*all done*/
return ;
}//if
sendline[strlen(sendline) - 1] = '\0';
write(sockfd , sendline , strlen(sendline));
}//if
if(FD_ISSET(sockfd , &rset))
{
if(readline(sockfd , recvline , MAX_LINE) == 0)
{
perror("handleMsg: server terminated prematurely.\n");
exit(1);
}//if
if(fputs(recvline , stdout) == EOF)
{
perror("fputs error");
exit(1);
}//if
}//if
}//while
}
int main(int argc , char **argv)
{
/*宣告套接字和連結伺服器地址*/
int sockfd;
struct sockaddr_in servaddr;
/*判斷是否為合法輸入*/
if(argc != 2)
{
perror("usage:tcpcli <IPaddress>");
exit(1);
}//if
/*(1) 建立套接字*/
if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
{
perror("socket error");
exit(1);
}//if
/*(2) 設定連結伺服器地址結構*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
{
printf("inet_pton error for %s\n",argv[1]);
exit(1);
}//if
/*(3) 傳送連結伺服器請求*/
if(connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
{
perror("connect error");
exit(1);
}//if
/*呼叫普通訊息處理函式*/
str_cli(sockfd);
/*呼叫採用select技術的訊息處理函式*/
//str_cli2(stdin , sockfd);
exit(0);
}
3.3 執行結果
伺服器監聽終端:兩個客戶連結伺服器:
注:以上部分理論內容來自參考部落格,多謝原博主!
相關推薦
基本套接字程式設計(3) -- select篇
1. I/O複用 我們學習了I/o複用的基本知識,瞭解到目前支援I/O複用的系統呼叫有select、pselect、poll、epoll。而epoll技術以其獨特的優勢被越來越多的應用到各大企業伺服器。(後面將有poll & epoll單獨學習筆記) 基本概念 IO
基本套接字程式設計(7) -- udp篇
UDP 是User Datagram Protocol的簡稱, 中文名是使用者資料報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連線的傳輸層協議,提供面向事務的簡單不可靠資訊傳送服務,IETF RFC 768是UDP的正式規範。UD
基本套接字程式設計(5) -- epoll篇
1. epoll技術 epoll是Linux核心為處理大批量檔案描述符而作了改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被
基本套接字程式設計(4) -- poll篇
1. poll技術 poll函式起源於SVR3,最初侷限於流裝置。SVR4取消了這種限制,允許poll工作在任何描述符上。poll提供的功能與select類似,不過在處理流裝置時,它能夠提供額外的資訊。 poll的機制與select類似,與select在本質上沒有多大差別,
基本套接字程式設計(6) -- 執行緒篇
1. 執行緒 傳統Unix模型中,當一個程序需要另一個實體來完成某事,它就fork一個子程序來處理。Unix上大多數網路伺服器程式便是以建立多個子程序的方式實現的:父程序accept一個連線,fork一個子程序,該子程序處理與該連線對端的客戶之間的通訊。 儘管,這種正規化多
linux sock_raw原始套接字程式設計 (轉)和Linux下Libpcap原始碼分析和包過濾機制
sock_raw原始套接字程式設計可以接收到本機網絡卡上的資料幀或者資料包,對與監聽網路的流量和分析是很有作用的.一共可以有3種方式建立這種 socket 1.socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROT
《c語言從入門到精通》看書筆記——第16章 網路套接字程式設計(上)——網路
(1)TCP協議:傳輸協議(TCP)是一種提供克勞資料傳送的通用協議,他是TCP/IP體系結構中傳輸層上的協議。在傳送資料時,應用層的資料傳輸到傳輸層,傢伙是哪個TCP首部,資料就構成了報文。報文就是網路層IP的資料,如果再加上IP首部,就構成了IP資料報。TCP協議的C語言資料描述如下:
套接字程式設計(一)----基於TCP協議
套接字(socket):可以看做是不同主機之間的程序進行雙向通訊的端點,即通訊的兩方的一種約定,用套接字中的相關函式來完成通訊過程。 **socket=Ip Address+TCP/UDP+port
《網際網路程式設計(Java)》——課程筆記7:UDP套接字程式設計(無連線)
學會使用UDP套接字來實現網路應用程式設計。 UDP通訊特點: (1) UDP有別於TCP,有自己獨立的套接字(IP+PORT),它們的埠號不衝突; (2) UDP 通訊前通常[不]需要連線; (3) 基於使用者資料報文(包)讀寫;
TCP和UDP套接字程式設計 (java實現)
在瞭解網路程式設計之前,我們先了解一下什麼叫套接字 套接字即指同一臺主機內應用層和運輸層之間的介面 由於這個套接字是建立在網路上建立網路應用的可程式設計介面 因此也將套接字稱為應用程式和網路之間的應用程式程式設計介面! 關於TCP和UDP這裡就不作太多介紹了,我們知道TCP是面向連
網路程式設計基礎【林老師版】:簡單的 套接字通訊(一)
一、服務端程式碼 import socket #1、買手機 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # print(phone) #2、繫結手機卡 phone.bind(('127.0.0.1',8081)) #0-
網路程式設計、常用的通訊協議、Socket套接字程式設計(Socket和ServerSocket)、埠號
網路程式設計: java語言中,提供了一套統一的程式設計介面。很多細節都已經底層化。 所在,可以進行無痛的網路通訊程式設計。 提供的是Socket套接字技術。 --常用的通訊協議: (1)TCP/IP:在通訊之前,需
套接字(二):Socket 套接字程式設計(附例項)
TCP/IP地址家族統一的套接字地址結構定義如下: struct sockaddr_in { short sin_family; //指定地址家族,即地址格式 unsigned short
第二部分:基本套接字程式設計
IPv4網際套接字地址結構: struct in_addr{ in_addr_t s_addr; }; struct sockaddr_in{ uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_a
套接字程式設計的基本模式(網路程式設計Linux_C -> 筆記二)
套接字程式設計的基本模式 客戶端/伺服器 客戶端/伺服器模式就是基本的網路程式設計模式,簡稱C/S(即Client/Server)模式。需要注意的是這裡的客戶端、伺服器指的是軟體層面的意思而不是硬體,即客戶端、伺服器是分別執行在兩臺電腦上的兩個軟體。
嵌入式Linux網路程式設計,網路基礎,套接字socket(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW),IP地址,埠號,位元組序,位元組序轉換函式,IP地址的轉換
文章目錄 1,socket 1.1,socket的型別(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW) 1.2,socket的位置 2,IP地址 2.1,特殊IP地址: 3,埠號
Windows Socket套接字(三)-MFC套接字程式設計
同步:指傳送方發出資料後,等收到接收方發回的響應,才發下一個數據包的通訊方式 非同步:指的是傳送方不等接收方響應,便接著發下個數據包的通訊方式; 阻塞:指呼叫某函式時,直到該函式完成操作,才返回;否則一直阻塞在該呼叫上 非阻塞:指呼叫某操作時,不管操作是否成功都立即返回,而不
基本套接字詳解(轉)
1.套接字地址結構 結構體sturct sockaddr定義了一種通用的套接字地址,它在linux/socket.h中的定義程式碼如下: struct sockaddr { unsigned short sa_family; char
實現UDP套接字程式設計 整理《計算機網路——自頂向下方法(James F. Kurose, Keith W. Rose)》
1. 首先介紹一下網路應用程式。主要有兩類: 一類是實現“在協議標準(RFC或其他標準文件)中所定義的操作”,是開放的網路應用程式,開發者必須遵守協議所規定的規則。因此,不同開發者開發的程式能夠互動操作(這些程式需要使用與該協議關聯的周知埠號);
套接字程式設計簡介(筆記)
July 25, 2015 8:26 PM 前言 網路程式設計->套接字->套接字地址結構。 套接字地址結構可以在兩個方向上傳遞:從程序到核心、從核心到程序! 套接字結構 以Windows作為例項,看看套接字的結構: /* *