【嵌入式Linux】<总览> 网络编程(更新中)

news2024/11/27 12:38:29

文章目录

前言

一、网络知识概述

1. 网路结构分层

2. socket

3. IP地址

4. 端口号

5. 字节序

二、网络编程常用API

1. socket函数

2. bind函数

3. listen函数

4. accept函数

5. connect函数

6. read和recv函数

7. write和send函数

三、TCP编程

1. TCP介绍

2. TCP通信流程

3. TCP服务端和客户端程序

4. TCP并发服务器(多进程)

5. TCP并发服务器(多线程)


前言

记录学习嵌入式Linux网络编程的知识重点与难点,若涉及版权问题请联系本人删除!


一、网络知识概述

1. 网路结构分层

①主要存在两种分层模型:OSI七层模型(理论)和TCP/IP四层模型(实际)。

②分层思想:采用分治法,将复杂问题划分为各个层级上的子问题。每一层向上提供服务,同时使用下层提供的服务。

③网络封包和拆包:如下图所示,从主机A传输数据到主机B的流程一般为:主机A的应用层封装好了传输数据后,传输层会给上层数据包添加TCP头部,网络层会给上层数据包添加IP头部,网络接口层会给上层数据包添加对应的头部和尾部(一般为CRC校验);路由器将接收到的帧去掉头部和尾部传输给上一层,传输层将解析IP地址并查表转发,然后继续封装为帧,传输到指定的主机B;主机B逐层拆包,最终在应用层获取接收到的真正数据。

2. socket

①概念:socket是一个特殊的文件描述符,用于网络通信。

②socket分类:

  • 流式套接字(SOCK_STREAM):对应TCP,面向连接、可靠。
  • 数据报套接字(SOCK_DGRAM):对应UDP,无连接、不可靠。
  • 原始套接字(SOCK_RAW):对应多个协议,可以直接访问IP、ICMP,跨过了传输层。

3. IP地址

①概念:IP地址是网络中主机地址的标识。通过IP可以找到对应的主机。

②IP地址分类:

  • IPv4:一个32位(4字节)的整数,每个字节用.来分隔。每个字节的数据范围为0~255。例如: 192.168.5.11  不够用,引入局域网可以解决不够用的问题
  • IPv6:一个128(16字节)的整数,每两个字节为一部分,总共有8部分,每部分用:分隔。例如:2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b   “地球上每一粒沙子都能分配一个”

③特殊的IP地址:

  • 局域网IP:192.XXX.XXX.XXX    10.XXX.XXX.XXX
  • 广播IP:XXX.XXX.XXX.255    255.255.255.255(全网广播)
  • 组播IP:224.XXX.XXX.XXX到239.XXX.XXX.XXX

4. 端口号

①概念:是一个16位的整数,范围:1~65535.

②端口号分类:

  • 众所周知的端口:1~1023. (FTP: 21, SSH: 22, HTTP: 80, HTTPS: 469)
  • 保留端口:1024~5000  不建议使用
  • 可以使用端口:5001~65535

③注意事项:

  • TCP端口和UDP端口是相互独立的。(因为它们在内核中的处理路径不同)
  • 网络通信由IP地址+端口号来决定。IP地址确定主机位置,端口号确定主机中哪个进程来处理。

5. 字节序

①概念:当涉及内存中存取多字节数据时,就会遇到字节序的问题。

②两种字节序:

  • 小端:低字节序内容存储到低地址的内存中。
  • 大端:低字节序内容存储到高地址的内存中。

③注意事项:

  • 一般本地采用小端模式,网络传输采用大端模式。
  • 在发送数据和接收数据前,应该先将本地字节序和网络字节序进行转换。

④主机字节序和网络字节序转换函数:

#include <arpa/inet.h>

// 主要用于网络通信过程中IP和端口的转换
uint16_t htons(uint16_t hostshort);   //短整形,主机字节序->网络字节序

uint32_t htonl(uint32_t hostlong);    //整形,主机字节序->网络字节序	

uint16_t ntohs(uint16_t netshort);    //短整形,网络字节序->主机字节序

uint32_t ntohl(uint32_t netlong);     //整形,网络字节序->主机字节序

⑤IP地址转换函数:

inet_pton函数:将IP地址从主机字节序->网络字节序。

【1】头文件:#include <arpa/inet.h>

