【问题标题】:C Server/Client with Sockets带有套接字的 C 服务器/客户端
【发布时间】:2016-11-19 20:55:20
【问题描述】:

[仅剩 2 个小问题]

我尝试用 C 编写一个简单的服务器/客户端来通过套接字发送消息。 它必须在带有 MinGW 的 Linux 和 Windows 下运行。我发现了很多 Linux 的例子,但是很多人都不能使用 Windows。 如果你能帮助我,那就太好了。

对于服务器,我有一些我不明白的地方。

我在服务器端做什么?

  1. 需要在 Windows 上初始化 WSA,在 Linux 上不需要。
  2. 在服务器端为服务器创建一个套接字。
  3. 为服务器创建 struct sockaddr_in。
  4. 在 ANY-IP 上绑定套接字。
  5. 监听套接字。
  6. 接受连接,使用 newSocket 处理连接。
  7. 用 6) 关闭新的 Socket 重复。

只有当我不关闭 newSocket 时它才能正常工作,但为什么呢? (EBADF 错误)

Edit2 后的代码更新:

// IMPORTANT: On linker errors try -lws2_32 as Linkerparameter
#ifdef __WIN32__
# include <winsock2.h> // used for sockets with windows, needs startup / shutdown
# include <ws2tcpip.h> // for MinGW / socklen_t
# define INIT_SOCKET_SYSTEM WSADATA wsaData;if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {printf ("Error initialising WSA.\n");exit(6);}
# define CLEAR_SOCKET_SYSTEM WSACleanup();
# include <windows.h> // for Sleep
# define SLEEP Sleep(10); // sleeping 10ms
# define CLOSE_SOCKET_FUNCTION closesocket
#else
# include <sys/socket.h>
# define INIT_SOCKET_SYSTEM printf("Linux dont need a special init for sockets, so all fine.\n");
# define CLEAR_SOCKET_SYSTEM printf("Linux dont need a special clear for sockets, so all fine.\n");
# include <time.h>
# define SLEEP sleep(1); // sleeping a second :-/
# define CLOSE_SOCKET_FUNCTION close
#endif
#include <stdio.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <strings.h> // used for bzero
//used in the tutorial but not necessary!?
//#include <sys/types.h>
//#include <unistd.h>
//#include <stdlib.h>


/* could be still useful
// polling 10ms...
//#include <time.h>
//#define SLEEP time_t tStart, tEnd;time(&tStart);do {time(&tEnd);} while (difftime(tEnd, tStart) < 0.01);
 */
/* Random Sources
 * http://pubs.opengroup.org/onlinepubs/9699919799/
 * http://linux.die.net/man/2
 * http://stackoverflow.com/questions/31765278/simple-webserver-wont-work
 * http://blog.stephencleary.com/2009/05/using-socket-as-server-listening-socket.html
 * 
 * http://cs.baylor.edu/~donahoo/practical/CSockets/WindowsSockets.pdf
 */

// functions
void createListenSocket(int * retListenSocket, const int port, bool * isRunning);
void listenFor(int * listenSocket, bool * isRunning);
void acceptFor(int * listenSocket, socklen_t * addrlen, bool * isRunning);
void handleConnection(int * inSocket, struct sockaddr_in * addClient);

// http://www.gnu.org/software/libc/manual/html_node/Cleanups-on-Exit.html
int * cleanSocket;
void cleanUp() {
    CLOSE_SOCKET_FUNCTION(* cleanSocket);
    CLEAR_SOCKET_SYSTEM
}


//[todo] WSAGetLastError handling for windows
int main(int argc, char ** argv) {
    atexit(cleanUp);
    bool isRunning = true;
    socklen_t addressLen = sizeof(struct sockaddr_in);
    
    // create listening socket
    const int port = 15000;
    int listenSocket;
    cleanSocket = &listenSocket;
    
    createListenSocket(&listenSocket, port, &isRunning);
    listenFor(&listenSocket, &isRunning);
    while (isRunning) {
        acceptFor(&listenSocket, &addressLen, &isRunning);
        SLEEP
    }
    
    return 0;
}

