1.SDL2_net TCP服务器端和客户端的通信

这几天打算把以前做的游戏尝试加入局域网联机,恰巧SDL提供了对应的库,即SDL2_net。

1.安装

我的系统是ubuntu,安装相对简单,下面一个命令即可:

sudo apt install libsdl2-net-dev

等待安装完成即可。

如果使用的是window,可以去官网下载对应的链接库

http://www.libsdl.org/projects/SDL_net/

下载dll或者源码。

2.链接头文件和库文件

无论使用的是cmake、makefile、还是visual studio,都是需要确定对应的头文件路径和库文件路径;我这里使用的是cmake,cmake是对makefile的封装,跨平台。本次项目文件路径大致如下:

client文件夹包为客户端,server文件夹为服务器端。

cmake中并没有SDL2相关库的Find*.cmake,所以需要自己添加。FindSDL2*.cmake在ubuntu下是可用的,未测试过windows。

server/CMakeLists.txt

#工程所需最小版本号
cmake_minimum_required(VERSION 3.10)

project(server)

#设置搜索路径
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../cmake")

#找到SDL2_net库
find_package(SDL2 REQUIRED)
find_package(SDL2_net REQUIRED)

#添加对应的头文件搜索目录
include_directories(${SDL2_NET_INCLUDE_DIR})
#生成可执行文件
aux_source_directory(. SRC_LIST)
add_executable(main ${SRC_LIST})
#链接对应的函数库
target_link_libraries(main 
        ${SDL2_NET_LIBRARY}
        ${SDL2_LIBRARY})
#设置生成路径在源路径下
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})

需要注意的是,SDL2_net是可以独立使用的,不过如果要是使用SDL_Delay()、SDL_Init()等函数,还是需要SDL.h和相关库文件的。

client文件夹的CMakeLists.txt和上述相似,这里不再赘述。

3.TCP

TCP是一个可靠的连接,相对于UDP来说较慢,但是更加安全。服务器和客户端需要先建立连接,然后收发数据,之后约定一个结束标志,断开连接。本示例只能一个服务器和一个客户端。

4.Server 服务器端

服务器端相对来说要比客户端复杂,服务器可以 指定监听的端口号,比如:./main 2000,表示监听端口为2000的服务器;也可以不指定,不指定则在当期代码中使用默认端口2000。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "SDL_net.h"

