保持C语言中的套接字开放状态

3
我认为这可能是一个简单的解决方案,我只是想太多了。我正在编写一个非常基本的聊天程序,客户端和服务器轮流发送消息。现在,我让它只发送一次来回的消息,然后客户端关闭套接字。该程序不必同时打开套接字,只要能像秋千一样来回切换即可,而不是像真正的聊天程序那样可以同时从两侧接收多个输入。
在客户端中使用while循环会保持其打开状态,但while循环的条件是什么?
我尝试过几种不同的条件,但它们都没有起作用...它只会让它挂起。我还尝试注释掉服务器代码中的一些close()函数,但也没有用。
我还有一个小问题,就是接收到的输入打印出来是乱码,但我认为这是因为在字符串数组中没有内容时打印了内存地址...我只是想不起如何缩短它。哈哈。
Server.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3490"  // The port users will be connecting to

#define BACKLOG 10   // How many pending connections queue will hold

char input[20];
char *pointer;

void sigchld_handler(int s)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

// Get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd, new_fd;  // Listen on sock_fd, new connection on new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr; // Connector's address information
    socklen_t sin_size;
    struct sigaction sa;
    int yes = 1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                             p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                       sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        return 2;
    }

    freeaddrinfo(servinfo); // All done with this structure

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler; // Reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    sin_size = sizeof their_addr;

    while((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) > 0) {  // Main accept() loop

        if (new_fd == -1) {
            perror("accept");
            continue;
        }

        inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s);
        printf("server: got connection from %s\n", s);

        if (!fork()) { // this is the child process
            close(sockfd); // child doesn't need the listener

            char input[20];
            char *pointer;
            printf("Type in an server's input: ");
            scanf("%s", input);
            pointer = input;  //Will need to clean this up to be more effcient... later

            if (send(new_fd, pointer, strlen(input), 0) == -1) //Need to change the length to
                                                               //the actual length of the
                                                               //input... later.

                perror("send");
            close(new_fd);
            exit(0);
        }

        char Cinput[20];
        if ((recv(new_fd, Cinput, strlen(Cinput), 0)) == 0) { //NEW LINE ADDED HERE
            printf("No more messages");
        }

        if ((recv(new_fd, Cinput, strlen(Cinput), 0)) == -1) {
                perror("recv");
                exit(1);
        }

        printf("Server: received '%s'\n",Cinput);

        close(new_fd);  // Parent doesn't need this
    }
    return 0;
}

Client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#define PORT "3490" // The port client will be connecting to

#define MAXDATASIZE 100 // Max number of bytes we can get at once

// Get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes;
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    /*
        This is what is in the struct

        struct addrinfo {
            int ai_flags; // AI_PASSIVE, AI_CANONNAME, etc.
            int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
            int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
            int ai_protocol; // Use 0 for "any"
            size_t ai_addrlen; // Size of ai_addr in bytes
            struct sockaddr *ai_addr; // struct sockaddr_in or _in6
            char *ai_canonname; // Full canonical hostname
            struct addrinfo *ai_next; // Linked list, next node
        };

        getaddrinfo() will return a pointer to this
    */
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // Loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                             p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
              s, sizeof s);
    printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo); // All done with this structure

    while(1)//NEW LINE ADDEDthis is getting the client to repeat asking for the input, but doesn't send it.
    {
        if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == 0) { //NEW LINE ADDED
               printf("Shutdown");
        }

        if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
            perror("recv");
            exit(1);
        }

        buf[numbytes] = '\0';

        printf("client: received '%s'\n",buf);

        char Cinput[20];
        char *pointer;
        printf("Type in an client's input: ");
        scanf("%s", Cinput);
        pointer = Cinput;
        if (send(sockfd, pointer, strlen(Cinput), 0) == -1)
        {
            perror("send");
            close(sockfd);
            exit(0);
        }
    }

    close(sockfd); //As soon as the client receives a message, it closes the socket.
    //We probably need a while loop in here in order to keep the socket open,
    //but what are the parameters for the while loop?
    return 0;
}

您的循环应该在连接存在的同时,执行客户端预期使用该连接进行的任何操作。这不是一个真正的问题。 - user207421
1
好的,我提到过我想保持客户端的连接开放(这样它就可以继续接受消息),我尝试了不同的条件,但都没有成功。我刚刚编辑了客户端的while条件,现在它只是不断地要求输入而不是发送它。 - Hokerie
1个回答

5
以下伪代码将确保同一个客户端和服务器可以无限发送消息,直到其中一个挂断:

客户端:

  1. 创建socket
  2. 连接到它。
  3. 接收数据。
  4. 如果接收返回0,则表示另一端已执行有序关闭。 转到步骤7。
  5. 发送响应。
  6. 转到步骤3。
  7. 停止。

服务器

  1. 创建socket
  2. 绑定套接字到地址。
  3. 将套接字标记为监听
  4. 接受连接。
  5. 如果接受的连接无效,请转到步骤4。
  6. 发送数据。
  7. 接收响应。
  8. 如果接收返回0,则表示另一端已执行有序关闭。 转到步骤4以接受新连接。
  9. 转到步骤6。

1
不是的。关闭连接是由recv返回0来指示的。-1表示recv失败了。 - Anish Ramaswamy
1
这个伪代码还确保服务器一次只能为一个客户端提供服务。真正的服务器会启动一个单独的线程来处理步骤6-9。 - user207421
@Hokerie,是的,你绝对需要一个while循环。如果你跟踪伪代码,你会发现它确实在循环。 - Anish Ramaswamy
@Hokerie,你能把修改后的代码包含在你的问题中吗?我无法确定哪些部分的代码被编辑了。 - Anish Ramaswamy
我在某处读到,对于UDP,recv的返回值可能为0,表示接收到了一个0字节的数据包,因此如果还有其他数据包需要接收,就不应该关闭连接。 - mLstudent33
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接