<Linux开发> linux应用开发-之-socket通信开发例程

20 篇文章 3 订阅
4 篇文章 1 订阅

一、简介
对于socket通信的相关介绍,作者不过多介绍了,网上的介绍有很多。

二、环境搭建
本次测试socket通信的应用例程是运行在ubuntu pc上的;当然也是可以运行在linux开发板 或相关linux设备上的。
开发环境无特别要求,如果是Linux 板子上 要连接非局域网,则需要底层硬件网络驱动等的支持。

三、例程代码
本次代码会使用单独的一个c文件用来编写服务器进程代码,用以接收数据并在终端打印;
使用单独的一个c文件用来编写客户端进程代码,用以发送数据。

1、服务器代码如下:

/***************************************************************
Copyright © OneFu Co., Ltd. 1998-2022. All rights reserved.
文件名 : server.c
作者 : waterfxw
版本 : V1.0
描述 : socket server 示例代码
其他 : 无
日志 : 初版 V1.0 2023/03/15 waterfxw创建
***************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <pthread.h>

#define SERVER_PORT 9876 //端口号不能发生冲突,不常用的端口号通常大于 5000


void *Server_Thread(void* arg);
void  *Client_Thread( void* arg);

int Num=1;
int flasg=0;
typedef struct
{
    int sockfd;
    int connfd;
    int ID;
    int flag;
    /* data */
}Client_TYPE;


Client_TYPE ClientT[50];        //用于存放所有的连接客户端
pthread_t ClientThread[50];     //线程对象



//客户端连接后创建的线程
void  *Client_Thread( void* arg)
{
    printf("Client_Thread有客户端接入...\n");
    int ret;
    Client_TYPE  *ClientTr= (Client_TYPE *)arg;
    char recvbuf[512];

    /* 接收客户端发送过来的数据 */
    for ( ; ; ) {
        // 接收缓冲区清零
        memset(recvbuf, 0x0, sizeof(recvbuf));
        
        // 读数据
        ret = recv(ClientTr->connfd, recvbuf, sizeof(recvbuf), 0);
        if(0 >= ret) {
            perror("recv error");
            close(ClientTr->connfd);
            break;
        }

        // 将读取到的数据以字符串形式打印出来
        printf("from client[%d]: %s\n", ClientTr->ID ,recvbuf);

        // 如果读取到"exit"则关闭套接字退出程序
        if (0 == strncmp("exit", recvbuf, 4)) {
            printf("server client[%d] exit...\n",ClientTr->ID);
            close(ClientTr->connfd);
            break;
        }
    }
    Num--;
    pthread_exit(NULL);

}