#define SIZE 1024
int main(int argc, char** argv)
{
        IPaddress ip; 
        TCPsocket server_socket, client_socket;
        bool quit = false;
        char buffer[SIZE];
        //默认端口
        Uint16 port = 2000;

        SDL_Init(0);

        if (argc < 2)
        {
                printf("the server wiil use the default port:%u\n", port);
        }
        else
                port = (Uint16)atoi(argv[1]);

        printf("the server will use port:%u\n", port);
        SDLNet_Init();
        //创建一个服务器类型的IPaddress
        if (SDLNet_ResolveHost(&ip, NULL, port) != 0)
        {
                printf("SDLNet_ResolveHost: %s\n", SDLNet_GetError());
                return 1;
        }

这一部分主要是初始化网络库和填充IPaddress结构体 。


①.SDLNet_Init()           SDLNet_Quit()

初始化网络库,使用该库函数前都得首先调用该函数。、

退出网络库。

②.IPsocket

typedef struct {
    Uint32 host;            /* 32-bit IPv4 host address */
    Uint16 port;            /* 16-bit protocol port */
} IPaddress;

ip地址和对应的端口号,这个结构体主要是使用下面的函数进行属性填充,并不需要预先设置。

int SDLNet_ResolveHost(IPaddress *address, const char *host, Uint16 port)
  • address 将要填充的IPaddress结构体。
  • host 如果是服务器端,则host为NULL;如果为客户端,则填写的是域名或者IP地址。比如127.0.0.1或者onatkins.org。
  • port 如果是服务器端,则表示为要监听的端口号;如果是客户端,则表示对应的服务器所监听的端口号。

这个函数的主要作用就是填充IPaddress结构体,如果成功则返回0,否则返回-1,如果失败,则很可能是host错误。

③.TCPsocket

typedef struct _TCPsocket *TCPsocket;

直译就是TCP套接字,其内部属性不公开(即该结构体的定义不在头文件中),主要用作TCP连接,为指针。


        //打开一个TCP连接
        if ((server_socket = SDLNet_TCP_Open(&ip)) == nullptr)
        {
                printf("error:%s\n", SDLNet_GetError());
                return 2;
        }
        printf("create server success\n");

这一部分主要是通过对应的IPaddress来创建一个TCP套接字,上面也讲过,TCPsocket为指针类型,所以判断其是否生成使用的是server_socket == nullptr.


④.SDLNet_TCP_Open(IPaddress* ip)

TCPsocket SDLNet_TCP_Open(IPaddress *ip)

根据已经处理好的IPaddress变量来创建一个TCP套接字,官网wiki上说如果ip->host == INADDR_ANY,则仅仅使用port。这个INADDR_ANY是一个宏,其定义如下:

#ifndef INADDR_ANY
#define INADDR_ANY      0x00000000
#endif

具体相关目前我还不太懂,初学,初学。


        while (!quit)
        {
                //存在TCP连接
                if ((client_socket = SDLNet_TCP_Accept(server_socket)) != nullptr)
                {
                        IPaddress* remoteIP = nullptr;

                        //获取远程ip
                        if ((remoteIP = SDLNet_TCP_GetPeerAddress(client_socket)) != nullptr)
                        {
                                printf("remote ip is %x, port %u\n"
                                        , SDLNet_Read32(&remoteIP->host)
                                        , SDLNet_Read16(&remoteIP->port));
                        }
                        bool running = true;

                        //监听
                        while (running)
                        {
                                if (SDLNet_TCP_Recv(client_socket, buffer, SIZE) > 0)
                                {
                                        printf("Client say: %s\n", buffer);

                                        if (strcmp(buffer, "exit") == 0)
                                        {
                                                running = false;
                                                printf("Terminal\n");
                                        }
                                        else if (strcmp(buffer, "quit") == 0)
                                        {
                                                quit = true;
                                                running = false;
                                                printf("Quit\n");
                                        }
                                }
                        }
                        SDLNet_TCP_Close(client_socket);
                }
        }

这一部分主要负责监听是否存在客户端,如果是的话,则建立连接,然后等待对方发消息。


⑤.SDLNet_TCP_Accept()

TCPsocket SDLNet_TCP_Accept(TCPsocket server)

接收一个server对应的即将到来的客户端连接,非阻塞,如果没有返回NULL。不要用这个函数用于已经连接的服务器(试了试,当存在多个客户端时,仅仅第一个客户端有效,第二个客户端发送的数据,服务器端接收不到,当然,也和上面的代码相关)。

⑥.SDLNet_TCP_Recv()

int SDLNet_TCP_Recv(TCPsocket sock, void *data, int maxlen)
  • sock 合法的、已连接的客户端套接字。
  • data 要存放的数据,需要注意的是为void*。
  • maxlen 接收的数据的最大长度。
  • return 如果返回值小于等于0,则发生错误,可能是客户端已经断开连接。

 5.客户端

客户端做的事非常类似与服务器,这里负责发送数据。

#include <string.h>
#include <stdlib.h>
#include "SDL_net.h"

#define SIZE 1024
int main(int argc, char** argv)
{
        IPaddress ip;
        TCPsocket server_socket;
        bool quit = false;
        int len = 0;
        char buffer[1024];

        if (argc < 3)
        {
                printf("Usage: %s host port\n", argv[0]);
                return 1;
        }
        SDLNet_Init();
        //客户端
        if (SDLNet_ResolveHost(&ip, argv[1], atoi(argv[2])) != 0)
        {
                printf("error:%s\n", SDLNet_GetError());
                return 1;
        }
        //打开ip
        if ((server_socket = SDLNet_TCP_Open(&ip)) == nullptr)
        {
                printf("error:%s\n", SDLNet_GetError());
                return 1;
        }

        while (!quit)
        {
                printf("Write something:\n");
                scanf("%s", buffer);

                len = strlen(buffer) + 1;

                if (SDLNet_TCP_Send(server_socket, (void*)buffer, len) < len)
                {
                        printf("error:%s\n", SDLNet_GetError());
                }
                if (strcmp(buffer, "exit") == 0 || strcmp(buffer, "quit") == 0)
                        quit = true;
        }

        SDLNet_TCP_Close(server_socket);
        SDLNet_Quit();
        return 0;
}

发送消息给服务器。


⑦.SDLNet_TCP_Send()

int SDLNet_TCP_Send(TCPsocket sock, const void *data, int len)
  • sock 合法的、已连接的TCP套接字。
  • data 要发送的数据。
  • len 发送的数据的长度。
  • return 发送的数据长度,小于等于len

 6.运行

服务器和客户端编写完成,编译好运行即可。

服务器运行

客户端运行,并尝试发送数据1234给服务器

服务器接收,并输出:

客户端可主动申请发送exit释放此次连接或发送quit退出,这里不再演示。

本节代码如下:

https://github.com/sky94520/SDL2_net

局域网:

服务器端:ubuntu 客户端:ubuntu (由于SDL的跨平台性,其他的系统应该也可以连接成功)

测试成功

云服务器+ubuntu客户端

测试失败


该程序测试在局域网下成功,而在云服务器上失败,估计是云服务器还另外需要其他的程序支撑,,,暂时无法解决。。。

猜你喜欢

转载自blog.csdn.net/bull521/article/details/84334183