本文主要记录socket学习概要,主要分析了非阻塞编程
字节序
大端:高尾端 0x01020304 内存由低到高:01 02 03 04
小端:低尾端 0x01020304 内存由低到高:04 03 02 01
优劣势:大端首位可以快速判断正负;小端强制类型转换方便,不用调整内存数据。
网络字节序:使用大端传输,数据顺序传输,每位数据都当独立数据传输。
转化函数
1)字节序转换
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
2) IP转换
int inet_pton(int af, const char *src, void *dst); “10.0.0.101” --> 123424
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
这两种支持IPV6
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
只支持IPV4,并且inet_ntoa返回值保存为静态变量里,连续使用会覆盖前面的值。
套接字选项获取和设置
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
1) 字段说明
sockfd: 套接字描述符
level: 选项协议层。通用套接字 SOL_SOCKET,
其他协议层,IPPROTO_IP,IPPROTO_TCP和IPPROTO_IPV6。
optname: 访问/设置类型名称。
optval: 数据缓存区,存放返回数据,或者要设置的数据。根据不同类型有不同数据结构。
optlen: 数据缓存区大小。
2) 返回说明
成功:return 0
失败:return -1,并设置errno
EBADF:sockfd不是有效的文件描述符
EFAULT:optval指向内存并非有效的进程空间
EINVAL:在调用setsockopt时,optlen无效
ENOPROTOOPT:level指定协议层不能识别
ENOSOCK:sockfd不是套接字描述符
3) 重用sockfd
在服务器关闭后,已经和客户端连接的socket,不会立刻关闭,会出现TIME_WAIT一段时间,此时重启服务器会bind失败。
解决此问题用optname=SO_REUSEADDR, optval = 1(int)
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSERADDR, &optval, sizeof(optval));
3) 禁止TIME_WAIT阶段
int optval = 0;
setsockopt(sockfd, SOL_SOCKET, SO_DONTLINGER, &optval, sizeof(optval));
5)发送超时
windows: int outtime = 1000; //1秒
linux: struct timeval outtime = {1, 0};
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &outtime, sizeof(outtime));
6)接收超时
windows: int outtime = 1000; //1秒
linux: struct timeval outtime = {1, 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &outtime, sizeof(outtime));
7)收发缓冲器设置
int membuf = 32 * 1024; //32K
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &membuf, sizeof(membuf));
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &membuf, sizeof(membuf));
8)设置广播属性
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
9)设置关闭时等待发送数据发送完毕
struct linger {
u_short l_onoff; // 1 - 允许发送完毕后在关闭(等待时间为0时,立即关闭即使还有数据未发送),0 - 立即关闭(未发送的数据发送后关闭)
u_short l_linger; //允许等待时间(秒)
};
struct linger m_slinger;
m_slinger.l_onoff = 1;
m_slinger.l_linger = 5;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &m_slinger, sizeof(m_slinger));
非阻塞编程
1)fcntl函数
int fcntl(int fd, int cmd, ... /* arg */ );
fd: 套接字描述符
cmd: F_SETFL/F_GETFL 设置或获取文件属性
arg: O_NONBLOCK|O_NDELAY
O_NDELAY:早起版本,有bug,在没有数据和文件末尾,读取都返回0.
2)阻塞和非阻塞设置
int flags;
flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
3)非阻塞模式下 accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
没有新连接时,
return: -1
errno: EWOULDBLOCK(11)
4)非阻塞模式下connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
连接成功:
return 0;
可能成功:
return -1;
errno: EINPROGRESS
连接失败:
return -1;
errno: !=EINPROGRESS
int my_connect(int sockfd, struct sockaddr *serveraddr)
{
int ret = connect(sockfd, sereveraddr, sizeof(serveraddr));
if (ret == 0)
return 0;
if (errno != EINPROGRESS)
return -1;
fd_set wset;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
struct timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;
//超时、出错、连接出错
if (select(sockfd + 1, NULL, &wset, NULL, &timeout) == -1)
return -1;
if (!FD_ISSET(sockfd, &wset)) //不可写
return -1;
int error;
socklen_t len = sizeof(error);
//调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return -1;
if (error != 0)
return -1;
return 0;
}
5)非阻塞模式下write
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t write(int fildes, const void *buf, size_t nbyte);
返回值:
return == 0; socket关闭,发送失败
return > 0; 发送成功
return < 0; 发送失败。EWOULDBLOCK|EAGAIN没有空间写数据,EINTR连接正常,操作中断可继续发送数据。
int writeNonBlock(int sockfd, char *send_buf, size_t send_len)
{
int write_pos = 0;
int nLeft = send_len;
while (nLeft > 0) {
int nWrite = write(sockfd, send_buf + write_pos, nLeft);
if (nWrite == 0)
return -1;
if (nWrite < 0) {
if (errno == EINTR) {
//可使用select检查是否可写
continue;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
//nWrite = 0;
continue;
} else {
return -1;
}
} else {
nLeft -= nWrite;
write_pos += nWrite;
}
}
return 0;
}
6)非阻塞模式下read
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t read(int fildes, void *buf, size_t nbyte);
返回值:
return == 0; socket关闭,发送失败
return > 0; 发送成功
return < 0; 发送失败。EINTR表示操作被中断,可以继续读取;EWOULDBLOCK|EAGAIN,表示本来应该阻塞的立即返回,可以下次继续读取数据
int readNonBlock(int sockfd, char *buf, size_t buf_len)
{
int nread = 0;
while (1) {
nread = read(sockfd, buf, buf_len);
if (nread > 0)
return 0;
if (nread == 0)
return -1;
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
//检测是否可读
continue;
}
return -1;
}