//暂无使用
void *Server_Thread(void* arg)
{
    struct sockaddr_in client_addr = {0};
    int addrlen = sizeof(client_addr);
    int connfd;
    char ip_str[20] = {0};
    Client_TYPE  *SendC;
    int i;

    int sockfd = (int *)arg;;

    /* 阻塞等待客户端连接 */
    connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
    if (0 > connfd) {
        perror("accept error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("有客户端接入...\n");
    //转换获取IP
    inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
    printf("客户端主机的 IP 地址: %s\n", ip_str);
    printf("客户端进程的端口号: %d\n", client_addr.sin_port);

    for(i=0;i<50;i++)
    {
        if(ClientT[i].flag != 1) //检查空余位置
        {
            ClientT[i].sockfd = sockfd;
            ClientT[i].connfd = connfd;
            ClientT[i].ID = i;
            ClientT[i].flag = 1;    //标记该位置已连接客户机
            SendC  = &ClientT[i];
            Num ++;
            flasg = 1;
            break;
        }   
        
    }

    //创建客户端对应的处理线程
    pthread_create(&ClientThread[i],NULL,Client_Thread,(void *)(SendC));
    printf("connet:%d   Num:%d\n",i,Num-1);

}


int main(void) {

    struct sockaddr_in server_addr = {0};
    int sockfd;
    int ret;
    int i;

     struct sockaddr_in client_addr = {0};
    int addrlen = sizeof(client_addr);
    int connfd;
    char ip_str[20] = {0};
    Client_TYPE  *SendC;

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }


    //设置端口复用
    int opt=1;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));

    /* 将套接字与指定端口号进行绑定 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVER_PORT);
    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("bind error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    
    /* 使服务器进入监听状态 */
    ret = listen(sockfd, 50);
    if (0 > ret) {
        perror("listen error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }


    while(Num>1 || flasg !=1 )
    {

        /* 阻塞等待客户端连接 */
        connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
        if (0 > connfd) {
            perror("accept error");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
        printf("有客户端接入...\n");
        //转换获取IP
        inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
        printf("客户端主机的 IP 地址: %s\n", ip_str);
        printf("客户端进程的端口号: %d\n", client_addr.sin_port);

        for(i=0;i<50;i++)
        {
            if(ClientT[i].flag != 1) //检查空余位置
            {
                ClientT[i].sockfd = sockfd;
                ClientT[i].connfd = connfd;
                ClientT[i].ID = i;
                ClientT[i].flag = 1;    //标记该位置已连接客户机
                SendC  = &ClientT[i];
                Num ++;
                flasg = 1;
                break;
            }   
            
        }

        //创建客户端对应的处理线程
        pthread_create(&ClientThread[i],NULL,Client_Thread,(void *)(SendC));
        printf("connet:%d   Num:%d\n",i,Num-1);
            
        // sleep(1); //一秒钟发送一次
        
    }

    printf("所有客户端都退出连接!!!\n");

    for(i=0;i<Num-1;i++)
    {
        if(ClientT[i].flag != 1)
        {
            ClientT[i].sockfd = 0;
            ClientT[i].connfd = 0;
            ClientT[i].ID = 0;
            ClientT[i].flag = 0;

            pthread_join(ClientThread[i],NULL); //等待线程退出
            printf("client[%d] exit...\n",i);
        }

    }
        
    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
}

2、客户端代码如下:

/***************************************************************
Copyright © OneFu Co., Ltd. 1998-2022. All rights reserved.
文件名 : client.c
作者 : waterfxw
版本 : V1.0
描述 : socket client 示例代码
其他 : 无
日志 : 初版 V1.0 2023/03/15 waterfxw创建
***************************************************************/

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

#define SERVER_PORT 9876 //服务器的端口号
#define SERVER_IP "192.168.140.191" //服务器的 IP 地址


int main(void) {
    struct sockaddr_in server_addr = {0};
    char buf[512];
    int sockfd;
    int ret;
    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd) {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 调用 connect 连接远端服务器 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT); //端口号
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP 地址
    ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret) {
        perror("connect error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("服务器连接成功...\n\n");

    /* 向服务器发送数据 */
    for ( ; ; ) {
        // 清理缓冲区
        memset(buf, 0x0, sizeof(buf));

        // 接收用户输入的字符串数据
        printf("Please enter a string: ");
        fgets(buf, sizeof(buf), stdin);

        // 将用户输入的数据发送给服务器
        ret = send(sockfd, buf, strlen(buf), 0);
        if(0 > ret){
            perror("send error");
            break;
        }

        //输入了"exit",退出循环
        if(0 == strncmp(buf, "exit", 4))
        break;
    }
    close(sockfd);
    exit(EXIT_SUCCESS);
}

四、编译测试
1、server编译命令:

 gcc server.c -o server -lpthread

由于线程数据外部链接库 所以需要指明 -lpthread;

2、client编译

gcc client.c -o client

3、运行效果如下:

服务端接收客户端连接输出的log如下:
在这里插入图片描述

客户端启动连接服务端:
在这里插入图片描述

多个客户端连接服务端并发送数据效果图:
在这里插入图片描述

五、总结
上述测试例程只是socket一个简单测试例程,实际的项目开发 逻辑过程 更加复杂。但是我们掌握了基本的socket通信流程,相信读者能写出优秀的代码。加油…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waterfxw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值