void createListenSocket(int * retListenSocket, const int port, bool * isRunning) {
    INIT_SOCKET_SYSTEM
    struct sockaddr_in addServer;
    (* retListenSocket) = socket(AF_INET, SOCK_STREAM, 0);
    int tErr = errno;
    if ((* retListenSocket) > 0) {
        printf("The socket was created (%i)\n", * retListenSocket);
    } else {
        printf("Couldnt create socket\n- ");
        switch (tErr) {
            case EACCES:
                printf("Permission to create a socket of the specified type and/or protocol is denied.\n");
                break;
            case EAFNOSUPPORT:
                printf("The implementation does not support the specified addServer family.\n");
                break;
            case EINVAL:
                printf("Unknown protocol, or protocol family not available. OR Invalid flags in type.\n");
                break;
            case EMFILE:
                printf("Process file table overflow.\n");
                break;
            case ENFILE:
                printf("The system limit on the total number of open files has been reached.\n");
                break;
            case ENOBUFS:
                printf("Insufficient memory is available. The socket cannot be created until sufficient resources are freed.\n");
                break;
            case ENOMEM:
                printf("Insufficient memory is available. The socket cannot be created until sufficient resources are freed.\n");
                break;
            case EPROTONOSUPPORT:
                printf("The protocol type or the specified protocol is not supported within this domain.\n");
                break;
            default:
                printf("unspecified error %i ... \n", tErr);
                break;
        }
        * isRunning = false;
        return;
    }
    
    addServer.sin_family = AF_INET;
    addServer.sin_addr.s_addr = INADDR_ANY;
    addServer.sin_port = htons(port);
    
    if (bind(* retListenSocket, (struct sockaddr * ) &addServer, sizeof(struct sockaddr_in)) == 0) {
        printf("Socket bind successfull\n");
    } else {
        printf("Socket bind failed\n");
        * isRunning = false;
        return;
    }
}

// http://linux.die.net/man/2/listen / http://pubs.opengroup.org/onlinepubs/9699919799/
void listenFor(int * listenSocket, bool * isRunning) {
    int t = listen(* listenSocket, 10);
    int tErr = errno;
    if (t < 0) {
        printf("Error while listening\n- ");
        //perror("server: listen");
        switch (tErr) {
            case EADDRINUSE:
                printf("Another socket is already listening on the same port.\n");
                break;
            case EBADF:
                printf("The argument sockfd is not a valid descriptor.\n");
                break;
            case ENOTSOCK:
                printf("The argument sockfd is not a socket\n");
                break;
            case EOPNOTSUPP:
                printf("The socket is not of a type that supports the listen() operation\n");
                break;
            default:
                printf("Undefined Error%i\n", tErr);
                break;
        }
        * isRunning = false;
    }
}

// http://linux.die.net/man/2/accept / http://pubs.opengroup.org/onlinepubs/9699919799/
void acceptFor(int * listenSocket, socklen_t * addrlen, bool * isRunning) {
    struct sockaddr_in addClient;
    memset(&addClient, 0, sizeof(addClient));
    int NewSocket = accept(* listenSocket, (struct sockaddr *) &addClient, addrlen);
    int tErr = errno;
    
    //write(NewSocket, "Hoi\n", 4);
    if (tErr != 0) {
        printf("Error while accepting\n- ");
        switch (tErr) {
            case EAGAIN:
                printf("The socket is marked nonblocking and no connections are present to be accepted. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.\n");
                break;
            case EWOULDBLOCK:
                printf("The socket is marked nonblocking and no connections are present to be accepted. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.\n");
                break;
            case EBADF:
                printf("The descriptor is invalid\n");
                break;
            case ECONNABORTED:
                printf("A connection has been aborted.\n");
                break;
            case EFAULT:
                printf("The addr argument is not in a writable part of the user addServer space.\n");
                break;
            case EINTR:
                printf("The system call was interrupted by a signal that was caught before a valid connection arrived; see signal(7).\n");
                break;
            case EINVAL:
                printf("Socket is not listening for connections, or addrlen is invalid (e.g., is negative). or (accept4()) invalid value in flags\n");
                break;
            case EMFILE:
                printf("The per-process limit of open file descriptors has been reached.\n");
                break;
            case ENFILE:
                printf("The system limit on the total number of open files has been reached.\n");
                break;
            case ENOBUFS:
                printf("Not enough free memory. This often means that the memory allocation is limited by the socket buffer limits, not by the system memory.\n");
                break;
            case ENOMEM:
                printf("Not enough free memory. This often means that the memory allocation is limited by the socket buffer limits, not by the system memory.\n");
                break;
            case ENOTSOCK:
                printf("The descriptor references a file, not a socket.\n");
                break;
            case EOPNOTSUPP:
                printf("The referenced socket is not of type SOCK_STREAM.\n");
                break;
            case EPROTO:
                printf("Protocol error\n");
                break;
            default:
                printf("Undefined Error %i\n", tErr);
                break;
        }
        * isRunning = false;
    } else if (NewSocket != -1) {
        handleConnection(&NewSocket, &addClient);
    }
}