【2】函数原型:int inet_pton(int af, const char *src, void *dst);

【3】参数说明:

  • af:地址族,填写AF_INET(IPv4)或者AF_INET6(IPv6)
  • src:传入参数,即要转换的IP地址。例如:192.168.5.11
  • dst:传出参数,存放转换后的IP地址。

【4】返回值:成功返回1,第一个参数无效返回-1,第二个参数无效返回0.

inet_ntop函数:将IP地址从网络字节序->主机字节序。

【1】头文件:#include <arpa/inet.h>

【2】函数原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

【3】参数说明:

  • af:地址族,填写AF_INET(IPv4)或者AF_INET6(IPv6)
  • src:传入参数,其中存储了网络字节序的IP地址。
  • dst:传出参数,存放转换后的IP地址。
  • size:dst指向的内存中最多可以存储多少个字节。

【4】返回值:成功返回指针指向第三个参数对应的地址,失败返回NULL。


二、网络编程常用API

以下函数除read和write外都需包含头文件#include <sys/types.h>、#include <sys/socket.h>

1. socket函数

【1】功能:创建网络套接字。

【2】函数原型:int socket(int domain, int type, int protocol);

【3】参数说明:

domainAF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCAL本地通信
typeSOCK_STREAM流式套接字,对应TCP
SOCK_DGRAM数据报套接字,对应UDP
SOCK_RAW原始套接字
protocol一般写0即可,使用默认协议。非0一般用于原始套接字。

【4】返回值:成功返回套接字fd,失败返回-1。

【5】代码示例:

int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
    perror("socket error");
    return -1;
}

2. bind函数

【1】功能:将本地的IP、端口与套接字绑定。

【2】函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

【3】参数说明:

sockfd套接字,由socket函数生成
addrstruct sockaddr类型的变量的地址
addrlenaddr指向的内存的地址大小

struct sockaddr结构体:(写数据时一般不用)

struct sockaddr {
	sa_family_t sa_family;       // 地址族协议, ipv4
	char        sa_data[14];     // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}

struct sockaddr_in结构体:(常用)一般将端口和IP地址保存在该类型的变量中,然后强转为struct sockaddr类型。(它们的大小完全相同)

struct sockaddr_in
{
    sa_family_t sin_family;		/* 地址族 */
    in_port_t sin_port;         /* 端口号, 2字节 -> 网路字节序 */
    struct in_addr sin_addr;    /* IP地址, 4字节 -> 网络字节序 */
    /* 填充8字节,初始化为0 */
    unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
               sizeof (in_port_t) - sizeof (struct in_addr)];
};

struct in_addr
{
    in_addr_t s_addr;
};

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

#define  SERV_PORT    9999
#define  SERV_IP      "192.168.5.12"

struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;        //IPv4协议
sin.sin_port = htons(SERV_PROT); //端口号,转化为网络字节序
//IP地址,转化为网络字节序
if (inet_pton(AF_INET, SERV_IP, &sin.sin_addr.s_addr) != 1) {
    perror("inet_pton error");
    return -1;
}
if (bind(fd, (struct sockaddr*)&sin, sizeof(sin))) {
    perror("bind error");
    return -1;
}

3. listen函数

【1】功能:设置套接字监听。调用之前需要bind绑定。

【2】函数原型:int listen(int sockfd, int backlog);

【3】参数说明:

sockfd监听的文件描述符,由socket函数生成
backlog同时处理的最大连接数,一般可取5,最大值为128。表示系统允许2*backlog+1个客户端同时进行三次握手。

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

if (listen(fd, 128)) {
    perror("listen error");
    return -1;
}

4. accept函数

【1】功能:服务器阻塞等待客户端的连接请求,建立新连接,得到通信使用的套接字。

【2】函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

【3】参数说明:

sockfd监听的文件描述符,由socket函数生成
addr传出参数,保存客户端的地址信息
addrlenaddr指向的内存的大小

【4】返回值:成功返回与客户端进行通信的套接字,失败返回-1。

【5】代码示例:

struct sockaddr_in clientAddr;
int clientLen = sizeof(clientAddr);
int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);
if (cfd < 0) {
    perror("accept error");
    return -1;
}

5. connect函数

【1】功能:客户端发起连接请求。成功连接服务器后,客户端会自动随机绑定一个端口。

【2】函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

【3】参数说明:

