【问题标题】:Socket programming Issue - C套接字编程问题 - C
【发布时间】:2022-10-06 00:30:14
【问题描述】:

我正在尝试制作一个简单的回显程序,其中包括一个客户端和一个在套接字编程下使用 TCP 协议的服务器。为此,我使用 C 来编写 server.c 和 client.c。

我遇到的问题是在 server.c 程序中,我试图在建立连接后从客户端接收消息,但一直失败。这发生在服务器程序中的 recv() API 中。这是两个程序:

1.)服务器.c:

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/ip.h>

int main(){

    //Let\'s create a socket here.
    int serverSoc = socket(PF_INET, SOCK_STREAM,0);


    struct sockaddr_in serverInfo;
    serverInfo.sin_family = PF_INET;
    serverInfo.sin_port = htons(8000);
    serverInfo.sin_addr.s_addr = INADDR_ANY; 
    socklen_t addrLen = (socklen_t)sizeof(serverInfo);

      //We need to bind the socket to the port number. So, here we make use of the in-built struct
    if(bind(serverSoc,(struct sockaddr*)&serverInfo,addrLen)<0){
        printf(\"The binding of the server socket with the given port has been unsuccessful\\n.\");
        return -1;
    }

    //After binding the socket, here we listen for connections from the clients
    if(listen(serverSoc, 1)<0){ //We used 1 here in the int backlog because that specifies the number of pending connections that the queue will hold. This will help in the case when multiple clients try to connect to the server.
        printf(\"We have an error in connecting to the client(s)\\n\");
        return -1; 
    }

    if(accept(serverSoc,(struct sockaddr*)&serverInfo,&addrLen)<0){
        printf(\"We have an error accepting the connections from the client(s).\\n\");
        return -1;
    }

    char recvBuff[50];
    if(recv(serverSoc,&recvBuff,sizeof(recvBuff),0)<0){
     printf(\"We have an error receiveing messages from the client.\\n\");
     printf(\"%zd\\n\",recv(serverSoc,recvBuff,sizeof(recvBuff),0));
     return -1;
    }else{
        printf(\"The size of recvBuff is:%d\\n\",(int)sizeof(recvBuff));
        printf(\"%s\\n\",recvBuff); 
    }
     
     char sendBuff[50] = \"hello\";
    if(send(serverSoc,&sendBuff,sizeof(sendBuff),0)<0){
        printf(\"We have an error sending message to the client.\\n\");
        return -1;
    }else{
         printf(\"%zd\\n\",send(serverSoc,&sendBuff,sizeof(sendBuff),0));
    }

return 0;
}

2.) 客户端.c:

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

int main(){
    char clientRecv[50];
    char clientSend[50] = \"Hello Server\\n\";
    //Here, we create a socket for the client with the in-built function socket that we get from sys/socket.h, socket(int domain, int type , int protocol)
    int clientSoc = socket(PF_INET,SOCK_STREAM,0);
    printf(\"The client socket has been assigned the value of: %d\\n\",clientSoc);
    if(clientSoc < 0){
        printf(\"We have an error\");
        return (-1);
    }

    //all the struct(s) that are needed for the program
    struct sockaddr_in serverInfo;
    serverInfo.sin_family = PF_INET;
    serverInfo.sin_port = htons(8000);
    serverInfo.sin_addr.s_addr = INADDR_ANY;

    //Connecting the client to the server
    if((connect(clientSoc,(struct sockaddr*)&serverInfo,(socklen_t)sizeof(serverInfo)))< 0){
        printf(\"Not connecting\");
return(-1);
    }

if(send(clientSoc,&clientSend,sizeof(clientSend),0)<0){
    printf(\"We are having an error in sending the message.\\n\");
    return -1;
}
int clientRecvmsg = (int)recv(clientSoc, &clientRecv,sizeof(clientRecv),0);
if(clientRecvmsg<0){
    printf(\"We have error receiving messages from the server.\\n\");
}else{
printf(\"clientRecvmsg: %s\\n\", clientRecv);
return -1;
}
return 0;
}
  • accept 返回用于与客户端通信的新套接字,如果 accept 不成功,则返回 -1。
  • 你真的应该使用getaddrinfo 而不是那些过时的 IPv4 东西。
  • @KompjoeFriek,我应该将接受函数中的第二个和第三个参数设置为 NULL 吗?
  • @Cheatah,感谢您的回复,我将研究“getaddrinfo”是什么并尝试实现它。

标签: c sockets unix server client


【解决方案1】:

不要在侦听服务器套接字 (serverSoc) 上调用 recv()。该套接字仅用于接受新连接。通信需要在accept() 返回的已连接套接字上进行。

所以,不要使用这个:

accept(serverSoc,...);
recv(serverSoc,...);

但一定要使用这个:

int conSoc = accept(serverSoc,...);
recv(conSoc,...);

一些解释,如果需要的话。

请注意,如果您同时只接受一个连接(目前您正在接受),这可能看起来过于复杂(为什么 API 会强制我使用 2 个不同的套接字?有人可能会问)。

但是,如果您不这样做,那么请考虑两种不同的事情可能随时同时发生在服务器上:

  • 已连接的客户端可能会发送或读取一些数据。或断开连接
  • 一个新的、尚未连接的客户端可能会连接。

想象一下,一个 Web 服务器:虽然它正在为三个不同的客户端提供一些大文件,但您不希望服务器拒绝来自第四个客户端的任何连接(至少如果您是 Google 则不会。我的服务器可能可以) .因此,虽然该服务器服务于 3 个客户端(读取他们所写的内容,写他们所读的内容),但它需要 4 个套接字。 3 (conSoc-like) 用于向 3 个客户端中的每一个发送/接收数据,第 4 (serverSoc-like) 用于获取传入的新连接。

在 C 中,您可以使用线程或进程来实现它。或poll 或select。在这种情况下,您将拥有 4 个线程/进程(1 个 recving/sending 来自/到每个客户端,在 conSoc 类似的套接字上,另一个 accepting 新客户端在 serverSoc). Or pass 4 sockets to listen@987654335 @poll(3conSoc-like to watch when a connected client is ready to recv or send. A 1 serverSocto watch when a new connection is ready to beaccept``ed。)

所以,长话短说,用于接收新连接的serverSoc 套接字与用于与这些连接对话的conSoc 不同。即使一次连接 1 个,原因也不是很明显。

【讨论】:

  • 使用完毕后不要忘记close() conSoc。
  • 谢谢你的帮助,chrslg
【解决方案2】:

您的代码有两个主要问题:

  1. 您的服务器使用了错误的套接字 recv() 和 send()。它需要使用accept() 返回的已连接套接字,而不是正在调用accept() 的侦听套接字。这就是recv() 失败的原因。

  2. 您的客户端是 connect()'ing 到错误的 IP 地址。 INADDR_ANY (0.0.0.0) 可以让服务器监听(它将监听所有本地接口),但不能让客户端连接。您必须连接到服务器绑定并侦听的实际 IP 地址。例如,如果您的客户端和服务器在同一台 PC 上运行,您可以改为连接到 INADDR_LOOPBACK (127.0.0.1)。

    在较小的说明中,您的代码也有一些其他问题:

    • 您说您正在尝试创建回显服务器,但您的服务并未尝试回显客户端发送的内容。

    • 调用recv() 后,您的服务器错误地使用了recvBuff。不能保证它是空终止的(即使客户端发送了一个空终止符),但您假设它总是会这样。您忽略了recv() 的返回值,它告诉您实际写入recvBuff 的字节数。

    • TCP 中的发送和读取之间没有 1:1 的关系。任何一种操作都可以报告比请求更少的字节,因此您必须准备好处理这种情况。如果send() 报告的字节数少于您要求发送的字节数,您必须再次调用send() 以发送剩余的未发送字节数。同样,如果recv() 报告的字节数少于您要求读取的字节数,则必须再次调用recv() 才能读取剩余的未读字节数。

    • 您在打印消息中调用recv() 和send(),您根本不应该这样做,尤其是在错误消息中。

    尝试更多类似的东西:

    服务器.c:

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<netinet/ip.h>
    
    ssize_t sendAll(int sockfd, const void *buf, size_t len) {
        const char *pbuf = (const char*) buf;
        while (len > 0) {
            ssize_t numSent = send(sockfd, pbuf, len, 0);
            if (numSent < 0) return -1;
            pbuf += numSent;
            len -= numSent;
        }
        return 0;
    }
    
    int main(){
    
        //Let's create a socket here.
        int serverSoc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (serverSoc < 0){
            printf("The creation of the server socket has been unsuccessful\n.");
            return -1;
        }
    
        struct sockaddr_in serverInfo;
        memset(&serverInfo, 0, sizeof(serverInfo));
        serverInfo.sin_family = AF_INET;
        serverInfo.sin_port = htons(8000);
        serverInfo.sin_addr.s_addr = INADDR_ANY; 
    
        //We need to bind the socket to the port number. So, here we make use of the in-built struct
        if (bind(serverSoc, (struct sockaddr*)&serverInfo, (socklen_t)sizeof(serverInfo)) < 0){
            printf("The binding of the server socket with the given port has been unsuccessful\n.");
            return -1;
        }
    
        //After binding the socket, here we listen for connections from the clients
        if (listen(serverSoc, 1) < 0){ //We used 1 here in the int backlog because that specifies the number of pending connections that the queue will hold. This will help in the case when multiple clients try to connect to the server.
            printf("We have an error in listening for clients\n");
            return -1; 
        }
    
        int clientSoc = accept(serverSoc, NULL, NULL);
        if (clientSock < 0){
            printf("We have an error accepting the connection from a client.\n");
            return -1;
        }
    
        char recvBuff[50];
        ssize_t numRecvd, numSent;
    
        while ((numRecvd = recv(clientSoc, recvBuff, sizeof(recvBuff), 0)) > 0){
            printf("The size of recvBuff is: %zd\n", numRecvd);
            printf("%.*s\n", (int)numRecvd, recvBuff); 
    
            if (sendAll(clientSoc, recvBuff, numRecvd) < 0){
                printf("We have an error sending a message to the client.\n");
                return -1;
            }
        }
        
        if (numRecvd < 0){
            printf("We have an error receiving a message from the client.\n");
            return -1;
        } 
    
        printf("The client disconnected.\n");
    
        close(clientSoc);
        close(serverSoc);
        
        return 0;
    }
    

    客户端.c:

    #include<stdio.h>
    #include<netinet/in.h>
    #include<netinet/ip.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<unistd.h>
    
    ssize_t sendAll(int sockfd, const void *buf, size_t len) {
        const char *pbuf = (const char*) buf;
        while (len > 0) {
            ssize_t numSent = send(sockfd, pbuf, len, 0);
            if (numSent < 0) return -1;
            pbuf += numSent;
            len -= numSent;
        }
        return 0;
    }
    
    ssize_t recvAll(int sockfd, void *buf, size_t len) {
        char *pbuf = (char*) buf;
        while (len > 0) {
            ssize_t numRecvd = recv(sockfd, pbuf, len, 0);
            if (numRecvd <= 0) return numRecvd;
            pbuf += numRecvd;
            len -= numRecvd;
        }
        return 1;
    }
    
    int main(){
        char clientRecv[50];
        char clientSend[50] = "Hello Server\n";
    
        //Here, we create a socket for the client with the in-built function socket that we get from sys/socket.h, socket(int domain, int type , int protocol)
        int clientSoc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        printf("The client socket has been assigned the value of: %d\n", clientSoc);
        if (clientSoc < 0){
            printf("We have an error");
            return (-1);
        }
    
        //all the struct(s) that are needed for the program
        struct sockaddr_in serverInfo;
        memset(&serverInfo, 0, sizeof(serverInfo));
        serverInfo.sin_family = AF_INET;
        serverInfo.sin_port = htons(8000);
        serverInfo.sin_addr.s_addr = INADDR_LOOPBACK;
    
        //Connecting the client to the server
        if (connect(clientSoc, (struct sockaddr*)&serverInfo, (socklen_t)sizeof(serverInfo)) < 0){
            printf("Not connecting");
            return -1;
        }
    
        size_t len = strlen(clientSend);
    
        if (sendAll(clientSoc, clientSend, len) < 0){
            printf("We are having an error in sending the message.\n");
            return -1;
        }
    
        ssize_t clientRecvmsg = recvAll(clientSoc, clientRecv, min(sizeof(clientRecv), len));
        if (clientRecvmsg <= 0){
            printf("We have error receiving a message from the server.\n");
            return -1;
        }
    
        printf("clientRecvmsg: %.*s\n", (int)clientRecvmsg, clientRecv);
    
        close(clientSoc);
        return 0;
    }
    

【讨论】:

  • 感谢您的帮助雷米勒博。我只是有一个问题,为什么你在接受之后在服务器中使用while循环而不是'if',为什么不通过send()函数发送一个字符串?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-19
  • 2023-03-27
  • 1970-01-01
相关资源
最近更新 更多