android socket非阻塞,Android c/c++ Socket(一)非阻塞模式

最近接了一个第三方的C库。是直接在native层创建socket通信的。

之前只了解java层的阻塞模式socket。于是初看到c端的socket写法非常困惑。最后发现是线程模型不一样,c端socket类似于 Java端的NIO(非阻塞模式)。

首先是File Description,简写FD,它是Linux特有的东西,类似于windows的句柄

Linux实现非阻塞式Socket靠的是一个struct和几个函数:

1. fd_set

typedef struct {

fd_mask fds_bits[FD_SETSIZE/NFDBITS];

} fd_set;

fd_set是一个结构体,成员只有一个unsigned long类型的数组。 fd_mask是unsigned long。

我把fd_set称为观察数组。观察的是句柄所指向的内容, 当发生改变后就会通知你。

数组成员一一对应一个文件句柄(socket、文件、管道、设备等)建立联系。调用FD_SET()建立联系。

2. FD_ZERO()

FD_ZERO就是把fd_set这个结构体初始化为0。

/* Inline loop so we don't have to declare memset. */

#define FD_ZERO(fd_set)

do {

size_t __i;

for (__i = 0; __i < sizeof(fd_set)/sizeof(fd_mask); ++__i) { \

(fd_set)->fds_bits[__i] = 0;

}

} while (0)

将fd_set观察数组初始化清零。就像memset()一样.

3. FD_SET()

把需要观察的socket fd加入到fd_set的观察数组。

4. FD_CLR()

从fd_set观察数组中移除。在这次socket编程中没有用到。

5. select()

int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);

这是实现非阻塞式socket最关键的函数。

当把需要观察的socket加入到fd_set以后。就开始循环调用 select()。当调用select()时,Linux内核会根据IO状态修改fd_set的内容,由此来通知哪个句柄发生了改变,有可读内容或有可写的机会了。一定要设置超时时间,一般是5ms。不然等待时间太长,thread就无法顺利结束。这里犯过错。

1.timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)

2.timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)

3.timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)

参数maxfd是需要监视的最大的文件描述符值+1;rdset是可读文件描述符的观察数组;wrset是可写文件描述符的观察数组,以及异常文件描述符的观察数组。

6. FD_ISSET()

当select()函数返回有改变时,调用FD_ISSET来确定发送改变的就是你关心的socket fd。

7. accept()

这个函数就建立server socket的时候使用的,类似于java的accept方法,阻塞等待客户端的scoket过来连接。但是这儿要讲的accept函数是不阻塞的。通过循环调用select来通知有客户端来建立连接, FD_ISSUT()函数来确定是客户端发过来的连接请求。

8.recv()

接收客户端或者服务端的数据。如何有可接收的数据,也是通过循环调用select函数来通知你的。再通过FD_ISSUT()函数来确定就是你关心的那个客户端或服务端。

下面是简单的流程:

static THREAD_RETVAL server_socket_thread(void *arg) {

server_socket *server_socket = arg;

int stream_fd = -1;

unsigned char packet[128];

memset(packet, 0, 128);

unsigned int readstart = 0;

while (1) {

fd_set rfds;

struct timeval tv;

int nfds, ret;

MUTEX_LOCK(server_socket->run_mutex);

if (!server_socket->running) {

MUTEX_UNLOCK(server_socket->run_mutex);

break;

}

MUTEX_UNLOCK(server_socket->run_mutex);

//设置超时时间5ms

tv.tv_sec = 0;

tv.tv_usec = 5000;

//rfds清零

FD_ZERO(&rfds);

if (stream_fd == -1) {

//server_socket->data_sock是做为服务端的socket句柄。放到观察数组里去

//以此来监听是否有客户端来建立连接。

FD_SET(server_socket->data_sock, &rfds);

nfds = server_socket->data_sock + 1; //最新监听句柄加一,just规则。

} else {

FD_SET(stream_fd, &rfds);

nfds = stream_fd + 1;

}

//检查是否有变化的fd, 这里的变化是有可读可写等等。

ret = select(nfds, &rfds, NULL, NULL, &tv);

if (ret == 0) {

/* Timeout happened */

continue;

} else if (ret == -1) {

//出现异常需要关闭

logger_log(server_socket->logger, LOGGER_INFO, "Error in select");

break;

}

//判断是否有客户端过来建立连接

if (stream_fd == -1 && FD_ISSET(server_socket->data_sock, &rfds)) {

struct sockaddr_storage saddr;

socklen_t saddrlen;

logger_log(server_socket->logger, LOGGER_INFO, "Accepting client");

saddrlen = sizeof(saddr);

stream_fd = accept(server_socket->data_sock, (struct sockaddr *) &saddr,

&saddrlen);

if (stream_fd == -1) {

//出现异常并关闭整个线程。

logger_log(server_socket->logger, LOGGER_INFO, "Error in accept %d %s", errno,

strerror(errno));

break;

}

}

//这里就是读取客户端的数据了。

if (stream_fd != -1 && FD_ISSET(stream_fd, &rfds)) {

// packetlen初始0

ret = recv(stream_fd, packet + readstart, 4 - readstart, 0);

if (ret == 0) {

/* TCP socket closed */

logger_log(server_socket->logger, LOGGER_INFO, "TCP socket closed");

break;

} else if (ret == -1) {

/* FIXME: Error happened */

logger_log(server_socket->logger, LOGGER_INFO, "Error in recv");

break;

}

readstart += ret;

if (readstart < 4) {

continue;

}

// 普通数据块

do {

// 读取剩下的124字节

ret = recv(stream_fd, packet + readstart, 128 - readstart, 0);

readstart = readstart + ret;

} while (readstart < 128);

//-----------------

在这里接受处理客户端数据,主要是根据协商好的协议读出数据

//-----------------

memset(packet, 0, 128);

readstart = 0;

}

}

//关闭server socket

if (stream_fd != -1) {

closesocket(stream_fd);

}

logger_log(server_socket->logger, LOGGER_INFO, "Exiting TCP server_socket_thread ");

return 0;

}

鉴于对Linux的Socket实现方式, JAVA NIO的原理就很好理解了,背后就是用了select模式。

最后再说一下阻塞模式和非阻塞模式:

1. 阻塞模式, 有一个缺点,就是严重浪费线程资源。服务端需要一对一的开启线程,给每一个客户端来处理数据交换。

2. 服务端和客户端连接后,并不是时时刻刻都有数据交换的,所以socket管道本身时间利用率就不高,完全可以通过不断查询客户端socket有没有可读可写的变化,再来处理读写。

3. 阻塞模式没有fd系列方法, 也没有select方法。只有accept(), rec()这两个阻塞的方法,有连接请求或者有数据可读时才返回。而非阻塞模式只要一条线程就可以处理很多客户端的连接。只要用select()不断的查询IO变化就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值