Socket编程,简单实现一台机器向另一台机器发消息

网上一搜,就可以看到一大堆的Socket编程介绍,一般是写个简单的服务端和客户端进行连接并做简单的交互。我这里又要多一篇这样的文章了,并不是故意的,而是有意的:D.当然不是直接复制粘贴,而是学习了解后亲自实践下来体会体会。

之前做了个五子棋,要可以两个人对战的。如果是单机的话,两个人对战或人机对战,都是在一个客户端上进行的,你只需要考虑在一个客户端上的情况就行了。如果你写了一个客户端,同时安装在两台机器上,并要可以对战交流,那要怎么办?你这边的操作,对面是看不到的,就像两个隔很远的人,没有通讯工具怎么交流。因此这两个客户端也需要一个”通讯工具”,那就是服务端。有了通讯工具,如果两人的语言不一样,也是无法交流的,因此两个客户端之间要用一种双方都懂的规范才能交流,这种规范叫协议,如常见的TCP协议。当然,这两个客户端要能联网,否则怎么连上服务端。服务端负责监听客户端,接收客户端发来的消息,然后给客户端发送消息。举个栗子:例如我这边下子了,然后我给服务端发送一条消息说-我下子了,棋子的颜色是黑色,位置是(x, y),然后服务端接收到消息后就给另一个客户端发条消息说,他下子了,然后另一个客户端就根据提供的消息,在自己棋盘上相应的位置下个黑子。反过来也一样,对方下子,我也会收到消息。这样,两个客户端间就像在同一个棋盘下子一样。服务端并不只是收发消息,像那些网游服务端,还要处理很多逻辑。当然,我做的五子棋并没有服务端,也没有用到Socket编程和什么TCP协议,我只是处理好前端逻辑,然后给别人接上对方提供的SDK,前端可以给那边发消息或接收消息。举的这个栗子不太恰当,不过应该可以帮助理解。

至于什么是Socket,什么是TCP,我这里就不讲了,别人写的很详细,我无法写得更详细,而且这个好像讲一下是讲不完的,我们先简单知道下就可以了。可以看看这篇文章socket编程之实现一个简单的TCP通信。如果你觉得这篇也不详细,那就再看看其它的。或者里边还有不懂的,你可以搜索一下,比如浅谈TCP协议的端口(port),再比如socket开发中INADDR_ANY”的含义是什么?。我觉得看完这3篇就差不多了,可以自己实现代码体验一下。

两台联网的机器如何通信?其实只要用Socket写个简单的服务端和一个简单的客户端分别运行就行了。当然,服务端和客户端的IP地址和端口得一样,不然它们是无法连接上的。

下面来看下代码:

wtlServer.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 端口,任意指定
#define PORT 9990 
// 有多块网卡就会有多个IP地址,可填 (INADDR_ANY) 或填你电脑这在使用的IP地址,格式为 ("xx.xx.xx.xx")
#define IP (INADDR_ANY)
// 用来储存接收消息的缓冲区的大小
#define BUF_SIZE 1024

// 消息处理函数
typedef int(*fn)(int);
// 接收消息的缓冲区
static unsigned char gMsg[BUF_SIZE];

int wtlServer_Init(int* ipSockfd);
void wtlServer_Run(int iSockfd, fn callback);
void wtlServer_Quit(int iSockfd);
static int OnRecvHandle(int iSock);

int main(int argc, char* argv[])
{
    // 服务端文件描述符
    int iSockfd;
    // 运行服务端
    wtlServer_Run(wtlServer_Init(&iSockfd), OnRecvHandle);
    // 服务端退出
    wtlServer_Quit(iSockfd);

    return 0;
}

// 服务端初始化函数,用于创建一个服务端的文件描述符,并会返回该描述符
int wtlServer_Init(int* ipSockfd){
    int status;
    // 服务端文件描述符
    int iSockfd;
    // 用于存储服务端的一些信息,包括IP地址和端口等
    struct sockaddr_in server;

    // 创建一个服务端文件描述符,后面操作服务端都是根据该文件描述符进行的
    // 第一个参数一般都是这个,第二个参数是套接字类型,SOCK_STREAM代表TCP协议,第三个一般为0
    iSockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 创建失败时的处理
    if (iSockfd < 0) {
        perror("socket");
        exit(0);
    }

    // 填写服务端的相关信息
    server.sin_family = AF_INET;
    // 端口。htons()函数在上面给的连接有介绍,还有个htonl(),注意使用
    server.sin_port   = htons(PORT);
    // IP地址。注意,如果服务端和客户端都跑在同一台机器,就用下面这句
    server.sin_addr.s_addr = htons(INADDR_ANY);
    // 如果跑在不同机器,就用下面这句。IP为服务端运行的机器的IP地址
    //server.sin_addr.s_addr = inet_addr(IP);

    // 将文件描述符和服务端信息进行绑定
    status = bind(iSockfd, (struct sockaddr*)&server, sizeof(server));
    // 绑定失败时的处理
    if (status < 0) {
        perror("bind");
        exit(0);
    }

    // 监听是否有客户端请求连接,如果有,将其放到等待队列
    status = listen(iSockfd, 5);
    // 监听失败时的处理
    if (status < 0) {
        perror("listen");
        exit(0);
    }

    *ipSockfd = iSockfd;

    return iSockfd;
}

