Socket编程:使用C编写UDP客户端-服务器

7
我正在尝试使用UDP编写客户端服务器程序,并使用等待和停止,但我还没有到达那个部分,我仍在努力弄清楚两个进程(服务器和客户端)如何通信,因为在我的客户端程序中,用户需要输入服务器名称或IP地址以及端口名称,然后发送一个表达式,服务器应该计算。然而,我在互联网上找到了一些教程,并按照所述进行编码,但我无法使客户端与服务器通信。以下是我的代码,请告诉我我做错了什么,如果是bind()sendto()recvfrom()socket(),或者全部都是。我看不出到底哪里出了问题。我知道客户端不应该在一个无限循环中运行,但目前我想让程序彼此通信,之后我会优化我的代码。谢谢!
客户端代码:
#include <stdio.h>      // Default System Calls
#include <stdlib.h>     // Needed for OS X
#include <string.h>     // Needed for Strlen
#include <sys/socket.h> // Needed for socket creating and binding
#include <netinet/in.h> // Needed to use struct sockaddr_in
#include <time.h>       // To control the timeout mechanism

#define EXPR_SIZE   1024
#define BUFLEN      512
#define TRUE        1
#define FALSE       0
#define SERVERLEN   1024

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

    long portNum;           // Since it's possible to input a value bigger
                            // than 65535 we'll be using long to
                            // avoid overflows
    char expr[EXPR_SIZE];
    char server[SERVERLEN];
    int fd;                 // file descriptor for the connected socket
    int buf[512];
    struct hostent *h;           // information of the host
    unsigned int addrLen;        // address length after getting the port number
    struct sockaddr_in myaddr;   // address of the client
    struct sockaddr_in servaddr; // server's address
    unsigned int exprLen;
    socklen_t slen = sizeof(servaddr);

    printf("Enter server name or IP address:");
    scanf("%s",server);
    printf("Enter port:");
    scanf("%ld",&portNum);
    if ((portNum < 0) || (portNum > 65535)) {
        printf("Invalid port number. Terminating.");
        return 0;
    }
    printf("Enter expression:");
    scanf("%s",expr);

    if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        perror("cannot create socket");
        return 0;
    }

    memset((char *)&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myaddr.sin_port = htons(0);

    if(bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0){
        perror("cannot bind");
        return 0;
    }

    /*
     // Discovering the port number the OS allocated
     addrLen = sizeof(myaddr);
     if(getsockname(fd, (struct sockaddr *)&myaddr, &addrLen) < 0){
     perror("cannot getsockname");
     return 0;
     }
     printf("local port number = %d\n", ntohs(myaddr.sin_port));
     */

    memset((char*)&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htonl(portNum);

    exprLen = sizeof(expr);


    while(TRUE){
        printf("Sending message to %s port %ld\n",server, portNum);
        if (sendto(fd, expr, strlen(expr), 0, (struct sockaddr *)&servaddr, slen) < 0) {
            perror("cannot sendto()");
        }
        printf("Success\n");

    }


    return 0;
}

服务器端代码:

#include <stdio.h>      // Default System Calls
#include <stdlib.h>     // Needed for OS X
#include <string.h>     // Needed for Strlen
#include <sys/socket.h> // Needed for socket creating and binding
#include <netinet/in.h> // Needed to use struct sockaddr_in
#include <time.h>       // To control the timeout mechanism

#define EXPR_SIZE   1024
#define BUFLEN      512
#define TRUE        1
#define SERVERLEN   1024

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

    struct sockaddr_in myaddr;  // address of the server
    struct sockaddr_in claddr;  // address of the client
    char buf[BUFLEN];
    int fd;
    long recvlen;
    socklen_t clientlen;



    if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        perror("cannot create socket");
        return 0;
    }

    memset((char *)&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myaddr.sin_port = htons(0);

    if(bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0){
        perror("cannot bind");
        return 0;
    }
    clientlen = sizeof(claddr);

    while (TRUE) {
        recvlen = recvfrom(fd, buf, BUFLEN, 0, (struct sockaddr *)&claddr, &clientlen);
        if (recvlen < 0) {
            perror("cannot recvfrom()");
            return 0;
        }
        printf("Received %ld bytes\n",recvlen);
        buf[recvlen] = 0;
        printf("Received message: \"%s\"\n",buf);

    }

    return 0;
}

