【问题标题】:c socket prog - select() problemsc socket prog - select() 问题
【发布时间】:2011-09-12 19:48:59
【问题描述】:

我是网络编程新手。我必须用 C 编写一个简单的客户端/服务器程序。服务器将侦听连接,客户端将连接到服务器,发送消息,并接收来自客户端的回显。我们必须使用 select() 更新它以同时处理从服务器进程到多个客户端的连接。我尝试按照指示在客户端实现 select(),但我认为我在 if(FD_ISSET(clientSockfd, &readfds)) 部分的客户端有一个无限循环。

//client1.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <resolv.h>
#include <arpa/inet.h>


const int BUF_SIZE = 512;

int main(int argc, char *argv[]) {

    char buf[BUF_SIZE], buf2[BUF_SIZE];
    char *msg;
    struct sockaddr_in serverInfo;
    int clientSockfd, errorCheck, readVal, numfd;
    struct hostent *hostName;
    fd_set readfds;

    //make sure user entered correct arguments when starting client
    if(argc != 3)
    {
        printf("error: must enter 'programName portNumber hostname'\n");
        exit(errno);
    }

    //create socket and error check socket() call
    clientSockfd=socket(AF_INET, SOCK_STREAM, 0);
    if (clientSockfd == -1)
    {
        perror("error creating socket");
        exit(errno);
    }

    //assign sockaddr_in info for RemoteAddr
    bzero(&serverInfo, sizeof(serverInfo));
    serverInfo.sin_family=AF_INET;

    hostName=gethostbyname(argv[2]);
    if(hostName == NULL)
    {
        herror("Error when calling gethostbyname()");
        exit(errno);
    }

    memcpy((unsigned char *) &serverInfo.sin_addr, (unsigned char *)hostName->h_addr, hostName->h_length); //copy IP address to be used
    serverInfo.sin_port=htons(atoi(argv[1]));       //port number to be used, given in command line, must be converted to network byte order

    //connect to server side
    if(connect(clientSockfd, (struct sockaddr *)&serverInfo, sizeof(serverInfo)) == -1)
    {
        perror("error when connecting to server");
        exit(errno);
    }

    while(1)
    {
        FD_ZERO(&readfds);  //zero out set
        FD_SET(fileno(stdin), &readfds);
        FD_SET(clientSockfd, &readfds);

        int maxfd = fileno(stdin);
        if(maxfd < clientSockfd)  maxfd = clientSockfd;
            numfd = select(maxfd, &readfds, 0, 0, 0);   //call select()

        if(numfd > 0)
        {
            if(FD_ISSET(clientSockfd, &readfds))
            {
                //make sure buf is empty so it doesnt print extra chars
                bzero(buf2, BUF_SIZE);
                read(clientSockfd, buf2, BUF_SIZE);

            }
            if(FD_ISSET(fileno(stdin), &readfds))
            {
                bzero(buf, BUF_SIZE);
                fgets(buf, BUF_SIZE-1, stdin);
                printf("echo from server: %s\n", buf);
                errorCheck = write(clientSockfd, buf, strlen(buf)+1);
                if(errorCheck == -1)
                {
                    perror("error writing");
                }       
            }
        }
        else if(numfd == 0)
        {
            perror("Error using select()\n");
            exit(0);
        }
        else
            printf("no data\n");
    }
    //close connection to server
    errorCheck = close(clientSockfd);
    if(errorCheck == -1)
    {
        perror("Error closing connection.");
        exit(errno);
    }
    return 0;
}

这里是服务器..

//server.c

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


const int ASSIGNED_PORT = 17000;
const int BUF_SIZE = 512;

