10-套接字的多种可选项

参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨

测试环境:Ubuntu 10.10

GCC版本:4.4.5

 

一、套接字多种可选项

        套接字可选项是分层的。IPPROTO_IP层可选项是IP协议相关事项,IPPROTO_TCP层可选项是TCP协议相关的事项,SOL_SOCKET层是套接字相关的通用可选项。

可设置套接字的多种可选项
协议层选项名读取设置
SOL_SOCKETSO_SNDBUF(发送缓冲区大小)OO
SO_RCVBUF(接收缓冲区大小)OO
SO_REUSEADDROO
SO_KEEPALIVEOO
SO_BROADCASTOO
SO_OOBINLINEOO
SO_ERROROO
SO_TYPE(套接字类型)OO
IPPROTO_IPIP_TOSOO
IP_TTLOO
IP_MULTICAST_TTLOO
IP_MULTICAST_LOOPOO
IP_MULTICAST_IFOO
IPPROTO_TCPTCP_KEEPALIVEOO
TCP_NODELAYOO
TCP_MAXSEGOO

 

二、getsockopt & setsockopt

* 读取套接字可选项:getsockopt

头文件:#include <sys/socket.h>
函数公:读取套接字可选项
返回值:成功时返回0,失败时返回-1
函数原型:int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);
函数参数:
sock——用于查看套接字文件描述符
level——要查看的可选项协议层
optname——要查看的可选项名
optval——保存查看结果的缓冲地址值
optlen——向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。

* 更改套接字可选项:setsockopt

头文件:#include <sys/socket.h>
函数公:更改套接字可选项
返回值:成功时返回0,失败时返回-1
函数原型:int setsockopt(int sock, int level, int optname, const void* optval, socklen_t optlen);
函数参数:
sock——用于更改套接字文件描述符
level——要更改的可选项协议层
optname——要更改的可选项名
optval——保存要更改的选项信息的缓冲地址值
optlen——向第四个参数optval传递的可选项信息的字节数

 

* 介绍getsockopt函数使用方法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>

void errorHandling(const char* message);

int main(void)
{
    int tcpSock, udpSock;
    int sockType;
    socklen_t optlen;
    int state;

    optlen = sizeof(sockType);
    tcpSock = socket(PF_INET, SOCK_STREAM, 0);
    udpSock = socket(PF_INET, SOCK_DGRAM, 0);
    printf("SOCK_STREAM:%d\n", SOCK_STREAM);
    printf("SOCK_DGRAM:%d\n", SOCK_DGRAM);

    state = getsockopt(tcpSock, SOL_SOCKET, SO_TYPE, (void*)&sockType, &optlen);

    if(state)
        errorHandling("getsockopt() error!");
    printf("Socket type one:%d\n", sockType);

    state = getsockopt(udpSock, SOL_SOCKET, SO_TYPE, (void*)&sockType, &optlen);
    if(state)
        errorHandling("getsockopt() errro!");
    printf("Socket type two:%d\n", sockType);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

操作:

1)编译:gcc main.c -o getSockOpt.out,运行:./getSockOpt.out

SOCK_STREAM:1
SOCK_DGRAM:2
Socket type one:1
Socket type two:2

分析:

        第20、25行获取套接字类型信息。如果是TCP套接字,将获得SOCK_STREAM常数值1;如果是UDP套接字,则获得SOCK_DGRAM的常数值2。

 

三、SO_SNDBUF & SO_RCVBUF

* SO_RCVBUF:输入缓冲大小相关可选项

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>

void errorHandling(const char* message);

int main(void)
{
    int sock;
    int sendBuf;
    int recvBuf;
    int state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    len = sizeof(sendBuf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sendBuf, &len);

    if(state)
        errorHandling("getsockopt() error");

    len = sizeof(recvBuf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvBuf, &len);

    if(state)
        errorHandling("getsockopt() error");

    printf("Input buffer size:%d\n", recvBuf);
    printf("Output buffer size:%d\n", sendBuf);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

操作:gcc main.c -o getBuf.out,运行:./getBuf.out

Input buffer size:87380
Output buffer size:16384

 

* SO_SNDBUG:输出缓冲大小相关可选项

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>

void errorHandling(const char* message);

int main(void)
{
    int sock;
    int sendBuf = 1024 * 3;
    int recvBuf = 1024 * 3;
    int state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvBuf, sizeof(recvBuf));
    if(state)
        errorHandling("setsockopt() error!");

    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sendBuf, sizeof(sendBuf));
    if(state)
        errorHandling("setsockopt() error!");

    len = sizeof(sendBuf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&sendBuf, &len);
    if(state)
        errorHandling("getsockopt() error!");
    printf("Input buffer size: %d\n", sendBuf);

    len = sizeof(recvBuf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&recvBuf, &len);
    if(state)
        errorHandling("getsockopt() error!");
    printf("Output buffer size: %d\n", recvBuf);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