void handleConnection(int * inSocket, struct sockaddr_in * addClient) {
    if (* inSocket > 0){
        int bufferSize = 1024;
        char * buffer = malloc(bufferSize);
        memset(buffer, '\0', bufferSize);
        
        char response[] = "HTTP/1.1 200 OK\r\n"
                    "Content-Type: text/html\r\n\r\n"
                    "<html><head><title>test</title>"
                    "<html><body><H1>Hello world</H1></body></html>";
        printf("The Client is connected from %s ...\n", inet_ntoa((* addClient).sin_addr));
        //[todo] handle full buffer
        int received = recv(* inSocket, buffer, bufferSize, 0);
        printf("%s\nbuffer size: %i\n", buffer, bufferSize);
        send(* inSocket, response, strlen(response), 0);
        printf("=> response send\n");
        CLOSE_SOCKET_FUNCTION(* inSocket);
    }
}

我在客户端做什么

  1. 需要在 Windows 上初始化 WSA,在 Linux 上不需要。
  2. 在客户端为客户端创建一个套接字。
  3. 为服务器创建 struct sockaddr_in。

[尝试1]

  1. 为客户端创建 struct sockaddr_in。
  2. 将套接字绑定到客户端的结构。
  3. 使用客户端 Socket 连接到服务器结构。
  4. 发送消息。

[尝试2]

  1. 使用 sendto 因为我只想发送一条消息。

两者都不起作用,我认为我的问题是 struct sockaddr_in,但我不知道为什么。 我在这里做错了什么?

查看 Edit 3 以获得解决方案。

#ifdef __WIN32__
# include <winsock2.h> // used for sockets with windows, needs startup / shutdown
# include <ws2tcpip.h> // for MinGW / socklen_t / InetPtonA
# define INIT_SOCKET_SYSTEM WSADATA wsaData;if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {printf ("Error initialising WSA.\n");exit(6);}
# define CLEAR_SOCKET_SYSTEM WSACleanup();
# include <windows.h> // for Sleep
# define SLEEP Sleep(10); // sleeping 10ms
#else
# include <sys/socket.h>
# define INIT_SOCKET_SYSTEM printf("Linux dont need a special init for sockets, so all fine.\n");
# define CLEAR_SOCKET_SYSTEM printf("Linux dont need a special clear for sockets, so all fine.\n");
# include <time.h>
# define SLEEP sleep(1); // sleeping a second :-/
#endif
#include <stdio.h>
#include <sys/stat.h>
#include <stdbool.h>

// Step 1, create lokal Access point
void createSocket(int * mySocket);
// Step 2, create the target address
struct sockaddr_in getTargetAddress(char * ip, int port);

int * cleanSocket;
void cleanUp() {
    close(* cleanSocket);
    CLEAR_SOCKET_SYSTEM
}

int main() {
    int mySocket;
    // Step 1 create you Socket
    createSocket(&mySocket);
    // Step 2 get target
    struct sockaddr_in serverAddress = getTargetAddress("127.0.0.1", 15000);
    //struct sockaddr_in myAddress = getTargetAddress("127.0.0.1", 15000);
    // Step 3 bind & connect or sendto 
    //bind(mySocket, (const struct sockaddr *) &myAddress, sizeof(myAddress));
    //connect(mySocket, (const struct sockaddr * )&serverAddress, sizeof(serverAddress));
    char * question = "Whats up?\n";
    printf("sending %s\n", question);
    //send(mySocket, question, strlen(question), 0); // try to use protocol?
    sendto(mySocket, question, strlen(question), 0, (const struct sockaddr *) &serverAddress, sizeof(serverAddress));
    printf("sended!\n");
    
    close(mySocket);
    return 0;
}

