linux tcp ip c,Linux下TCP/IP编程--TCP实战(select)
本文參考自徐曉鑫《后臺開發》,記錄之。
一、為什么要使用非阻塞I/O之select
初學socket的人可能不愛用select寫程序,而習慣諸如connect、accept、recv/recvfrom這樣的阻塞程序。
當讓服務器同時為多個客戶端提供一問一答服務時,很多程序員采用多線程/進程模型來解決。但是若同時響應成百上千的連接請求,無論是多進程還是多線程都會嚴重占據系統資源降低系統對外響應的效率。(“線程池”旨在降低創建和銷毀線程的頻率,“連接池”旨在盡量重用已有連接,二者都需要考慮面臨的響應規模,即池的大小是有限的)。
高級程序員使用select就可以完成非阻塞方式工作的程序,它能夠監視被監測文件描述符的變化情況。
使用select的事件驅動模型只用單線程(進程)執行,占用資源少,不消耗太多CPU資源,同時能為多客戶端提供服務。當然select也有缺點如下:
每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
select支持的文件描述符數量太小了,默認是1024
后面學習poll、epoll就是解決這個問題的,這個后面會了解到。
二、slect函數原型
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
具體解釋select的參數:
maxfdp是一個整數值,集合中所有文件描述符的范圍,即所有文件描述符的最大值加1。
fd_set *readfds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的讀變化的,即我們關心是否可以從這些文件中讀取數據了,如果這個集合中有一個文件可讀,select就會返回一個大于0的值,表示有文件可讀;如果沒有可讀的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0;若發生錯誤返回負值。
fd_set *writefds是指向fd_set結構的指針,主要關心文件的寫變化,即是否可寫。
fd_set *errorfds用來監視文件錯誤異常
返回值:
正值表示準備就緒的描述符數, 0表示等待超時,負值表示select出錯
三、使用select函數循環讀取鍵盤輸入
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY |O_NONBLOCK);
assert(keyboard>0);
while(1)
{
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret = select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(ret == -1)
perror("select error\n");
else if (ret) {
if(FD_ISSET(keyboard,&readfd)) {
i = read(keyboard,&c,1);
if('\n'== c)
continue;
printf("The input is %c\n",c);
if('q'==c)
break;
}
}
else if (ret ==0)
printf("time out\n");
}
return 0;
}
只要發現鍵盤輸入字符,程序就輸出對應字符。若超過5s不輸入,打印time out。
四、使用select函數提高服務器處理能力
服務器端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEFAULT_PORT 6666
int main( int argc, char ** argv){
int serverfd,acceptfd; /* 監聽socket: serverfd,數據傳輸socket: acceptfd */
struct sockaddr_in my_addr; /* 本機地址信息 */
struct sockaddr_in their_addr; /* 客戶地址信息 */
unsigned int sin_size, myport=6666, lisnum=10;
if ((serverfd = socket(AF_INET , SOCK_STREAM, 0)) == -1) {
perror("socket" );
return -1;
}
printf("socket ok \n");
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(DEFAULT_PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 0);
if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {
perror("bind" );
return -2;
}
printf("bind ok \n");
if (listen(serverfd, lisnum) == -1) {
perror("listen" );
return -3;
}
printf("listen ok \n");
fd_set client_fdset; /*監控文件描述符集合*/
int maxsock; /*監控文件描述符中最大的文件號*/
struct timeval tv; /*超時返回時間*/
int client_sockfd[5]; /*存放活動的sockfd*/
bzero((void*)client_sockfd,sizeof(client_sockfd));
int conn_amount = 0; /*用來記錄描述符數量*/
maxsock = serverfd;
char buffer[1024];
int ret=0;
/*不斷的查看是否有新的client連接;已連接的client是否有發送消息過來*/
while(1){
/*初始化文件描述符號到集合*/
FD_ZERO(&client_fdset);
/*加入服務器描述符*/
FD_SET(serverfd,&client_fdset);
/*設置超時時間*/
tv.tv_sec = 30; /*30秒*/
tv.tv_usec = 0;
/*把活動的句柄加入到文件描述符中*/
for(int i = 0; i < 5; ++i){
/*程序中Listen中參數設為5,故i必須小于5*/
if(client_sockfd[i] != 0){
FD_SET(client_sockfd[i], &client_fdset);
}
}
/*printf("put sockfd in fdset!\n");*/
/*select函數,根據返回值判斷程序是否有異常*/
ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);
if(ret < 0){
perror("select error!\n");
break;
} else if(ret == 0){
printf("timeout!\n");
continue;
}
/*輪詢各個(已連接上的client的)文件描述符有無可讀(接收)數據,有就輸出,沒有或者異常時,關閉相應的client連接,并在集合里清理掉*/
for(int i = 0; i < conn_amount; ++i){
/*FD_ISSET檢查client_sockfd是否可讀寫,>0可讀寫*/
if(FD_ISSET(client_sockfd[i], &client_fdset)){
printf("start recv from client[%d]:\n",i);
ret = recv(client_sockfd[i], buffer, 1024, 0);
if(ret <= 0){
printf("client[%d] close\n", i);
close(client_sockfd[i]);
FD_CLR(client_sockfd[i], &client_fdset);
client_sockfd[i] = 0;
}
else{
printf("recv from client[%d] :%s\n", i, buffer);
}
}
}
/*檢查是否有新的連接,如果有,接收連接加入到client_sockfd中*/
if(FD_ISSET(serverfd, &client_fdset))
{
/*接受連接*/
struct sockaddr_in client_addr;
size_t size = sizeof(struct sockaddr_in);
int sock_client = accept(serverfd, (struct sockaddr*)(&client_addr), (unsigned int*)(&size));
if(sock_client < 0){
perror("accept error!\n");
continue;
}
/*把連接加入到文件描述符集合中*/
if(conn_amount < 5)
{
client_sockfd[conn_amount++] = sock_client;
bzero(buffer,1024);
strcpy(buffer, "this is server! welcome!\n");
send(sock_client, buffer, 1024, 0);
printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
bzero(buffer,sizeof(buffer));
ret = recv(sock_client, buffer, 1024, 0);
if(ret < 0){
perror("recv error!\n");
close(serverfd);
return -1;
}
printf("recv : %s\n",buffer);
//更新maxsock,因為下一次進入while循環調用時,需要傳當前最大的fd值+1給select函數
if(sock_client > maxsock){
maxsock = sock_client;
}
else{
printf("max connections!!!quit!!\n");
break;
}
}
}
}
//最后,把已連接上的clent的fd和server自身的fd都關閉
for(int i = 0; i < 5; ++i){
if(client_sockfd[i] != 0){
close(client_sockfd[i]);
}
}
close(serverfd);
return 0;
}
客戶端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEFAULT_PORT 6666
int main( int argc, char * argv[]){
int connfd = 0;
int cLen = 0;
struct sockaddr_in client;
if(argc < 2){
printf(" Uasge: clientent [server IP address]\n");
return -1;
}
client.sin_family = AF_INET;
client.sin_port = htons(DEFAULT_PORT);
client.sin_addr.s_addr = inet_addr(argv[1]);
connfd = socket(AF_INET, SOCK_STREAM, 0);
if(connfd < 0){
perror("socket" );
return -1;
}
if(connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0){
perror("connect" );
return -1;
}
char buffer[1024];
bzero(buffer,sizeof(buffer));
recv(connfd, buffer, 1024, 0);
printf("recv : %s\n", buffer);
bzero(buffer,sizeof(buffer));
strcpy(buffer,"this is client!\n");
send(connfd, buffer, 1024, 0);
while(1){
bzero(buffer,sizeof(buffer));
scanf("%s",buffer);
int p = strlen(buffer);
buffer[p] = '\0';
send(connfd, buffer, 1024, 0);
printf("i have send buffer\n");
}
close(connfd);
return 0;
}
驗證:
客戶端1:
客戶端2:
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的linux tcp ip c,Linux下TCP/IP编程--TCP实战(select)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dw如何写php代码提示,DW CS5
- 下一篇: linux怎么添加更新源,在Deepin