C语言 网络编程_socket编程“陷阱识别”

目前遇到2个陷阱:

  1. server端在受到异常退出后,想要再次启动发现bind函数进行系统报错:"Address already in use"
  2. accept函数虽然可以传出addr_len参数为addr结构体实际大小(16 字节),但是如果不事先初始化好addr_len参数的话,会导致accep函数拿不到当前连接客户端的IP地址和端口号。

先说“陷阱1”

如果先ctrl+c结束服务器端程序的话,再次启动服务器就会出现Address already in use这个错误,或者你的程序在正常关闭服务器端socket后还是有这个问题。

代码如下:

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>

#define PORT 8888//设置端口号
#define LEN 1024
#define LOCALIP "127.0.0.1"

int main()
{
    int sock_fd = 0;//sock句柄
    int connect_fd = 0;//accept返回的句柄
    char buff[LEN] = {0};
    struct sockaddr_in server;//下面用于存储服务器的基本信息:报文类型、端口、IP
    struct sockaddr_in client;//可以通过accept函数的传出参数,得到客户端的报文类型、端口、IP配置
    socklen_t socklen = 0;//socklen表明客户端sock套接字长度,accept函数会更新此数据
    printf("socklen is :%d\n", socklen);
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//赋值fd
    if(sock_fd < 0)//sock初始化失败
    {
        perror("sock build");
        return -1;
    }
    else//sock初始化成功
    {
        //初始化服务器信息:类型、端口、IP
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        inet_pton(AF_INET, LOCALIP, &server.sin_addr.s_addr);//点分十进制地址的(主机字节序)转换成(网络字节序)

        if(bind(sock_fd, (struct sockaddr*)&server, sizeof(server)) < 0)//绑定sock配置失败
        {
            perror("bind build");
            return -1;
        }
        else//绑定sock配置成功
        {
            //SOMAXCONN的大小可以通过cat /proc/sys/net/core/somaxconn查看,默认4kb(4096字节)
            if(listen(sock_fd, SOMAXCONN) < 0)//监听失败
            {
                perror("listen build");
                return -1;
            }
            else//监听成功
            {
                connect_fd = accept(sock_fd, (void*)&client, &socklen);
                printf("sizeof(client) = %ld,&client is: %p,socklen is :%d\n", sizeof(client), &client, socklen);
                if(connect_fd < 0)//连接失败
                {
                    perror("connect");
                }
                else//连接成功
                {
                    int ret;
                    char remoteip[20];
                    int remoteport = ntohs(client.sin_port);
                    inet_ntop(AF_INET, &client.sin_addr.s_addr, remoteip, sizeof(remoteip));
                    printf("22sizeof(client) = %ld,&client is: %p,socklen is :%d\n", sizeof(client), &client, socklen);
                    while(1)
                    {
                        ret = read(connect_fd, buff, LEN);
                        if(ret < 0)
                        {
                            perror("read");
                            break;
                        }
                        else if(ret == 0)    
                        {
                            printf("write dead.\n");
                            break;
                        }             
                        printf("收到来自IP[%s]Port[%d]的信息:%s\n",remoteip, remoteport, buff);                   
                    }
                    

                    close(connect_fd);//关闭传输句柄
                    close(sock_fd);//关闭连接句柄
                    return 0;
                }
            }
        }
    }
}

整个程序是进行正常的句柄关闭的。

bind 函数普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。

等待 TIME_WAIT 结束可能是令人恼火的一件事,特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。

代码如下:

// 设置套接字选项避免地址使用错误
    int on=1;
    if((setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
    {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

“陷阱2”

先来看accept函数的拆解:

  • 包含头文件<sys/socket.h>
  • 功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
  • 原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 参数:
    • sockfd:服务器套接字
    • addr:将返回对等方的套接字地址
    • addrlen:返回对等方的套接字地址长度
  • 返回值:成功返回非负整数,失败返回-1

坑的地方就在这个addrlen参数

先说结论:调用accept函数之前一定要初始化addrlen参数,其大小必须大于或等于struct sockaddr addr结构体的大小(也就是16字节)。

我一开始的代码如下:

nt main()
{
    int sock_fd = 0;//sock句柄
    int connect_fd = 0;//accept返回的句柄
    char buff[LEN] = {0};
    struct sockaddr_in server;//下面用于存储服务器的基本信息:报文类型、端口、IP
    struct sockaddr_in client;//可以通过accept函数的传出参数,得到客户端的报文类型、端口、IP配置
    socklen_t socklen = 0;//socklen表明客户端sock套接字长度,accept函数会更新此数据
    printf("socklen is :%d\n", socklen);
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//赋值fd
    if(sock_fd < 0)//sock初始化失败
    {
        perror("sock build");
        return -1;
    }
    else//sock初始化成功
    {
        //初始化服务器信息:类型、端口、IP
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        inet_pton(AF_INET, LOCALIP, &server.sin_addr.s_addr);//点分十进制地址的(主机字节序)转换成(网络字节序)

        if(bind(sock_fd, (struct sockaddr*)&server, sizeof(server)) < 0)//绑定sock配置失败
        {
            perror("bind build");
            return -1;
        }
        else//绑定sock配置成功
        {
            //SOMAXCONN的大小可以通过cat /proc/sys/net/core/somaxconn查看,默认4kb(4096字节)
            if(listen(sock_fd, SOMAXCONN) < 0)//监听失败
            {
                perror("listen build");
                return -1;
            }
            else//监听成功
            {
                connect_fd = accept(sock_fd, (void*)&client, &socklen);

发现获取的客户端IP地址和端口号是0.0.0.0 port:0,此时我在accept函数下打印了addrlen参数的数值,发现其也已经被accept函数变更为16了。

原来是系统内核会先通过addrlen原始的数值大小进行判断, 是否能够满足struct sockaddr addr结构体的数据存储,如果不满足,则放弃本次参数的传出(所以获得全0的客户端ip和端口号),在函数结束之前,在返回addrlen的数值为sizeof(struct sockaddr addr)也就是“16字节”。

所以虽然accept函数调用结束后,打印出来的addrlen从0变成了16,但是却没有成功保存客户端ip和端口。

以上是我在网络编程中遇见的2个小陷阱,希望能帮助到各位coder。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值