sockfd套接字,由socket函数生成
addr存储要连接的服务端的IP和端口信息(需要网络字节序)
addrlenaddr指向的内存的大小

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

#define  SERV_PORT    9999
#define  SERV_IP      "192.168.5.12"

struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;//IPv4协议
sin.sin_port = htons(SERV_PORT);//目标服务端的端口号,网络字节序
//目标服务端的IP地址,网络字节序
if (inet_pton(AF_INET, SERV_IP, &sin.sin_addr.s_addr) != 1) {
    perror("inet_pton error");
    return -1;
}
//调用connect函数
if (connect(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
    perror("connect error");
    return -1;
}

6. read和recv函数

【1】功能:接收数据。

【2】函数原型:

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

【3】参数说明:

sockfd用于通信的文件描述符
buf存储接收的数据
lenbuf指向的内存的容量
flags特殊属性,一般不使用,指定为0

【4】返回值:>0表示实际接收的字节数,==0表示对方断开了连接,-1表示失败。

7. write和send函数

【1】功能:接收数据。

【2】函数原型:

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

【3】参数说明:

sockfd用于通信的文件描述符
buf发送的数据
lenbuf的长度
flags特殊属性,一般不使用,指定为0

【4】返回值:>0表示实际发送的字节数(与len相等),-1表示失败。


三、TCP编程

1. TCP介绍

TCP(Transmission Control Protocol)是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:建立连接需要三次握手,断开连接需要四次挥手。
  • 可靠安全:在TCP通信过程中,对每个发送的数据包都会进行校验,若数据丢失则重传。
  • 流式传输:发送端和接收端的处理速度、数据量都可以不一致。

2. TCP通信流程

参考爱编程的大丙:

3. TCP服务端和客户端程序

服务端程序:接收客户端数据并打印,同时将收到的数据重新发回客户端。

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

#define SERV_PORT   9999

int main(int argc, char **argv)
{
    /* 1.创建TCP的套接字 */
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("server socket error");
        return -1;
    }

    /* 2.绑定IP和端口号 */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY宏表示本机所有IP
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret == -1) {
        perror("server bind error");
        return -1;
    }

    /* 3.设置监听 */
    ret = listen(fd, 128);
    if (ret == -1) {
        perror("server listen error");
        return -1;
    }

    /* 4.等待客户端连接 */
    struct sockaddr_in clientAddr;
    int clientLen = sizeof(clientAddr);
    int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);
    if (cfd == -1) {
        perror("server accept error");
        return -1;
    }
    //打印客户端信息
    char ip[24] = {0};
    printf("客户端IP: %s, 端口: %d\n",
            inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip)),
            ntohs(clientAddr.sin_port));

    /* 5.与客户端通信 */
    while (1) {
        //接收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        ret = read(cfd, buf, sizeof(buf));
        if (ret == 0) {
            printf("服务端:与客户端断开连接\n");
            break;
        } else if (ret < 0) {
            perror("server read error");
            break;
        } else {
            //打印接收数据,并重新发回接收的数据
            printf("服务端接收: %s\n", buf);
            write(cfd, buf, sizeof(buf));
        }
    }

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

客户端程序:每1秒发送指定数据,同时接收来自服务端的数据。

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

#define SERV_PORT   9999
#define SERV_IP     "192.168.124.6"

int main(int argc, char **argv)
{
    /* 1.创建TCP的套接字 */
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        perror("client socket error");
        return -1;
    }

    /* 2.与服务端建立连接 */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret == -1) {
        perror("client connect error");
        return -1;
    }
    //打印连接的服务端信息
    printf("连接的服务端IP: %s, 端口: %d\n", SERV_IP, SERV_PORT);

    /* 3.与服务端通信 */
    int number = 0;
    while (1) {
        //发送数据
        char buf[1024] = {0};
        sprintf(buf, "客户端: number = %d\n", number++);
        write(fd, buf, sizeof(buf));

        //接收数据
        memset(buf, 0, sizeof(buf));
        ret = read(fd, buf, sizeof(buf));
        if (ret == 0) {
            printf("客户端:与服务端断开连接\n");
            break;
        } else if (ret < 0) {
            perror("client read error");
            break;
        } else {
            printf("客户端接收: %s\n", buf);
        }
        sleep(1);//发送数据慢一些
    }

    /* 4.关闭文件描述符 */
    close(fd);
    return 0;
}

