1. 程式人生 > >基本套接字程式設計(3) -- select篇

基本套接字程式設計(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技術

select()函式確定一個或多個套介面的狀態,本函式用於確定一個或多個套介面的狀態,對每一個套介面,呼叫者可查詢它的可讀性、可寫性及錯誤狀態資訊,用fd_set結構來表示一組等待檢查的套介面,在呼叫返回時,這個結構存有滿足一定條件的套介面組的子集,並且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網路程式設計,網路基礎,socketSOCK_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作為例項,看看套接字的結構: /* *