int main() {

    int serverfd, clientfd;
    struct sockaddr_in serverSock;      //NOTE: a pointer to sockaddr_in can be cast to a pointer to 
    //      a struct sockaddr - useful for connect()

    char buf[BUF_SIZE];
    int errorCheck, msgLength;

    //create socket with error checking (-1 ret on error)
    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if(serverfd < 0 )
    {
        perror("socket failed.");
        exit(errno);
    }

    //assign sockaddr_in info for server
    bzero(&serverSock, sizeof(serverSock));     //set to all 0's
    serverSock.sin_family = AF_INET;                
    serverSock.sin_addr.s_addr = htonl(INADDR_ANY);
    serverSock.sin_port = htons(ASSIGNED_PORT);


    //bind a name to the socket with error checking (0 ret on success)
    errorCheck = bind(serverfd, (struct sockaddr*)&serverSock, sizeof(serverSock));
    if(errorCheck < 0 )
    {
        perror("bind failed.");
        exit(errno);
    }

    //listen for connections with error checking (0 ret on success)
    errorCheck = listen(serverfd, 10);
    if(errorCheck < 0 )
    {
        perror("listen failed.");
        exit(errno);
    }

    printf("Listening for connections.  Enter CNTRL-c to kill server.\n");

    //create infinite loop to accept, read, write, and close connections with error hecking 
    while(1)
    {

        //accept the connection from the client 
        clientfd = accept(serverfd, 0, 0);
        if(clientfd ==  -1)
        {
            perror("error accepting connection.");
            exit(errno);
        }

        //read data from the client
        bzero(buf, BUF_SIZE);
        msgLength = read(clientfd, buf, BUF_SIZE-1);
        if(msgLength == -1)
        {
            perror("error reading from client");
            close(clientfd);
            close(serverfd);
            exit(errno);
        }

        if(buf[0] '\0')
        {
            printf("connection closing");
            exit(0);
        }
        //print what the client sent
        printf("Message from client: %s\n", buf);

        //echo back what the client sent
        errorCheck = write(clientfd, buf, strlen(buf)+1);   

        if(errorCheck == -1 )
        {
            perror("error writing to client");
            exit(errno);
        }

        //close the connection
        errorCheck = close(clientfd);
        if(errorCheck == -1)
        {
            perror("error closing connection");
            exit(errno);
        }       
    }

    errorCheck = close(serverfd);
    if(errorCheck==-1)
    {
        perror("error closing server, exiting program now");
        sleep(6);
        exit(errno);
    }
    return 0;
}

【问题讨论】:

  • 请正确格式化您的代码。每行之间都有大量的空白行。
  • 快速浏览会发现一些事情:您的"/0" 应该是"\0"(两个NUL 字符)吗?此外,我认为您想要 if (buf[0] == '\0') (请注意用于 char 比较的单引号)。除此之外,bzero 已弃用,herror 未定义。

标签: c sockets select-function


【解决方案1】:

我认为您的客户端代码中的问题(或至少是一个问题)是您将幻数 32 传递给 select()。这是不正确的。您应该传递的是所有套接字编号中的最大值,再加一。例如,像这样:

int maxfd = fileno(stdin);
if (maxfd < clientSockFD) maxfd = clientSockFD;
// further maximizations for other sockets would go here, if you had any other sockets...
numfd = select(maxfd+1, &readfds, 0, 0, 0);

【讨论】:

  • 那么,也许一个好问题是我应该如何测试客户端以查看选择功能是否正常工作?我想我可能会感到困惑,因为我所看到的一切总是在服务器端而不是客户端实现 select() 。所以,我有点困惑。此外,当我在客户端发送文本字符串时,它限制我在关闭前只发送 3 个。这是为什么呢??
  • 糟糕,我的代码 sn-p 出错了,选择的第一个参数应该是 maxfd+1,而不仅仅是 maxfd。我将编辑我的答案以更正。
  • 至于为什么你的客户端的socket会被关闭,大概是因为服务器端设置成只从TCP连接中读取一个流,然后关闭TCP连接。
【解决方案2】:

您需要在服务器端使用select() 来处理多个客户端,而不是在客户端。

【讨论】:

  • select() 对连接的任一侧都有帮助,具体取决于所需的行为。
  • 我首先在服务器端实现了 select(),我的老师告诉我这不是他想要的,选择应该在客户端,而不是服务器
  • Select 在您希望程序同时处理多个套接字文件的任何地方都很有用。它最常用于服务器,因为服务器通常希望能够同时处理多个客户端连接,但它在客户端中也很有用(如您的情况,您希望处理在标准输入和来自TCP 套接字同时)
  • 有道理。我感谢您的帮助。我相信我终于让它像我的老师想要的那样工作了。我想太多了。
  • @newuser12 你的老师为什么会提出这个奇怪的要求?它与常见的行业惯例背道而驰。