(Linux) 套接字socket基础

前言

本文将以纯C语言描述,编译器gcc。

C/C++没有标准的网络库,因为都需要用到各个平台的接口才行。

本文讲解Linux下最基础的socket编程,实现一个简单的回声服务器。

基本原理

具体流程如下:

此图描述的非常清晰,就是一步一步往下操作即可。

tcp.jpg (408×560) (subingwen.cn)

Code

废话不多说,直接上code

cv代码后直接可以编译运行

注意:

  • 请先启动server再启动client
  • 如出现异常,请确认端口是否可以用
  • 如果是云端操作,请注意ip

server

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    
    
    printf("***** server main start ******\n");

    // 1. 创建监听的套接字
    // ipv4 流式传输
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
    
    
        printf("socket = -1");
        return -1;
    }

    // 配置基本信息
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // 主机字节序转为网络字节序
    // 设定一个没有冲入的端口
    saddr.sin_port = htons(12345);
    // any就是0->0.0.0.0 此时不区分大小端
    // 自动定读网卡的实际ip地址
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 2. 绑定socket
    int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
    
    
        printf("bind = -1");
        return -1;
    }

    // 3. 设置监听
    // 最大128
    ret = listen(fd, 128);
    if (ret == -1) {
    
    
        printf("listen = -1");
        return -1;
    }

    printf("***** ready to accept ******\n");

    struct sockaddr_in caddr;
    int addrlen = sizeof(struct sockaddr_in);
    // 4. 阻塞并等待客户端的连接
    // 成功则获得客户端的fd
    int cfd = accept(fd, (struct sockaddr*)&caddr, &addrlen);
    if (cfd == -1) {
    
    
        printf("accept = -1");
        return -1;
    }

    // 连接成功,打印客户端ip和端口
    char ip[32] = {
    
    };
    printf("client ip = %s, port = %d\n",
           inet_ntop(AF_INET, &caddr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(caddr.sin_port));

    // 5. 通信
    while (1) {
    
    
        char buff[1024] = {
    
    };
        int len = recv(cfd, buff, sizeof(buff), 0);
        if (len > 0) {
    
    
            printf("client say : %s\n", buff);
            // 原版不动的回传
            send(cfd, buff, len, 0);
        } else if (len == 0) {
    
    
            printf("client close ... \n");
            break;
        } else {
    
    
            printf("recv = -1\n");
            break;
        }
    }

    // 6. 关闭文件描述符
    close(fd);
    close(cfd);

    printf("***** server main end ******\n");
    return 0;
}

client

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    
    
    printf("***** client main start ******\n");
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
    
    
        printf("socket = -1");
        return -1;
    }

    // 连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(12345);
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);

    // 2. 绑定socket
    int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
    
    
        printf("connect = -1");
        return -1;
    }

    int number = 0;
    // 3. 通信
    while (1) {
    
    
        // 发送
        char buff[1024] = {
    
    };
        sprintf(buff, "Hello i am client [%d]...\n", number++);
        send(fd, buff, strlen(buff) + 1, 0);

        // 接受
        memset(buff, 0, sizeof(buff));
        int len = recv(fd, buff, sizeof(buff), 0);
        if (len > 0) {
    
    
            printf("server say : %s\n", buff);

        } else if (len == 0) {
    
    
            printf("server close ... \n");
            break;
        } else {
    
    
            printf("read = -1\n");
            break;
        }

        // 延时2秒
        sleep(2);
    }

    // 4. 关闭文件描述符
    close(fd);

    printf("***** client main end ******\n");
    return 0;
}

核心函数

头文件

#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <unistd.h>

socket

用于创建socket对象

前两个参数决定了使用什么通信协议。

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
// ipv4 的流式 就是tcp
socket(PF_INET, SOCK_STREAM, 0);

// ipv4 的报文 就是udp
socket(PF_INET, SOCK_DGRAM, 0);

bind

服务端客户端通用

#include <sys/types.h>
#include <sys/socket.h>

// const 是入参
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen

服务端开启监听

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

accept

服务端等待客户端的接入

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
// 这里是回参
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

recv

接收数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

send

发送数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

connect

客户端去连接服务端

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
// const 是入参
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

close

关闭fd的通用函数

#include <unistd.h>

int close(int fd);

多线程改进

注意编译的时候要加入-lpthread

线程函数的接口是void* () (void*)

经过改进后可以并发接入多个客户端的连接。

#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/**
 * 将 struct sockaddr_in 和 fd 绑定到一起
*/
struct SockInfo {
    
    
    struct sockaddr_in addr;
    int fd;
};

struct SockInfo infos[512];

/**
 * pthread 固定函数类型
 * void* () (void*)
*/
void* working(void* arg) {
    
    
    // 指针的强转
    struct SockInfo* pinfo = (struct SockInfo*)arg;

    // 连接成功,打印客户端ip和端口
    char ip[32] = {
    
    };
    printf("client ip = %s, port = %d\n",
           inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(pinfo->addr.sin_port));

    // 5. 通信
    while (1) {
    
    
        char buff[1024] = {
    
    };
        int len = recv(pinfo->fd, buff, sizeof(buff), 0);
        if (len > 0) {
    
    
            printf("client say : %s\n", buff);
            send(pinfo->fd, buff, len, 0);
        } else if (len == 0) {
    
    
            printf("client close ... \n");
            break;
        } else {
    
    
            printf("recv = -1\n");
            break;
        }
    }

    // 6. 关闭文件描述符
    close(pinfo->fd);
    pinfo->fd = -1;

    printf("thread end success\n");
    return NULL;
}

int main() {
    
    
    printf("***** server main start ******\n");
    // socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    if (fd == -1) {
    
    
        printf("socket = -1");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(12345);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // bind
    int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1) {
    
    
        printf("bind = -1");
        return -1;
    }

    // listen
    ret = listen(fd, 128);
    if (ret == -1) {
    
    
        printf("listen = -1");
        return -1;
    }

    // 初始化所有封装的 struct
    printf("********ready to accept**********\n");
    int max = sizeof(infos) / sizeof(infos[0]);
    for (int i = 0; i < max; i += 1) {
    
    
        // memset 0
        bzero(&infos[i], sizeof(infos[i]));
        infos[i].fd = -1;
    }

    // accept
    // thread
    int addrlen = sizeof(struct sockaddr_in);
    while (1) {
    
    
        // 找出可用空间
        struct SockInfo* pinfo = NULL;
        for (int i = 0; i < max; i += 1) {
    
    
            if (infos[i].fd == -1) {
    
    
                pinfo = &infos[i];
                break;
            }
        }

        int cfd = accept(fd, (struct sockaddr*)&pinfo->addr, &addrlen);
        pinfo->fd = cfd;
        
        // 根据自己的需要处理失败的 accept
        if (cfd == -1) {
    
    
            printf("accept = -1");
            break;
        }

        // 创建子线程,并与主线程分离
        pthread_t tid;
        pthread_create(&tid, NULL, working, pinfo);
        pthread_detach(tid);
    }

    // 关闭fd
    close(fd);

    printf("***** server main end ******\n");
    return 0;
}


END

参考:套接字 - Socket | 爱编程的大丙 (subingwen.cn)

Qt多线程网络通信-套接字通信 socket_哔哩哔哩_bilibili

猜你喜欢

转载自blog.csdn.net/CUBE_lotus/article/details/131546218