读、写、异常事件发生条件
可读的条件:
套接口缓存区有数据可读;
连接的读一半关闭,即接受到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
#include
#include
#include
#include
#include
#include
#include
#icnldue
#include
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,是两个客户端: