要写一个简单的WebSocket客户端,在写的过程中手贱于是重写了所有的Socket,TCP,SSL和HTTP的代码。因为原来的HTTP只是一个简单的HTTP/1.0的客户端,为了让能给WebSocket使用,决定对HTTP/1.1进行有限的支持。
在开发HTTP/1.1的时候遇到一个问题:Request发送出去之后,迟迟收不到Response。查找了半天发现因为WebSocket是一个长连接和原来的HTTP/1.0的一锤子买卖不一样。原有的处理逻辑是一直read,直到Socket连接断开就可以了。但是对于一个长连接来说这种处理方法显然是不合适的。
借鉴了很多的HTTP Client的代码,后来选择在recv的过程中就把HTTP Response给处理了,对Header以及Content进行分别处理。然后发现可以一次recv一个Byte,这样处理起来很方便,而且逻辑很顺畅。
前面已经测试完毕了,但是挂到环境里一测试又出问题了,还是Response收不到。原因是Select和SSL_read不太兼容导致的。代码中为了能加入timeout,所以在所有读写之前都进行了Select:
int ret = select (....);
if (ret > 0) {
SSL_read(...)/SSL_write(...)/ recv(...)/ send(...);
}
这里有一个问题,因为每一次都只收1 byte。在之前测试时用的是recv,没有问题。但是在用SSL_read的时候,再进行select就会超时。想了半天,可能是SSL_read会一次把Tcp的Buffer读进来,从而导致下次select的时候可能没有数据了,也就超时了。实际测试发现这个猜测是对的。怎么办呢?解决方案是请SSL_pending出马,这个函数可以检测目标SSL中到底有多少可读数据。那么在Select之前先用它来做一次测试,这样就解决了有数据却读不出来的问题。最后贴上真实的代码,以备后用。
int Tls::recv(char * buf, int len) {
if (ssl == NULL) {
return Socket::recv(buf, len);
}
if (buf == NULL || len <= 0) {
err = ERR_PARAM_NULL;
return -1;
}
int pending = SSL_pending(ssl);
if (pending <= 0) {
if (!blocking) {
if (waitReadable(timeout) < 0) {
return -1;
}
}
}
if (pending > 0 && len > pending) {
len = pending;
}
int ret = SSL_read(ssl, buf, len);
if (ret <= 0) {
sslerr = SSL_get_error(ssl, ret);
if (sslerr == SSL_ERROR_WANT_READ) {
err = ERR_TLS_WANT_READ;
}
}
return ret;
}