C语言 套接字编程:C 中的 UDP 客户端-服务器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35568996/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 10:20:37  来源:igfitidea点击:

Socket Programming: UDP Client-Server in C

csocketsunixclient-serverunix-socket

提问by William Studart

I'm trying to write a client server program using UDP, and wait-and-stop, but I haven't got to that part, I'm still trying to figure it out how the two processes (server and client) communicate, because on my client program, the user needs to enter the server name or IP address, and a port name, and then send an expression that the server should calculate. However, I dug some tutorials in the internet and after coding accordingly (or I thought so) I can't make the client communicate with the server. Below is my code, please enlighten me what I'm doing wrong, if it's the bind(), sendto(), recvfrom()or socket(), or all of them. I can't see what exactly is wrong. I know that the client-side shouldn't run on a infinite loop, but so far I want to make the programs communicate with each other, afterwards I'll polish my code. Thanks!

我正在尝试使用 UDP 和等待并停止编写客户端服务器程序,但我还没有到那部分,我仍在尝试弄清楚两个进程(服务器和客户端)如何通信,因为在我的客户端程序中,用户需要输入服务器名称或IP地址,以及端口名称,然后发送服务器应该计算的表达式。但是,我在互联网上挖了一些教程,并在相应地编码后(或者我这么认为)我无法让客户端与服务器通信。下面是我的代码,请告诉我我做错了什么,如果是bind(), sendto(),recvfrom()socket(),或全部。我看不出到底出了什么问题。我知道客户端不应该无限循环运行,但到目前为止我想让程序相互通信,然后我会改进我的代码。谢谢!

client-side code:

客户端代码:

#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;
}

Server-side code:

服务端代码:

#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;
}

The server program doesn't output anything, while the client outputs until the process is interrupted:

服务器程序不输出任何内容,而客户端输出直到进程中断:

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

I tried changing the server name to localhost, and other ports, but to no avail.

我尝试将服务器名称更改为 localhost 和其他端口,但无济于事。

回答by user3386109

When developing networking software (especially when using the BSD socket interface), it's important to keep things as simple as possible until you've established basic communication. Then you can incrementally add functionality, while making sure that you don't break anything along the way.

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

On the client side, keeping things simple means

在客户端,保持简单意味着

  • Don't call bindin the client. The OS will choose an appropriate interface and assign a random port number, so there's no need to bindthe socket.

  • Use a hard-coded server address (e.g. 127.0.0.1). Address 127.0.0.1 (0x7f000001) is the local host address, suitable for sending packets to a server on the same machine.

  • Use a hard-coded port number (e.g. 50037). Ephemeral port numbersshould be greater than 0xC000 hex (49152 decimal).

  • Use a hard-coded message, e.g. "hello".

  • 不要打电话bind给客户。操作系统将选择一个合适的接口并分配一个随机端口号,因此不需要bind套接字。

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

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

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

With that in mind, here's what the client software looks like

考虑到这一点,这就是客户端软件的样子

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 );
}

On the server side, keeping things simple means

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

  • Bind to INADDR_ANY, i.e. let the OS pick an appropriate interface.
  • Bind to a hard-coded port, e.g. 50037 (must be the same port the client uses).
  • Don't request the address information from recvfrom, i.e. pass NULL, 0as the last two parameters.
  • 绑定到INADDR_ANY,即让操作系统选择合适的接口。
  • 绑定到硬编码端口,例如 50037(必须与客户端使用的端口相同)。
  • 不要从 请求地址信息recvfrom,即NULL, 0作为最后两个参数传递。

With that in mind, here's what the server software looks like

考虑到这一点,这就是服务器软件的样子

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] = '##代码##';
        printf( "%d bytes: '%s'\n", length, buffer );
    }

    close( fd );
}