4. TCP并发服务器(多进程)

单个服务器需要能够与多个客户端进行通信,因此本节采用多进程方式来实现服务器的并发。

服务器中的父进程:

  • 循环accept,每次与客户端建立新连接后,fork子进程。
  • sigaction捕捉SIGCHLD信号,回收子进程资源。

服务器中的子进程:负责与新连接的客户端进行通信。

注意事项:accept函数阻塞时,若捕捉到SIGCHLD信号那么会取消阻塞并执行信号处理函数,那么处理完毕后accept函数返回值为-1并且错误号为EINTR。我们需要编写相关代码来处理这种情况,让服务器重新调用accept而不是退出。

服务器程序:监听所有的客户端连接,每次建立新连接后都创建子进程来与客户端进行通信。子进程读取从客户端接收的数据并显示。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>

#define SERV_PORT   6666

/* SIGCHLD处理函数 */
void cycle(int arg)
{
    while (1) {
        pid_t cPid = waitpid(-1, NULL, WNOHANG);
        if (cPid <= 0) {
            break;
        } else {
            printf("子进程%d被回收\n", cPid);
        }
    }
}

/* 子进程与客户端通信 */
int serverCommu(int fd)
{
    char buf[1024] = {0};
    int len = read(fd, buf, sizeof(buf));
    if (len < 0) {
        printf("server read error\n");
    } else if (len == 0) {
        printf("服务端: 与客户端断开连接\n");
    } else {
        printf("服务端接收: %s\n", buf);
    }
    return len;
}


int main(int argc, char **argv)
{
    /* 1.创建socket套接字 */
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("server socket error");
        return -1;
    }

    /* 2.绑定IP和端口 */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
        perror("server bind error");
        return -1;
    }

    /* 3.设置监听  */
    ret = listen(fd, 5);
    if (ret < 0) {
        perror("server listen error");
        return -1;
    }

    /* 捕捉信号SIGCHLD, 回收子进程 */
    struct sigaction sact;
    sact.sa_flags = 0;
    sact.sa_handler = cycle;
    sigemptyset(&sact.sa_mask);
    sigaction(SIGCHLD, &sact, NULL);

    /* 4.循环等待客户端连接 */
    while (1) {
        //accept阻塞等待
        struct sockaddr_in clientAddr;
        int clientLen = sizeof(clientAddr);
        int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);
        if (cfd == -1) {
            if (errno == EINTR) {//SIGCHLD处理后
                continue;
            }
            perror("server accept error");
            break;
        }
        //打印客户端IP和Port
        char IPAddr[24];
        printf("连接的客户端IP: %s, 端口: %d\n",
                inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, IPAddr, sizeof(IPAddr)),
                ntohs(clientAddr.sin_port));
        //创建子进程与客户端通信
        pid_t pid = fork();
        if (pid < 0) {//出错
            perror("fork error");
            break;
        } else if (pid == 0) {//子进程
            close(fd);
            while (1) {
                int len = serverCommu(cfd);
                if (len <= 0) {
                    break;
                }
            }
            close(cfd);
            return 0;
        } else {//父进程
            close(cfd);
        }
    }

    /* 5.父进程关闭套接字 */
    close(fd);
    return 0;
}

客户端程序:与服务器建立连接,发送用户输入的数据。

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

#define SERV_PORT   6666
#define SERV_IP     "192.168.5.12"

int main(int argc, char **argv)
{
    /* 1.创建socket套接字*/
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("client socket error");
        return -1;
    }

    /* 2.建立连接 */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0) {
        perror("client connect error");
        return -1;
    }

    /* 3.与服务端通信 */
    while (1) {
        //发送消息
        char buf[1024] = {0};
        char *tips = "客户端: ";
        int tipsLen = strlen(tips);
        strcpy(buf, tips);
        if (!fgets(buf+tipsLen, sizeof(buf)-tipsLen, stdin)) {
            perror("input error");
            continue;
        }
        write(fd, buf, sizeof(buf));
    }

    /* 4.关闭套接字 */
    close(fd);
    return 0;
}

测试结果:运行一个服务器程序、两个客户端程序,查看对应的结果。

5. TCP并发服务器(多线程)

本节采用多线程的方式来实现TCP并发服务器。