服务器程序不输出任何内容,而客户端则一直输出,直到进程被中断:
Enter server name or IP address:127.0.0.1
Enter port:30
Enter expression:2+2
Sending message to 127.0.0.1 port 30
cannot sendto(): Can't assign requested address

我尝试将服务器名称更改为localhost,以及其他端口,但都没有成功。

2
我无法使客户端与服务器通信。你能具体说明一下吗?程序是否崩溃,是否没有输出,是否产生错误的输出?请同时提供您已经收集到的任何调试结果。 - kaylum
1
您的客户需要发送到服务器绑定的相同端口。 - kaylum
1
对于客户端中的这一行代码:scanf("%s",server);,在%s格式说明符上没有最大输入字符修饰符。因此用户可以越过输入缓冲区,导致未定义的行为并可能导致段错误事件。建议修改为:scanf("%1023s",server); - user3629249
1
客户端软件的编译结果会出现多个警告信息,例如未使用的参数、未使用的变量、已设置但未使用的变量等。解决这些警告的好方法是将当前的main()函数签名替换为int main(void) - user3629249
1
在调用大多数系统函数时,例如 scanf(),始终检查返回的值(而不是参数值),以确保操作成功。 - user3629249
显示剩余4条评论
1个回答

15

在开发网络软件时(特别是使用BSD套接字接口时),重要的是在建立基本通信之前尽可能保持简单。然后可以逐步添加功能,同时确保在此过程中不会破坏任何东西。

对于客户端而言,保持简单意味着:

  • 不要在客户端调用bind函数。操作系统会选择一个适当的接口并分配一个随机端口号,因此没有必要bind套接字。

  • 使用硬编码服务器地址(例如127.0.0.1)。地址127.0.0.1(0x7f000001)是本地主机地址,适用于将数据包发送到同一台机器上的服务器。

  • 使用硬编码端口号(例如50037)。临时端口号应大于0xC000十六进制(49152十进制)。

  • 使用硬编码消息,例如“hello”。

考虑到这一点,客户端软件如下所示:

int main( void )
{
    int fd;
    if ( (fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        perror("socket failed");
        return 1;
    }

    struct sockaddr_in serveraddr;
    memset( &serveraddr, 0, sizeof(serveraddr) );
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons( 50037 );              
    serveraddr.sin_addr.s_addr = htonl( 0x7f000001 );  

    for ( int i = 0; i < 4; i++ ) {
        if (sendto( fd, "hello", 5, 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0 ) {
            perror( "sendto failed" );
            break;
        }
        printf( "message sent\n" );
    }

    close( fd );
}

在服务器端,保持简单意味着:

  • 绑定到INADDR_ANY,即让操作系统选择适当的接口。
  • 绑定到一个硬编码端口,例如50037(必须是客户端使用的相同端口)。
  • 不要从recvfrom请求地址信息,即将NULL,0作为最后两个参数传递。

考虑到这点,下面是服务器软件的样子

int main( void )
{
    int fd;
    if ( (fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        perror( "socket failed" );
        return 1;
    }

    struct sockaddr_in serveraddr;
    memset( &serveraddr, 0, sizeof(serveraddr) );
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons( 50037 );
    serveraddr.sin_addr.s_addr = htonl( INADDR_ANY );

    if ( bind(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0 ) {
        perror( "bind failed" );
        return 1;
    }

    char buffer[200];
    for ( int i = 0; i < 4; i++ ) {
        int length = recvfrom( fd, buffer, sizeof(buffer) - 1, 0, NULL, 0 );
        if ( length < 0 ) {
            perror( "recvfrom failed" );
            break;
        }
        buffer[length] = '\0';
        printf( "%d bytes: '%s'\n", length, buffer );
    }

    close( fd );
}

在“玩具”应用程序中,我怀疑没有人会有任何异议,但总的来说:不要使用临时端口。如果你谷歌这个话题,你会发现有些人这样做并遇到了问题,或者你可以采纳他们的建议:http://books.google.si/books?id=Pm4RgYV2w4YC&pg=PA705&lpg=PA705&dq=use%20Ephemeral%20port%20for%20server&source=bl&ots=qwnYpJGseD&sig=3EihPoqMeNH06ge5lChaRmi9hFg&hl=en&sa=X&ei=JoZsU4GBJ4iL7Ab-yICYCA&redir_esc=y#v=onepage&q=use%20Ephemeral%20port%20for%20server&f=false。 - Brian Vandenberg

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