讀、寫、異常事件發生條件
可讀的條件:
套接口緩存區有數據可讀;
連接的讀一半關閉,即接受到FIN段,讀操作將返回0。
如果是監聽套接口,已完成連接隊列不爲空時。
套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。
可寫的條件:
套接口發送緩存區有空間容納數據。
連接的寫一半關閉。即接收到RST段之後,再次調用write操作。
套接口上發生了一個錯誤待處理,錯誤可以通過getsockopt指定SO_ERROR選項來獲取。
異常的條件:
套接口存在帶外數據。
帶外數據:
傳輸層協議使用帶外數據(out-of-band,OOB)來發送一些重要的數據,
如果通信一方有重要的數據需要通知對方時,協議能夠將這些數據快速
地發送到對方.爲了發送這些數據,協議一般不使用與普通數據相同的
通道,而是使用另外的通道.linux系統的套接字機制支持低層協議發送
和接受帶外數據.但是TCP協議沒有真正意義上的帶外數據.爲了發送重
要協議,TCP提供了一種稱爲緊急模式(urgentmode)的機制.TCP協議在
數據段中設置URG位,表示進入緊急模式.接收方可以對緊急模式採取特
殊的處理.很容易看出來,這種方式數據不容易被阻塞,可以通過在我們
的服務器端程序裏面捕捉SIGURG信號來及時接受數據或者使用帶OOB標
志的recv函數來接受、
好的解釋鏈接:https://www.cnblogs.com/c-slmax/p/5553857.html
使用select改進回射服務器:
服務器程序:
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#icnldue <stdlib.h>
#include <errno.h>
using namespace std;
struct packet
{
int len;
char buf[1024];
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; // 剩餘字節數
ssize_t nread;
char *bufp = (char*) buf;
while (nleft > 0)
{
nread = read(fd, bufp, nleft);
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
} else if (nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
}
else if (nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看傳入消息
if (ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf; // 當前指針位置
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, buf, nleft);
if (ret < 0)
{
return ret;
}
else if (ret == 0)
{
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
{
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft)
{
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
{
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}
void echo_srv(int connfd)
{
char recvbuf[1024];
// struct packet recvbuf;
int n;
while (1)
{
memset(recvbuf, 0, sizeof recvbuf);
int ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
}
}
int main(int argc, char** argv) {
// 1. 創建套接字
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);
int on = 1;
// 確保time_wait狀態下同一端口仍可使用
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0) {
ERR_EXIT("setsockopt");
}
// 3. 綁定套接字地址
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
ERR_EXIT("bind");
}
// 4. 等待連接請求狀態
if (listen(listenfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
// 5. 允許連接
struct sockaddr_in peeraddr;
socklen_t peerlen;
/*
// 6. 數據交換
pid_t pid;
while (1) {
int connfd;
if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {
ERR_EXIT("accept");
}
printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
printf("port = %d\n", ntohs(peeraddr.sin_port));
pid = fork();
if (pid == -1) {
ERR_EXIT("fork");
}
if (pid == 0) // 子進程
{
close(listenfd);
echo_srv(connfd);
//printf("child exit\n");
exit(EXIT_SUCCESS);
} else {
//printf("parent exit\n");
close(connfd);
}
}
*/
//使用select來實現
int i;
int client[FD_SETSIZE]; //FD_SETSIZE表示select處理的最大可讀I/O數量
int maxi = 0; //優化,代表最大的空閒位置
for(i = 0; i < FD_SETSIZE; i++)//爲了保存conn
{
client[i] = -1; //-1表示空閒狀態
}
//以單進程的方式來實現併發處理多個客戶端的連接
int nready; //表示檢測到的數據
int maxfd = listenfd; //select參數1
fd_set rset; //參數2
int conn;
fd_set allset;
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
{
//使用allset的爲了防止rset中的事件改變,沒有將所有事件保存起來,這樣就保證每次都是監聽所有的套接口。
rset = allset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if(nready == -1)
{
if(errno == EINTR)
continue;
ERR_EXIT("select");
}
if(nready == 0)
continue;
//處理監聽套接口
if(FD_ISSET(listenfd, &rset))
{
peerlen = sizeof peeraddr;
conn = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen);
if(conn == -1)
{
ERR_EXIT("accept");
}
for(i = 0; i < FD_SETSIZE; i++)
{
if(client[i] < 0)
{
client[i] = conn;
if(i > maxi)
{
maxi = i;
}
break;
}
}
if(i == FD_SETSIZE)
{
fprintf(stderr, "too many clients\n");
exit(EXIT_FAILURE);
}
printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
printf("port = %d\n", ntohs(peeraddr.sin_port));
FD_SET(conn, &allset); //將事件添加到集合當中
if(conn > maxfd)
{
maxfd = conn;
}
if(--nready <= 0)
{
continue;
}
}
//處理已經連接的套接口
for(i = 0; i <= maxi; i++)
{
conn = client[i];
if(conn == -1)
continue;
if(FD_ISSET(conn, &rset))
{
char recvbuf[1024];
int ret = readline(conn, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
FD_CLR(conn, &allset);
client[i] = -1;//由於刪除掉以後所以要重新賦值爲-1
}
fputs(recvbuf, stdout);
writen(conn, recvbuf, strlen(recvbuf));
if(--nready <= 0)
break;
}
}
}
// 7. 斷開連接
close(listenfd);
return 0;
}
測試結果:
1表示服務器,2,3,是兩個客戶端: