zoukankan      html  css  js  c++  java
  • socket执行accept函数时没有进入阻塞状态,而是陷入了无限循环

    接着前两天继续看《VC深入详解》的网络编程部分,这次我快速看了遍书上的函数以及套接字C-S模型,然后自己从0开始写了个简单的服务端,结果发现一直在输出

    而明明我还没有写客户端程序,由于打印的代码只有一处,在如下的while循环里

    	while (true)
    	{
     		/* 5. 接收客户端发送的连接请求 */
     		SOCKET sockConnect = accept(sockServer, (SOCKADDR*)&addrClient, &len);
     		/* 6. [发送/接收]数据 */
    		char sendBuf[BUFSIZ];
    		char ipBuf[INET_ADDRSTRLEN];
    		sprintf(sendBuf, "Welcome [IP: %s] to Server",
    			inet_ntop(AF_INET, &addrClient.sin_addr, ipBuf, sizeof(ipBuf)));
     		send(sockConnect, sendBuf, sizeof(sendBuf), 0);
    
    		char recvBuf[BUFSIZ];
    		recv(sockConnect, recvBuf, BUFSIZ, 0);
    		printf("Receive Data: %s
    ", recvBuf);
    		/* 7. 断开连接,关闭套接字 */
    		closesocket(sockConnect);
    	}
    

    引用《UNIX网络编程》:accept函数由TCP服务器调用,用于从完成连接队列头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠(假定套接字为默认的阻塞方式)。

    调试发现,每一次accept函数都成功完成并执行后续代码,所以才会有无限循环打印的现象。仔细对比书上代码和说明,我的accept函数也没有用错,于是头疼了很久。

    How often have I said to you that when you have eliminated the impossible, whatever remains, however improbable, must be the truth?

    既然我没有得到程序错误的真相,那么说明我没有排除掉所有的错误。回顾之前代码,我跟示例代码的区别就在于我把绑定IP和端口的代码封装起来了。

        /* 3. 绑定套接字到本地的地址和端口 */
        SOCKADDR_IN addrServer = SockAddr(6000, "127.0.0.1");
        /* 4. 将套接字设为监听模式, 准备接收连接请求 */
        listen(sockServer, SOMAXCONN);
    inline SOCKADDR_IN SockAddr(u_short port, PCSTR ip, int af = AF_INET)
    {
    	SOCKADDR_IN sockAddr_In;
    	sockAddr_In.sin_family = af;
    	sockAddr_In.sin_port = htons(port);
    	auto& addr = sockAddr_In.sin_addr.S_un.S_addr;
    	int err = inet_pton(af, ip, &addr);
    	if (err == 0)
    	{
    		throw std::runtime_error("IP地址字节序从网络转换到主机出错!");
    	}
    	else if (err == -1)
    	{
    		throw std::runtime_error("IP地址输入格式无效!");
    	}
    	return sockAddr_In;
    }
    

    但是错误并没有出在我的SockAddr函数,而是在于我没有绑定!漏掉了关键的一行代码

    bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));

    我仅仅只是创建了一个包含协议家族、IP地址、端口号的结构体SOCKADDR_IN,虽然也输入了IP和端口信息,但是却没有把SOCKADDR_IN当做SOCKADDR类型(套接字的地址信息)和SOCKET(套接字本身)进行绑定,而没有绑定的后果呢?

    继续引用《UNIX网络编程》:如果一个TCP客户或服务器未曾调用bind捆绑一个端口,当调用connect或listen时,内核就要为套接字选择一个临时端口。让内核选择临时端口对于TCP服务器来说非常罕见,因为服务器是通过它们的众所周知端口被大家认识。

    调试发现sockConnect(也就是accept的返回值)是4294967295。稍微敏感的就会发现,这是32位无符号整型的上限值,也就是2^32-1,而SOCKET类型实际上就是UINT_PTR(unsigned int表示的指针,也就是32位地址)

    typedef UINT_PTR        SOCKET;

    再仔细看看《UNIX网络编程》上关于accept函数的解释

    若出错则为-1,UNIX上的accept是返回int,而windows上则是相当于返回unsigned int,-1的二进制表示就是每一位都为1,对应unsigned int的上限值(UINT_MAX)。所以刚才并不是每次都连接成功,而是每次都连接出错(因为试图用一个未绑定地址的服务器socket来接收客户的连接请求),返回错误码-1。如果accept正常,则是有客户连接则返回一个表示连接的socket,没有客户连接则使进程睡眠直到有客户连接(即阻塞)。

    总结下来,根本原因还是socket没有配置好就调用accept函数了,同样,如果注释掉listen那行,也会出现同样的现象。

    ----------------------------------------------------------粗心的分割线----------------------------------------------------------

    最后附上一点感悟,急于求成反而会浪费更多的时间,但也有个好处,如果照着书上代码敲一遍,运行正确,然后再看看每个函数代表什么,也许当时就清楚了,但是后来出错时可能就不知道到底掉到那个坑了。有出错经验也不错。《UNIX网络编程》确实是本不错的书,对套接字API讲得很详细,像htnol还有inet_addr等转换函数还是看这本书讲得更清楚。

  • 相关阅读:
    Java并发实现一(并发的实现之Thread和Runnable的区别)
    Java中的enum
    Eclipse+Maven创建webapp项目
    手机上最简洁的"云笔记"软件
    工具与艺术的结合:浅谈博客的排版规范与样式设计
    页面定制CSS代码初探(四):cnblogs使用Github引用样式
    脑图工具MindNode"附属节点"是什么意思 图解
    页面定制CSS代码初探(三):设置正文最小高度
    Sublime 是自动检测而非自动设置缩进
    苹果操作系统名称演变史 新名称macOS
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/6346255.html
Copyright © 2011-2022 走看看