操作:gcc main.c -o setBuf.out,运行:./setBuf.out

Input buffer size: 6144
Output buffer size: 6144

分析:

       缓冲大小的设置需谨慎处理,因此不会完全按照我们的要求进行,只是通过调用setsockopt函数向系统传递我们的要求。

 

四、SO_REUSEADDR

1.回顾程序:回声客户端/服务端程序

* 回声服务端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define TRUE 1
#define FALSE 0

void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock;
    int clientSock;
    char message[30];
    int option;
    int strLen;
    socklen_t optlen, clientAddrSize;
    struct sockaddr_in servAddr, clientAddr;
    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error!");

    /*
    optlen = sizeof(option);
    option = TRUE;
    setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, (void*)*option, optlen);
    */

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error!");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error!");

    clientAddrSize = sizeof(clientAddr);
    clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
    if(-1 == clientSock)
        errorHandling("accept() error!");


    while((strLen = read(clientSock, message, sizeof(message))) != 0)
    {
        write(clientSock, message, sizeof(message));
        write(1, message, strLen);  //printf
        memset(message, 0, sizeof(message));
    }

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

编译:gcc reuseadrEserver.c -o reuseadrEserver.out,运行:./reuseadrEserver.out 9190:

* 回声客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    int clntDataLen = -1;
    struct sockaddr_in servAddr;

    if(argc != 3)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("connect() error");
    else
        puts("Connected............");


    while(1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        write(sock, message, strlen(message));
        clntDataLen = read(sock, message, BUF_SIZE);
        message[clntDataLen] = 0;
        printf("Message from server: %s\n", message);
    }

    close(sock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

编译:gcc echoClient.c -o echoClient.out,运行:./echoClient.out 127.0.0.1 9190

 

a)正常客户端/服务端通信:

服务端:

xxx@xxx-vm:~/workspace/network/TCPIP/09/reuseadrEserver$ ./reuseadrEserver.out 9190
123
123
123
123456
123
123

客户端:

Connected............
Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): 123456
Message from server: 123456

Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): 123
Message from server: 123

b)在客户端输入Q/q消息,或通过CTRL+C终止程序:

Input message(Q to quit): ^C

客户端和服务端都能正常退出。

c)通过CTRL+C终止服务端程序,再次运行服务端程序:

bind() error!

分析:

终止服务端程序后不能立刻重新运行服务端程序。需要过几分钟才可以正常运行。正常运行后,客户端需要重新和服务端建立连接才可以正常通信。

 

2.当服务器端程序和客户端程序建立连接后,通过CTRL+C终止服务端程序后,服务端程序无法立刻重新启动

分析原因:客户端程序每次运行程序时都会动态分配端口号,因此无需过多关注Time-wait。通过CTRL+C终止服务器端程序后,套接字处在Time-wait过程时,相应端口是正在使用状态,导致重新启动服务端程序调用bind函数时报错。

 

解决办法:重新分配服务端套接字端口号(更改套接字可选项)。套接字的可选项中更改SO_REUSEADDR的状态,SO_REUSEADDR的默认值为0(假),这意味着无法分配Time-wait状态下的套接字端口号,因此需要将这个值改为1(真)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define TRUE 1
#define FALSE 0

void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock;
    int clientSock;
    char message[30];
    int option;
    int strLen;
    socklen_t optlen, clientAddrSize;
    struct sockaddr_in servAddr, clientAddr;
    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error!");

    ///*
    optlen = sizeof(option);
    option = TRUE;
    setsockopt(servSock, SOL_SOCKET, SO_REUSEADDR, (void*)*option, optlen);
    //*/

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error!");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error!");

    clientAddrSize = sizeof(clientAddr);
    clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
    if(-1 == clientSock)
        errorHandling("accept() error!");


    while((strLen = read(clientSock, message, sizeof(message))) != 0)
    {
        write(clientSock, message, sizeof(message));
        write(1, message, strLen);  //printf
        memset(message, 0, sizeof(message));
    }

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

五、TCP_NODELAY

* Nagle算法

作用:Nagle算法用于TCP层,为防止因数据包过多而发生网络过载。

TCP套接字默认使用Nagle算法交换数据,因此最大限度地进行缓冲,直到收到ACK。从上图可知,减少了数据包传输。

当网络流量未受太大影响时,不使用Nagle算法要比使用它时传输速度快

* 如何引用Nagle算法

禁用Nagle算法,只需将套接字可选项TCP_NODELAY改为1(真)。

int optVal = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&optVal, sizeof(optVal));

查看Nagle算法的设置状态:

int optVal;
socklen_t optLen;
optLen = sizeof(optVal);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&optVal, &optLen);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值