所謂的回射是指:客戶端A向服務端B發送數據,服務端B接收到數據以後,再將接收到的數據發送回客戶端B。所謂的迭代服務器,是指服務器端只用一個進程處理或線程處理全部客戶端的請求。與之對應的是併發服務器,併發服務器是指對於每一一個客戶端的請求,服務端都分配一個進程或是線程獨立來處理客戶端的處理。下面介紹使用select函數實現TCP回射迭代服務。直接上代碼:ubuntu
服務端程序:服務器
/*=============================================================================
# FileName: tcpservselect.c
# Desc: receive client data and then send they back.
# Author: Licaibiao
# LastChange: 2017-02-12
=============================================================================*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#define MAXLINE 1024
#define LISTENLEN 10
#define SERV_PORT 6666
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENLEN);
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for ( ; ; )
{
rset = allset; /* structure assignment */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (FD_ISSET(listenfd, &rset)) /* new client connection */
{
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s, port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
ntohs(cliaddr.sin_port));
#endif
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE)
{
printf("too many clients");
exit(0);
}
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) /* check all clients for data */
{
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset))
{
if ( (n = read(sockfd, buf, MAXLINE)) == 0)/* connection closed by client */
{
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
write(sockfd, buf, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
在服務端的程序中,咱們使用select 來處理任意個客戶的單進程程序,而不是派生一個子程序。
客戶端程序:併發
/*=============================================================================
# FileName: tcpcliselect.c
# Desc: send data to server and receive data from server
# Author: Licaibiao
# LastChange: 2017-02-12
=============================================================================*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#define MAXLINE 1024
#define LISTENLEN 10
#define SERV_PORT 6666
int max(int a, int b)
{
return a>b ? a : b;
}
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
stdineof = 0;
FD_ZERO(&rset);
for ( ; ; )
{
if (stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
select(maxfdp1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset))
{
if ( (n = read(sockfd, buf, MAXLINE)) == 0) /* socket is readable */
{
if (stdineof == 1)
return; /* normal termination */
else
printf("str_cli: server terminated prematurely");
}
write(fileno(stdout), buf, n);
}
if (FD_ISSET(fileno(fp), &rset)) /* input is readable */
{
if ( (n = read(fileno(fp), buf, MAXLINE)) == 0)
{
stdineof = 1;
shutdown(sockfd, SHUT_WR); /* send FIN */
FD_CLR(fileno(fp), &rset);
continue;
}
write(sockfd, buf, n);
}
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
{
printf("usage: tcpcli <IPaddress>");
exit(0);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
在第60行咱們使用了shutdown函數。咱們知道,TCP是全雙工工做,在咱們作批量輸入批量輸出的時候,咱們客戶端已經把數據發送完畢,這個時候並不能直接關閉描述符,由於可能還有數據在從服務端發送回來的路上。close函數是直接終止讀和寫兩個方向的數據傳送。可是使用shutdown能夠單方向關閉數據傳輸。
在客戶端,咱們使用Ctrl + d 來結束客戶端程序。Ctrl + d 會發送一個exit 。在TCP傳輸中,若是對端TCP發送一個FIN(finish 對端進程終止),那麼該套接字變爲可讀,而且read返回0(EOF)socket
運行結果:tcp
運行服務端程序函數
root@ubuntu:/home/share/test# ./strserselect
另一個終端運行客戶端程序:
root@ubuntu:/home/share/test# ./strcliselect 127.0.0.1
china /*發送*/
china /*接收*/.net
注意:上面的程序存在一個問題,若是有一個惡意客戶端只發送一個字節數據(不是換行符)後進入睡眠,服務器調用read讀入一個字節,後面就阻塞在read函數以等待其餘的數據,這樣一來服務端就阻塞在一個客戶端,不能再處理其餘客戶端的請求(拒絕服務型攻擊)
解決上面問題有下面的幾種方法:線程
(a)使用非阻塞式IOorm
(b)對IO操做設置一個超時server
(c)讓每一個客戶由單獨的控制線程提供服務