服务器中的主线程:

  • 循环accept,将新建立的连接存储与某个结构体数组元素中。
  • 每有一个客户端连接,就创建一个子线程来与其通信。
  • 子线程分离,使子线程结束后让系统回收资源。

服务器中的子线程:负责与新连接的客户端进行通信。

注意事项:由于线程只有栈空间是独立的,需要注意数据覆盖的问题。在下面的程序案例中,采用一个全局的结构体数组来保存每一个线程对应操作的通信套接字、线程ID、客户端信息。

服务器程序:监听所有的客户端连接,在建立新连接前找到一块没有被占用的内存,保存建立新连接的通讯套接字、子线程ID和客服端信息。创建的子线程用来和客户端通信。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>

#define SERV_PORT   6666

typedef struct SockInfo {
    int fd;                 //通信的套接字
    pthread_t tid;          //线程tid
    struct sockaddr_in addr;//保存客户端信息
} SockInfo;

SockInfo sInfos[128];       //每个线程对应一个结构体


/* 子线程通信操作 */
void* working(void* arg)
{
    SockInfo *sinfo = (SockInfo*)arg;
    //打印客户端信息
    char IP[24] = {0};
    inet_ntop(AF_INET, &sinfo->addr.sin_addr.s_addr, IP, sizeof(IP));
    printf("连接的客户端IP: %s, 端口: %d\n", IP, ntohs(sinfo->addr.sin_port));
    //接收客户端的信息
    while (1) {
        char buf[1024] = {0};
        int ret = read(sinfo->fd, buf, sizeof(buf));
        if (ret < 0) {
            perror("server read error");
            break;
        } else if (ret == 0) {
            printf("服务端: 与客户端断开连接\n");
            break;
        } else {
            printf("服务端接收: %s\n", buf);
        }
    }
    //结束前清除数据
    sinfo->fd = -1;
    sinfo->tid = -1;
    memset(&sinfo->addr, 0, sizeof(sinfo->addr));
    return NULL;
}


int main(int argc, char **argv)
{
    /* 1.创建socket套接字 */
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("server socket error");
        return -1;
    }

    /* 2.绑定IP和端口 */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
        perror("server bind error");
        return -1;
    }

    /* 3.设置监听  */
    ret = listen(fd, 5);
    if (ret < 0) {
        perror("server listen error");
        return -1;
    }

    /* 4.主线程循环监听,子线程通信 */
    int sInfosLen = sizeof(sInfos) / sizeof(sInfos[0]);
    //初始化结构体数组
    for (int i = 0; i < sInfosLen; i++)
    {
        sInfos[i].fd = -1;
        sInfos[i].tid = -1;
        memset(&sInfos[i].addr, 0, sizeof(sInfos[i].addr));
    }
    //循环监听
    while (1) {
        //遍历sInfos,找到未占用的元素
        SockInfo *pinfo = NULL;
        for (int i = 0; i < sInfosLen; i++)
        {
            if (sInfos[i].fd == -1) {
                pinfo = &sInfos[i];
                break;
            }
            if (i == sInfosLen - 1) {//若全部被占用,停留在最后
                sleep(1);
                --i;
            }
        }
        //存在新连接,创建子线程,保存于pinfo指向的结构体
        int sockaddrLen = sizeof(pinfo->addr);
        int connfd = accept(fd, (struct sockaddr*)&pinfo->addr, &sockaddrLen);
        if (connfd < 0) {
            perror("server accept error");
            break;
        }
        pinfo->fd = connfd;
        pthread_create(&pinfo->tid, NULL, working, pinfo);
        pthread_detach(pinfo->tid);
    }

    /* 5.主线程关闭套接字 */
    close(fd);
    return 0;
}

客户端程序:与服务器建立连接,发送用户输入的数据。(与多进程的版本相同)

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

#define SERV_PORT   6666
#define SERV_IP     "192.168.5.12"

int main(int argc, char **argv)
{
    /* 1.创建socket套接字*/
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        perror("client socket error");
        return -1;
    }

    /* 2.建立连接 */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0) {
        perror("client connect error");
        return -1;
    }

    /* 3.与服务端通信 */
    while (1) {
        //发送消息
        char buf[1024] = {0};
        char *tips = "客户端: ";
        int tipsLen = strlen(tips);
        strcpy(buf, tips);
        if (!fgets(buf+tipsLen, sizeof(buf)-tipsLen, stdin)) {
            perror("input error");
            continue;
        }
        write(fd, buf, sizeof(buf));
    }

    /* 4.关闭套接字 */
    close(fd);
    return 0;
}