void createSocket(int * mySocket) {
    INIT_SOCKET_SYSTEM
    if ((* mySocket = socket(AF_INET, SOCK_STREAM, 0)) > 0) {
        printf("Socket creation successful\n");
    } else {
        printf("Socket creation failed\n");
    }
}

struct sockaddr_in getTargetAddress(char * ip, int port) {
    struct sockaddr_in ret;
    ret.sin_family = AF_INET;
    ret.sin_addr.s_addr = inet_addr(ip);
    ret.sin_port = htons(15000);
    return ret;
}

编辑 1

评论包括: 我没有任何编译器错误,只是一个警告,因为 int received 未使用。 我对此发表评论是因为我尝试了很多并想在将其发布到此处之前对其进行清理,但认为它可能足够重要以将其保留为评论。 也许它包含在另一个包含中?我会检查的。

我现在在windows上测试和写,但最后它也需要在linux上运行。我在连接到服务器并执行 GET 请求的同一台机器上的 Windows 上使用 Autoit 中的一个小工具测试上面的服务器。服务器得到了 GET,在他的控制台中打印出来,然后回复 Autoit 客户端得到并打印出来的回复,所以它工作了一次。 如果没有关闭操作,我每次都可以做到。

编辑 2 - 得到服务器的答案,客户端仍然没有运行

服务器现在运行良好,得到了答案: http://cs.baylor.edu/~donahoo/practical/CSockets/WindowsSockets.pdf

从 UNIX 套接字迁移到 Windows 套接字相当简单。 Windows 程序需要一组不同的包含文件,需要初始化和释放 WinSock 资源,使用 closesocket( ) 而不是 close( ),并使用不同的错误报告工具。但是,应用程序的核心与 UNIX 相同。

编辑 3 - 客户端工作,但有一个小问题

需要缩短链接,因为我不允许直接发布那么多链接。

我在尝试 1 中的错误是将客户端结构绑定到与服务器相同的 IP。 "127.0.0.1"(clientaddress) => "Pseudo" 及其工作。

mySocket = socket(AF_INET, SOCK_STREAM, 0)
struct sockaddr_in serverAddress = getTargetAddress("127.0.0.1", 15000);
struct sockaddr_in myAddress = getTargetAddress("Pseudo", 15000);
bind(mySocket, (const struct sockaddr *) &myAddress, sizeof(myAddress));
connect(mySocket, (const struct sockaddr * )&serverAddress, sizeof(serverAddress));
send(mySocket, question, strlen(question), 0);

但我不需要自己在这里绑定,如果没有绑定,connect 会绑定,它会处理它的未使用地址。 pubs.opengroup [.] org/onlinepubs/9699919799/functions/connect.html

如果套接字尚未绑定到本地地址,connect() 应将其绑定到一个地址,除非套接字的地址族是 AF_UNIX,否则该地址是未使用的本地地址。

mySocket = socket(AF_INET, SOCK_STREAM, 0)
struct sockaddr_in serverAddress = getTargetAddress("127.0.0.1", 15000);
connect(mySocket, (const struct sockaddr * )&serverAddress, sizeof(serverAddress));
send(mySocket, question, strlen(question), 0);

当然这是可行的,但在我看来是不正确的。

mySocket = socket(AF_INET, SOCK_STREAM, 0)
struct sockaddr_in serverAddress = getTargetAddress("127.0.0.1", 15000);
connect(mySocket, (const struct sockaddr * )&serverAddress, sizeof(serverAddress));
sendto(mySocket, question, strlen(question), 0, (const struct sockaddr *) &serverAddress, sizeof(serverAddress));

这在我看来也应该可以工作,我不应该在这个结构中连接,因为它应该内置在 sendto 中。

pubs.opengroup [.] org/onlinepubs/9699919799/functions/connect.html

connect() 函数应尝试在连接模式套接字上建立连接[...]

pubs.opengroup [.] org/onlinepubs/9699919799/functions/sendto.html

如果套接字是连接模式,则应忽略 dest_addr。

