Linux網路程式設計 -- select實現多路IO轉接伺服器
阿新 • • 發佈:2019-01-08
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "wrap.h"
#define SERV_PORT 8888
int main()
{
int opt = 1;
int i, j, n, maxi;
int nready, client[FD_SETSIZE]; //自定義陣列client, 防止遍歷1024個檔案描述符 FD_SETSIZE預設為1024
int maxfd, listenfd, connfd, sockfd;
char buf[BUFSIZ], clie_IP[INET_ADDRSTRLEN];
struct sockaddr_in clie_addr, serv_addr;
socklen_t clie_addr_len;
fd_set read_set, allset; //read_set 讀事件檔案描述符集合 allset用來暫存
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family= AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port= htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
Listen(listenfd, 128);
maxfd = listenfd;
maxi = -1 ; //指向陣列首地址的前一個
for (i = 0; i < FD_SETSIZE; ++i)
{
client[i] = -1; //將存放客戶端的檔案描述符的陣列初始化為 -1
}
FD_ZERO(&allset); //將allset清空
FD_SET(listenfd, &allset); //將listenfd 放入allset中
while (1)
{
read_set = allset;
//select 返回所有監聽的文教描述符滿足條件的總個數
//引數1:所監聽的所有檔案描述符中,最大的檔案描述符+1
//引數2/3/4:所監聽的檔案描述符 可讀、可寫、異常時間
nready = select(maxfd+1, &read_set, NULL, NULL, NULL);
if (nready < 0)
{
perr_exit("select error");
}
if (FD_ISSET(listenfd, &read_set)) //說明有新的客戶端連線請求
{
clie_addr_len = sizeof(clie_addr);
connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));
for (i = 0; i < FD_SETSIZE; ++i)
{
if (client[i] < 0) //定位到client陣列中沒有存入檔案描述符的位置
{
client[i] = connfd; //將新連線的客戶端的檔案描述符儲存如client
break;
}
}
if (FD_SETSIZE == i) //防止檔案描述符的個數超過陣列的容量
{
printf("client numbers over\n");
exit(1);
}
FD_SET(connfd, &allset); //將conf儲存到allset集合中
if (connfd > maxfd) //更新最大的檔案描述符
{
maxfd = connfd;
}
if (i > maxi) //更新client陣列的內容的長度
{
maxi = i;
}
if (--nready == 0)
{
continue;
}
}
for (i = 0; i <= maxi; ++i) ///檢測client陣列中有哪些檔案描述符滿足了條件 */
{
if ((sockfd = client[i]) < 0) //該元素還沒有儲存檔案描述符
{
continue;
}
if (FD_ISSET(sockfd, &read_set)) //該文教描述符是否滿足讀事件
{
n = Read(sockfd, buf, sizeof(buf));
if (0 == n) ///表示該客戶端關閉連結,此時,伺服器端也要關閉對應連結
{
Close(sockfd);
FD_CLR(sockfd, &allset); //將該檔案描述符從allset集合中清除
client[i] = -1;
}
else if (n > 0)
{
for (j = 0; j < n; ++j)
{
buf[j] = toupper(buf[j]);
}
Write(sockfd, buf, n); //將處理後的資料傳送給客戶端
Write(STDOUT_FILENO, buf, n); //在服務端顯示處理的資料
}
if (--nready == 0)
{
break;
}
}
}
}
Close(listenfd);
return 0;
}
wrap.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
//輸出錯誤資訊並退出
void perr_exit(const char *str)
{
perror(str);
exit(-1);
}
//建立套接字 帶出錯處理
int Socket(int family, int type, int protocol)
{
int n;
n = socket(family, type, protocol);
if (n < 0)
{
perr_exit("socket error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
n = bind(fd, sa, salen);
if (n < 0)
{
perr_exit("bing error");
}
return n;
}
int Listen(int fd, int backlog)
{
int n;
n = listen(fd, backlog);
if (n < 0)
{
perr_exit("listen error");
}
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
n = connect(fd, sa, salen);
if (n < 0)
{
perr_exit("connect error");
}
return n;
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
n = accept(fd, sa, salenptr);
if (n < 0)
{
if (errno == ECONNABORTED || errno == EINTR)
{
goto again;
}
else
{
perr_exit("accept error");
}
}
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
n = read(fd, ptr, nbytes);
if (-1 == n)
{
if (EINTR == errno)
{
goto again;
}
else
{
return -1;
}
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
n = write(fd, ptr, nbytes);
if (-1 == n)
{
if (EINTR == errno)
{
goto again;
}
else
{
return -1;
}
}
return n;
}
int Close(int fd)
{
int n;
n = close(fd);
if (-1 == n)
{
perr_exit("close error");
}
return n;
}