带套接字的C服务器/客户端

时间:2016-07-16 23:28:57

标签: c linux sockets struct

[仅剩下2个小问题

我尝试在C中编写一个简单的服务器/客户端,通过套接字发送消息。 它必须在Linux和Windows下使用MinGW运行。我找到了很多适用于Linux的例子,但很多人都没有使用Windows。 如果你愿意帮助我真的很好。

对于服务器,我有一些我不理解的东西。

我在服务器端做了什么?

  1. 需要在Windows上初始化WSA,在Linux上没有任何内容。
  2. 在服务器端为服务器创建套接字。
  3. 为服务器创建struct sockaddr_in。
  4. 在ANY-IP上绑定套接字。
  5. 听插座。
  6. 接受连接,处理与newSocket的连接。
  7. 用6)关闭新的Socket重复。
  8. 如果我不关闭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。
    4. [尝试1]

      1. 为客户创建struct sockaddr_in。
      2. 将套接字绑定到客户端的结构。
      3. 将客户端Socket连接到server-struct。
      4. 发送消息。
      5. [尝试2]

        1. 使用sendto,因为我只想发送一条消息。
        2. 两个都不工作,我认为我的问题是结构sockaddr_in,但我不知道为什么。 我在这里做错了什么?

          查看编辑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上运行。我在同一台机器上的Windows上的一个小工具上测试上面的服务器,该机器连接到服务器并执行GET请求。服务器获得GET,将其打印在他的控制台中并发送回复,Autoit-client得到并打印,因此它工作了一次。 如果没有关闭操作,我每次都可以这样做。

          编辑2 - 获得服务器的答案,客户端仍未运行

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

            

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

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

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

          我在try 1中的错误是将客户端结构绑定到与服务器相同的IP。 &#34; 127.0.0.1&#34;(clientaddress)=&gt; &#34;伪&#34;和它的工作。

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

          但是我不需要自己绑定,如果没有完成连接,它会处理它未使用的地址。 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和bind的情况下工作)

          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()的调用并不能保证传递消息。返回值-1表示仅检测到本地检测到的错误。

               

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

          我认为回报价值是无用的,还是没有?如果它是-1,它可以被传递,如果它可能不是。 也许确定另一个协议?

          小问题

          1. 为什么我仍然需要连接sendto?
          2. 我可以使用其他协议从send / sendto获得明确的返回值吗?
          3. 如果我找到了答案,我们会在这里搜索并编辑它,如果有人可以回答这个问题,仍会留意。 我的主要问题已经消失,所以非常感谢所有人。

            感谢阅读!

1 个答案:

答案 0 :(得分:1)

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

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

要使用带有TCP的send或sendTo,套接字已连接或您将收到错误。

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