// 运行服务端。第一个参数为服务端的文件描述符,第二个参数为收到客户端发来消息后的回调函数
void wtlServer_Run(int iSockfd, fn callback){
    // 该变量用来存放已连接上的客户端的相关信息,如IP地址和端口
    struct sockaddr_in client;
    // 已连接客户端的文件描述符
    int iSock;
    int status = 0;
    int len = sizeof(client);

    while (1) {
        // 客户端服务端进行3次握手后,就可以正确连接
        // 函数的第一个参数为服务端的文件描述符,第二个参数用来填写连接的客户端信息
        // 成功连接后,该函数返回客户端的文件描述符
        iSock = accept(iSockfd, (struct sockaddr*)&client, &len);
        // 连接失败时的处理
        if (iSock < 0) {
            fprintf(stdout, "Waiting for a client to conect...");
            continue;
        } else {
            // 连接成功,打印客户端的IP地址和端口号
            fprintf(stdout, "Conecting a client: IP-%s PORT:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        }

        if (callback != NULL) {
            // 收到客户端发来消息后的处理
            status = callback(iSock);
        }

        // 如果处理结果得到-1,说明客户端要退出了
        if (status == -1) {
            fprintf(stdout, "Client Disconected...\n");
            break;
        }
    }
}

// 服务端退出后,关闭服务端文件描述符
void wtlServer_Quit(int iSockfd){
    close(iSockfd);
}

// 收到客户端发来消息后的处理函数,参数为客户端的文件描述符
int OnRecvHandle(int iSock){
    ssize_t size;

    while (1) {
        // 从客户端文件描述符中读取这么多个字节到gMsg缓冲中
        size = read(iSock, gMsg, sizeof(gMsg) - 1);
        if (size > 0) {
            gMsg[size] = 0;
            // 读取到消息后,服务端只是简单的打印出来
            printf("%s\n", gMsg);

            // 如果客户端发来一个 bye ,表示要退出了
            if (strncmp(gMsg, "bye", 3) == 0) {
                return -1;
            }
        }
    }

    return 0;
}

wtlClient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 端口得和服务端设置的一致,不然服务端监听不到请求
#define PORT 9990
// IP地址,也得和服务端设置的一致,要么是 INADDR_ANY 要么是具体的某个IP地址
#define IP (INADDR_ANY) 
#define SIZE 1024

static unsigned char gMsg[SIZE];

int wtlClient_Init(int* ipSockfd);
void wtlClient_Run(int iSockfd);
void wtlClient_Quit(int iSockfd);

int main(int argc, char* argv[])
{
    // 客户端文件描述符
    int iSock;

    wtlClient_Init(&iSock);
    wtlClient_Run(iSock);

    wtlClient_Quit(iSock);

    return 0;
}

// 初始化客户端,和服务端的初始化差不多
int wtlClient_Init(int* ipSockfd){
    int iSock;
    int len;
    // 用来存放服务端相关信息,如IP地址和端口号
    struct sockaddr_in server;

    // 创建客户端文件描述符
    iSock = socket(AF_INET, SOCK_STREAM, 0);
    if (iSock < 0) {
        perror("socket");
        exit(0);
    }

    // 填写服务端相关信息,因为要连接到服务端
    server.sin_family = AF_INET;
    server.sin_port   = htons(PORT);
    server.sin_addr.s_addr = inet_addr(IP);

    len = sizeof(server);
    // 向服务端请求连接
    if (connect(iSock, (struct sockaddr*)&server, len) < 0) {
        perror("connect");
        exit(0);
    } else {
        // 连接成功
        fprintf(stdout, "Connected to a server!\n");
    }

    *ipSockfd = iSock;

    return iSock;
}

// 运行客户端,参数为客户端文件描述符
void wtlClient_Run(int iSockfd){
    int size = 0;

    while (1) {
        // 从终端输入文字
        printf("Client>> ");
        fflush(stdout);
        // 读取输入的文字到gMsg
        size = read(0, gMsg, SIZE - 1);
        gMsg[size] = 0;
        // 将gMsg中的信息写入到客户端文件描述符中
        write(iSockfd, gMsg, size);

        if (size > 0) {
            // 如果输入 bye ,退出客户端
            if (strncmp(gMsg, "bye", 3) == 0) {
                fprintf(stdout, "Disconnected to server...\n");
                break;
            }
        }
    }
}

// 客户端退出时关闭客户端文件描述符
void wtlClient_Quit(int iSockfd){
    close(iSockfd);
}

如果将IP地址填你电脑的IP地址,电脑编译运行wtlSercer.c,手机装个C4Droid,运行wtlClient.c,成功连接后,手机发送消息,电脑就可以收到消息了。这里写图片描述
这里写图片描述


这只是简单演示。还可继续改进。 : )

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值