测试结果:运行一个服务器程序、两个客户端程序,查看对应的结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1929586.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Monaco 使用 DocumentFormattingEditProvider

文档格式化&#xff0c;是 VSCode 比较常用的功能&#xff0c;在文档上点击右键选择格式化文档。效果如下&#xff1a; 在 Monaco 通过 registerDocumentFormattingEditProvider 方法注册处理函数&#xff0c;实现 provider 方法。 provider 方法返回格式化好的代码。 TextE…

Android C++系列:Linux文件系统(二)

1. VFS虚拟文件系统 Linux支持各种各样的文件系统格式&#xff0c;如ext2、ext3、reiserfs、FAT、NTFS、iso9660 等等&#xff0c;不同的磁盘分区、光盘或其它存储设备都有不同的文件系统格式&#xff0c;然而这些文件系统 都可以mount到某个目录下&#xff0c;使我们看到一个…

Kafka(四) Consumer消费者

一&#xff0c;基础知识 1&#xff0c;消费者与消费组 每个消费者都有对应的消费组&#xff0c;不同消费组之间互不影响。 Partition的消息只能被一个消费组中的一个消费者所消费&#xff0c; 但Partition也可能被再平衡分配给新的消费者。 一个Topic的不同Partition会根据分配…

【C#】部分国家/语言,string字符串转decimal、float时,小数点解析异常、小数点丢失、小数点被忽略

现象&#xff1a; 部分国家地区&#xff0c;字符串转小数后&#xff0c;小数点丢失&#xff0c;比如&#xff1a;输入"12.34"&#xff0c;输出1234&#xff0c;而非12.34。 部分相关函数decimal.Parse、decimal.TryParse、float.Parse、float.TryParse 原因&…

【Linux】常用命令总结(updating)

1.date2.du&#xff08;disk use&#xff09;3.df&#xff08;disk free&#xff09;4.find5.crontab6.netstat shell命令可以使用man查看命令文档说明&#xff0c;说明界面中可通过b(backward)向上翻页&#xff0c;f(forward)向下翻页&#xff0c;g(go to)跳到说明首页&#x…

【问题记录】Docker配置mongodb副本集实现数据流实时获取

配置mongodb副本集实现数据流实时获取 前言操作步骤1. docker拉取mongodb镜像2. 连接mongo1镜像的mongosh3. 在mongosh中初始化副本集 注意点 前言 由于想用nodejs实现实时获取Mongodb数据流&#xff0c;但是报错显示需要有副本集的mongodb才能实现实时获取信息流&#xff0c;…

springboot老年慢性病药物管理系统-计算机毕业设计源码70568

目录 摘要 Abstract 第一章 绪论 1.1 选题背景及意义 1.2 国内外研究现状 1.3 研究方法 第二章 相关技术介绍 2.1 MySQL简介 2.2 Java编程语言 2.3 B/S模式 2.4 springboot框架 第三章 老年慢性病药物管理系统 系统分析 3.1 系统目标 3.2 系统可行性分析 3.2.1 技…

【linux】服务器ubuntu安装cuda11.0、cuDNN教程,简单易懂,包教包会

【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会 【创作不易&#xff0c;求点赞关注收藏】 文章目录 【linux】服务器ubuntu安装cuda11.0、cuDNN教程&#xff0c;简单易懂&#xff0c;包教包会一、版本情况介绍二、安装cuda1、到官网…

Java面试八股之Redis哨兵机制

Redis哨兵机制 Redis Sentinel&#xff08;哨兵&#xff09;模式是一种高可用解决方案&#xff0c;用于监控和自动故障转移Redis主从集群。以下是对哨兵模式详细过程的描述&#xff1a; 1. 初始化与配置 部署哨兵节点&#xff1a;在不同的服务器上部署一个或多个Redis Sentin…

链表题目专题

19. 删除链表的倒数第 N 个结点 给定一个链表&#xff0c;删除链表的倒数第 n 个节点&#xff0c;并且返回链表的头结点。 非递归解决 这题让删除链表的倒数第n个节点&#xff0c;首先最容易想到的就是先求出链表的长度length&#xff0c;然后就可以找到要删除链表的前一个结…