由于上面的文字,我认为这也应该有效,但它没有。也许有人可以说为什么? (或者也许它应该在没有 myAddress 和绑定的情况下工作)

mySocket = socket(AF_INET, SOCK_STREAM, 0)
struct sockaddr_in serverAddress = getTargetAddress("127.0.0.1", 15000);
struct sockaddr_in myAddress = getTargetAddress("Pseudo", 15000);
bind(mySocket, (const struct sockaddr *) &myAddress, sizeof(myAddress));
sendto(mySocket, question, strlen(question), 0, (const struct sockaddr *) &serverAddress, sizeof(serverAddress));

顺便说一句,send 和 sendto 的返回值并不清楚。

成功完成对 send() 的调用并不能保证消息的传递。返回值 -1 表示仅本地检测到的错误。

成功完成对 sendto() 的调用并不能保证消息的传递。返回值 -1 表示仅本地检测到的错误。

我觉得返回值没用,还是不行?如果它的 -1 它可以交付,如果 1 它可能不是。 也许确定另一个协议?

小问题

  1. 为什么我还需要 connect 才能发送到?
  2. 我可以使用其他协议从 send/sendto 获得明确的返回值吗?

如果我找到答案,将在此处搜索并编辑它,如果有人可以回答这个问题,我仍然会注意。 非常感谢大家,我的主要问题都解决了。


感谢阅读!

【问题讨论】:

  • 我不知道答案,但是使用 Unix 实用程序 Netcat(或 Windows 等效程序)分别调试服务器和客户端比运行两个您不确定的程序更容易。要调试客户端,请运行nc -l [portnumber] 来托管服务器,并尝试与客户端连接。要调试服务器,请运行 nc [ipaddress] [portnumber] 以打开与服务器的连接。
  • 在您了解它们为什么被包含之前,您不应该将它们注释掉。 close(2)unistd.h 中。编译器应该告诉你的。顺便说一句:mallocstdlib.h 中,您也对此发表了评论。这里web.archive.org/web*/cs.baylor.edu/~donahoo/practical/CSockets/WindowsSockets.pdf 有一些区别。还有binarytides.com/socket-programming-c-linux-tutorialbinarytides.com/winsock-socket-programming-tutorial 了解更多详情。首先构建 WinWin 和 UnixUnix,然后是串扰,然后将两者合并。
  • @sudo 谢谢我会试试的。
  • @deamentiaemundi 我想我错过了一些信息,将其添加到帖子中,因为它在这里太长了。为什么是archive.org 链接?我可以直接查看 pdf 还是需要旧版本?其他的我现在要读了,谢谢。
  • @Kabanson 指向archive.org 的链接提出了一个变化,即它的活动时间将比指向大学个人网页的链接更长,当海报达到他们可以消失的年龄时,它可能会或可能不会消失领取他们的退休金。这也是stackoverflow等人的规则。选择链接时必须考虑到使用寿命。

标签: c linux sockets struct


【解决方案1】:

它不会起作用,因为你从不调用 connect()。您应该检查 connect() 、 send () 等调用的返回值。
解决方案:您应该在创建套接字后调用 connect。

connect(mySocket, (SOCKADDR *)&serverAddress, sizeof(sockaddr_in));  

要在 TCP 中使用 send 或 sendTo,套接字已连接,否则您将收到错误消息。

send(mySocket, question, strlen(question), 0);

【讨论】:

  • 已经在 [try 1] 中完成,但您的帖子帮助我在编辑 4 中找到了解决方案。但我认为我不需要连接 sendTo,原因在上面解释。谢谢!
  • 我用这两种方法测试了它,当它没有连接时,sendTo 也不起作用。我知道这是没有意义的,但是将地址传递给已经连接的套接字也是没有意义的。它可能是为了某种 api 兼容性,但我的想法是,如果发送方法获取地址作为参数,它应该与像 UDP 这样的无连接协议一起使用。 TCP 套接字已经连接,再次传递相同的地址是不必要的,传递另一个地址可能不起作用,如果你不断开连接并重新连接到新地址,这将是一个完全的混乱。
猜你喜欢
  • 2017-05-26
  • 2014-03-10
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-07
  • 2012-06-24
  • 1970-01-01
相关资源
最近更新 更多