void tcpflush(tcpsock s, int64_t deadline) { if(s->type != MILL_TCPCONN) mill_panic("trying to send to an unconnected socket"); struct mill_tcpconn *conn = (struct mill_tcpconn*)s; if(!conn->olen) { errno = 0; return; } char *pos = conn->obuf; size_t remaining = conn->olen; while(remaining) { ssize_t sz = send(conn->fd, pos, remaining, 0); if(sz == -1) { if(errno != EAGAIN && errno != EWOULDBLOCK) return; int rc = mill_fdwait(conn->fd, FDW_OUT, deadline); if(rc == 0) { errno = ETIMEDOUT; return; } mill_assert(rc == FDW_OUT); continue; } pos += sz; remaining -= sz; } conn->olen = 0; errno = 0; }
tcpsock tcpaccept(tcpsock s, int64_t deadline) { if(s->type != MILL_TCPLISTENER) mill_panic("trying to accept on a socket that isn't listening"); struct mill_tcplistener *l = (struct mill_tcplistener*)s; while(1) { /* Try to get new connection (non-blocking). */ int as = accept(l->fd, NULL, NULL); if (as >= 0) { mill_tcptune(as); struct mill_tcpconn *conn = malloc(sizeof(struct mill_tcpconn)); if(!conn) { close(as); errno = ENOMEM; return NULL; } tcpconn_init(conn, as); errno = 0; return (tcpsock)conn; } mill_assert(as == -1); if(errno != EAGAIN && errno != EWOULDBLOCK) return NULL; /* Wait till new connection is available. */ int rc = mill_fdwait(l->fd, FDW_IN, deadline); if(rc == 0) { errno = ETIMEDOUT; return NULL; } mill_assert(rc == FDW_IN); } }
/* Pause current coroutine for a specified time interval. */ void mill_msleep(long ms, const char *current) { /* No point in waiting. Still, let's give other coroutines a chance. */ if(ms <= 0) { yield(); return; } /* Do the actual waiting. */ mill_fdwait(-1, 0, ms, current); }
size_t tcpsend(tcpsock s, const void *buf, size_t len, int64_t deadline) { if(s->type != MILL_TCPCONN) mill_panic("trying to send to an unconnected socket"); struct mill_tcpconn *conn = (struct mill_tcpconn*)s; /* If it fits into the output buffer copy it there and be done. */ if(conn->olen + len <= MILL_TCP_BUFLEN) { memcpy(&conn->obuf[conn->olen], buf, len); conn->olen += len; errno = 0; return len; } /* If it doesn't fit, flush the output buffer first. */ tcpflush(s, deadline); if(errno != 0) return 0; /* Try to fit it into the buffer once again. */ if(conn->olen + len <= MILL_TCP_BUFLEN) { memcpy(&conn->obuf[conn->olen], buf, len); conn->olen += len; errno = 0; return len; } /* The data chunk to send is longer than the output buffer. Let's do the sending in-place. */ char *pos = (char*)buf; size_t remaining = len; while(remaining) { ssize_t sz = send(conn->fd, pos, remaining, 0); if(sz == -1) { if(errno != EAGAIN && errno != EWOULDBLOCK) return 0; int rc = mill_fdwait(conn->fd, FDW_OUT, deadline); if(rc == 0) { errno = ETIMEDOUT; return len - remaining; } mill_assert(rc == FDW_OUT); continue; } pos += sz; remaining -= sz; } return len; }
tcpsock tcpconnect(ipaddr addr, int64_t deadline) { /* Open a socket. */ int s = socket(mill_ipfamily(addr), SOCK_STREAM, 0); if(s == -1) return NULL; mill_tcptune(s); /* Connect to the remote endpoint. */ int rc = connect(s, (struct sockaddr*)&addr, mill_iplen(addr)); if(rc != 0) { mill_assert(rc == -1); if(errno != EINPROGRESS) return NULL; rc = mill_fdwait(s, FDW_OUT, deadline); if(rc == 0) { errno = ETIMEDOUT; return NULL; } int err; socklen_t errsz = sizeof(err); rc = getsockopt(s, SOL_SOCKET, SO_ERROR, (void*)&err, &errsz); if(rc != 0) { err = errno; close(s); errno = err; return NULL; } if(err != 0) { close(s); errno = err; return NULL; } } /* Create the object. */ struct mill_tcpconn *conn = malloc(sizeof(struct mill_tcpconn)); if(!conn) { close(s); errno = ENOMEM; return NULL; } tcpconn_init(conn, s); errno = 0; return (tcpsock)conn; }
/* Pause current coroutine for a specified time interval. */ void mill_msleep(int64_t deadline, const char *current) { mill_fdwait(-1, 0, deadline, current); }
size_t tcprecvlh(tcpsock s, void *buf, size_t lowwater, size_t highwater, int64_t deadline) { if(s->type != MILL_TCPCONN) mill_panic("trying to receive from an unconnected socket"); struct mill_tcpconn *conn = (struct mill_tcpconn*)s; /* The buffer is between lowwater and highwater. */ if(conn->ilen >= lowwater && conn->ilen <= highwater) { memcpy(buf, &conn->ibuf[conn->ifirst], conn->ilen); conn->ifirst = 0; conn->ilen = 0; errno = 0; return conn->ilen; } /* The buffer is above the highwater. */ if(conn->ilen >= highwater) { memcpy(buf, &conn->ibuf[conn->ifirst], highwater); conn->ifirst += highwater; conn->ilen -= highwater; errno = 0; return highwater; } /* The buffer is below the lowwater. */ /* Let's move all the data from the buffer first. */ char *pos = (char*)buf; size_t remaining = highwater; memcpy(pos, &conn->ibuf[conn->ifirst], conn->ilen); pos += conn->ilen; remaining -= conn->ilen; conn->ifirst = 0; conn->ilen = 0; mill_assert(remaining); while(1) { if(remaining > MILL_TCP_BUFLEN) { /* If we still have a lot to read try to read it in one go directly into the destination buffer. */ ssize_t sz = recv(conn->fd, pos, remaining, 0); if(!sz) { errno = ECONNRESET; return highwater - remaining; } if(sz == -1) { if(errno != EAGAIN && errno != EWOULDBLOCK) return highwater - remaining; sz = 0; } if(highwater - remaining + (size_t)sz >= lowwater) { errno = 0; return highwater - remaining + (size_t)sz; } pos += sz; remaining -= sz; } else { /* If we have just a little to read try to read the full connection buffer to minimise the number of system calls. */ ssize_t sz = recv(conn->fd, conn->ibuf, MILL_TCP_BUFLEN, 0); if(!sz) { errno = ECONNRESET; return highwater - remaining; } if(sz == -1) { if(errno != EAGAIN && errno != EWOULDBLOCK) return highwater - remaining; sz = 0; } if(highwater - remaining + (size_t)sz < lowwater) { memcpy(pos, conn->ibuf, sz); pos += sz; remaining -= sz; conn->ifirst = 0; conn->ilen = 0; } else { memcpy(pos, conn->ibuf, remaining); conn->ifirst = remaining; conn->ilen = sz - remaining; errno = 0; return highwater - remaining + (size_t)sz; } } /* Wait till there's more data to read. */ int res = mill_fdwait(conn->fd, FDW_IN, deadline); if(!res) { errno = ETIMEDOUT; return highwater - remaining; } } }