本节关键字:Linux、C语言、套接字、参数设置
相关C库函数:setsockopt、getsockopt、printf、connect、send、select、assert
int setsockopt(int sock, int level, int optname, const void *optval, socklent_t optlen);
/*
@param sock:将要被设置或选取选项的套接字
@param level:选项所在的协议层,SOL_OCKET:通用套接字,IPPROTO_IP:IP选项,IPPROTO_TCP:TCP选项
@param optname:需要访问的选项名,
@param optval:指向包含新选项值的缓冲
@param optlen:长度
@return 成功返回0,失败返回-1,errno被设为以下某一值
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
对应宏名称 | 说明 |
---|---|
SOL_OCKET | 通用套接字 |
IPPROTO_IP | IP选项 |
IPPROTO_TCP | TCP选项 |
宏名称 | 参数类型 | 说明 |
---|---|---|
SO_BROADCAST | int | 允许发送广播数据 |
SO_DEBUG | int | 允许调试 |
SO_DONTROUTE | int | 不查找路由 |
SO_ERROR | int | 获得套接字错误 |
SO_KEEPALIVE | int | 保持连接 |
SO_LINGER | struct linger | 延迟关闭连接 |
SO_OOBINLINE | int | 带外数据放入正常数据流 |
SO_RCVBUF | int | 接收缓冲区大小 |
SO_SNDBUF | int | 发送缓冲区大小 |
SO_RCVLOWAT | int | 接收缓冲区下限 |
SO_SNDLOWAT | int | 发送缓冲区下限 |
SO_RCVTIMEO | struct timeval | 接收超时 |
SO_SNDTIMEO | struct timeval | 发送超时 |
SO_REUSERADDR | int | 允许重用本地地址和端口 |
SO_TYPE | int | 获得套接字类型 |
SO_BSDCOMPAT | int | 与BSD系统兼容 |
宏名称 | 参数类型 | 说明 |
---|---|---|
IP_HDRINCL | int | 在数据包中包含IP首部 |
IP_OPTINOS | int | IP首部选项 |
IP_TOS | int | 服务类型 |
IP_TTL | int | 生存时间 |
宏名称 | 参数类型 | 说明 |
---|---|---|
TCP_MAXSEG | int | TCP最大数据段的大小 |
TCP_NODELAY | int | 不适用Nagle算法 |
TCP一次最多发送多少数据是由系统决定的,自行设置的数据大于系统承受的最大数据时失效,自行设置的数据小于系统承受的最大数据时有效
//发送缓冲区
int tmp, bufLen=1024*2;
socklen_t optlen = sizeof(int);
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char *)&buflen, sizeof(int));
getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (int*)&tmp, &optlen);
printf("send_tmp=%d,optlen=%d\n",tmp,(int)optlen); //设置发送缓冲区2048
//接收缓冲区
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char *)&buflen, sizeof(int));
getsockopt(fd,SOL_SOCKET, SO_RCVBUF,(int *)&tmp, &optlen);
printf("recv_tmp=%d,optlen=%d\n",tmp,(int)optlen); //设置接收缓冲区2048
1.Windows下timeout类型为int,Unix下timeout为struct timeval
2.通过设置为0秒和0微秒禁止超时(即一直阻塞),缺省情况下,超时都是禁止的
3.缺省状态为禁止超时,缓冲区被填满之后,再次向缓冲区中拷贝内容会出现一直阻塞的现象,此问题的解决只能在创建socket连接时设置超时时间,发送(send)或接收(recv)阻塞等待超时后会返回-1
int recvTimeout = 30 * 1000; //30s
int sendTimeout = 30 * 1000; //30s
setsockopt(fd, SOL_SOCKET, SO_RCVTIME0, (char *)&recvTimeout, sizeof(int));
setscokopt(fd, SOL_SOCKET, SO_SNDTIME0, (char *)&sendTimeout, sizeof(int));
/**
* 设置Socket发送超时,不设置时,缓冲区满再次send时默认会一直阻塞 */
#if defined(_WIN32)
int sendTimeout = 30 * 1000; //30s
int ret = setsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, (const char*)&sendTimeout, sizeof(int));
if (ret < 0) {
printf("Error: failed to setsockopt\n");
exit(0);
} else {
int timeout;
getsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(int));
printf("Windows time out: %d s\n", timeout);
}
#else
struct timeval tv;
tv.tv_sec = 30;
tv.tv_usec = 0;
int ret = setsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct timeval));
if (ret < 0) {
printf("Error: failed to setsockopt\n");
exit(0);
} else {
struct timeval tv;
socklen_t optlen = sizeof(struct timeval);
getsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, &tv, &optlen);
printf("Unix time out: %d s, %d us\n", tv.tv_sec, tv.tv_usec);
}
#endif
HP-Unix环境不适配参数SO_SNDTIMEO与SO_RCVTIMEO的设置,该环境下为防止发生send()与recv()的阻塞,可先使用select()进行套接字读写判断,相关参数为SO_SNDLOWAT、SO_RCVLOWAT,注意:套接字读写权限的select()在connect()之后才会生效,不然依旧会一直阻塞。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void print_sockopt(int m_Socket);
int main(void)
{
int ret, m_Socket;
m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_Socket < 0) return -1;
assert(m_Socket != -1);
printf("socket: %d\n", m_Socket);
struct timeval tv = {10, 0};
int optlen = sizeof(tv);
printf("Unix time out: %d s, %d us, len=%d\n", tv.tv_sec, tv.tv_usec, optlen);
ret = setsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
if (ret < 0) printf("set error\n");
assert(ret != -1);
printf("Unix time out: %d s, %d us, len=%d\n", tv.tv_sec, tv.tv_usec, optlen);
optlen = sizeof(tv);
printf("optlen=%d\n", optlen);
ret = getsockopt(m_Socket, SOL_SOCKET, SO_SNDTIMEO, &tv, &optlen);
if (ret < 0) printf("get error\n");
assert(ret != -1);
printf("Unix time out: %d s, %d us, len=%d\n", tv.tv_sec, tv.tv_usec, optlen);
print_sockopt(m_Socket);
int optval = 655350;
optlen = sizeof(optval);
setsockopt(m_Socket, SOL_SOCKET, SO_SNDBUF, &optval, optlen);
setsockopt(m_Socket, SOL_SOCKET, SO_RCVBUF, &optval, optlen);
optval = 360000;
setsockopt(m_Socket, SOL_SOCKET, SO_SNDLOWAT, &optval, optlen);
print_sockopt(m_Socket);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("172.16.100.149");
addr.sin_port = htons(12345);
if (connect(m_Socket, (struct sockaddr*)&addr, sizeof(addr)) == -1)
{
printf("connect error, close: %d\n", close(m_Socket));
return -1;
}
//if (fcntl(m_Socket, F_SETFL, O_NDELAY) < 0) printf("err: fcntl\n");
char send_buf[350001] = {0};
memset(send_buf, 'a', 350000);
int i = 1;
while (true)
{
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(m_Socket, &wfds);
tv.tv_sec = 3;
tv.tv_usec = 0;
printf("begin to select\n");
ret = select(m_Socket+1, NULL, &wfds, NULL, &tv);
if (ret <= 0)
{
if (errno != EINTR) printf("select error, i=%d\n", i=1);
else printf("select EINTR\n");
continue;
}
else if (ret == 0) printf("select tiemout\n");
else printf("select over\n");
int ret = send(m_Socket, send_buf, 350000, 0);
if (ret <= 0) break;
else printf("send cnt: %d, bytes: %d\n", i++, ret);
}
close(m_Socket);
return 0;
}
void print_sockopt(int m_Socket)
{
int optval;
int optlen = sizeof(optval);
getsockopt(m_Socket, SOL_SOCKET, SO_SNDBUF, &optval, &optlen);
printf("SO_SNDBUF: %d\n", optval);
optlen = sizeof(optval);
getsockopt(m_Socket, SOL_SOCKET, SO_RCVBUF, &optval, &optlen);
printf("SO_RCVBUF: %d\n", optval);
optlen = sizeof(optval);
getsockopt(m_Socket, SOL_SOCKET, SO_SNDLOWAT, &optval, &optlen);
printf("SO_SNDLOWAT: %d\n", optval);
optlen = sizeof(optval);
getsockopt(m_Socket, SOL_SOCKET, SO_RCVLOWAT, &optval, &optlen);
printf("SO_RCVLOWAT: %d\n", optval);
}