Hyper-v创建二代虚拟机无法进入bios问题解决

首先要确定从dvd驱动在上面&#xff0c;如果不在则把它向上移动然后保存。 启动虚拟机会进入下面界面 然后点下最左边的按钮然后疯狂点击f2(有的电脑是fnf2) 就可以顺利进入bios引导界面。

手机拯救计划:掌握3个技巧,轻松找回通讯录联系人号码

手机通讯录是我们的“社交地图”&#xff0c;一旦失去联系&#xff0c;就仿佛置身于茫茫人海中&#xff0c;不知所措。而安卓手机用户们&#xff0c;更是对通讯录的依赖达到了前所未有的高度&#xff0c;当发现它们丢失了&#xff0c;很容易产生焦虑情绪。别急&#xff0c;通过…

ARM架构(一)—— ARMV8V9基础概念

目录 1.ARMCore的时间线2.ARM术语小结2.1 A64和arrch642.2ARM架构现在的5个系列2.3 微架构2.4 PE2.5 Banked2.6 ARM文档术语2.7 IMPLEMENTATION DEFINFD 和 DEPRECATED2.8 EL1t和EL1h 3 ARMv7的软件架构4 安全状态切换模型4.1 Secure state和Non-secure state介绍 5 Interproce…

数据类型与结构设计:Rust 语言的深度探索

数据类型与结构设计&#xff1a;Rust 语言的深度探索 引言&#xff1a;数据与结构的精妙交响Rust 数据类型概览&#xff1a;坚实的基础数据类型详解基本数据类型&#xff1a;构建程序的原子单元复合数据类型&#xff1a;构建复杂数据结构的积木与结构体和枚举的结合 结构体与枚…

layui table template、或toolbar实现超出隐藏、更多展示全部效果

使用Layui table时&#xff0c;经常会使用template、或toolbar自定义模版属性。当使用该属性自定义HTML时&#xff0c;layui table 单元格原有的文本超出省略号隐藏功能&#xff0c;在该单元格讲不会生效。 前言&#xff1a;首先我们先搞懂layui超出隐藏原理&#xff0c;table单…

PHP微信小程序视频图文流量主变现小程序系统源码

&#x1f4b0;微信小程序新机遇&#xff01;视频图文流量主变现秘籍&#x1f511; &#x1f680;【流量变现新风口】&#x1f680; 还在为微信小程序的庞大流量如何转化为真金白银而苦恼吗&#xff1f;今天&#xff0c;就带你揭秘“微信小程序视频图文流量主变现小程序”的神…

mysql group_concat()函数、行转列函数

文章目录 一、group_concat函数1.1、语法1.2、示例1.2.1、查询所有姓名&#xff0c;并显示在一行1.2.2、单列合并&#xff0c;指定冒号分隔符1.2.3、单列合并&#xff0c;去重1.2.4、多列拼接合并1.2.5、多列拼接合并&#xff0c;列和列之间指定分隔符 在mysql的关联查询或子查…

十、Java集合 ★ ✔【泛型、通配符、List、Set、TreeSet、自然排序和比较器排序、Collections、可变参数、Map】

day05 泛型,数据结构,List,Set 今日目标 泛型使用 数据结构 List Set 1 泛型 1.1 泛型的介绍 ★ 泛型是一种类型参数&#xff0c;专门用来保存类型用的 最早接触泛型是在ArrayList&#xff0c;这个E就是所谓的泛型了。使用ArrayList时&#xff0c;只要给E指定某一个类型…

【Linux系统】信号的产生

信号 关于信号举一些生活中的例子 --- 比如交通指示灯... - 信号在生活中&#xff0c;随时可以产生 --- 信号的产生和我们是异步的&#xff01;&#xff08;异步的意思就是信号的产生和我没有直接关系&#xff09; - 你能认识这个信号 --- 我们知道这是信号&#xff0c;我们才…

C语言基础and数据结构

C语言程序和程序设计概述 程序:可以连续执行的一条条指令的集合 开发过程:C源程序(.c文件) --> 目标程序(.obj二进制文件,目标文件) --> 可执行文件(.exe文件) -->结果 在任何机器上可以运行C源程序生成的 .exe 文件 没有安装C语言集成开发环境,不能编译C语言程…