【Linux网络编程_TCP/UDP_字节序_套接字 实现: FTP 项目_局域网聊天项目 (已开源) 】.md updata:23/11/03

文章目录

      • TCP/UDP对比
      • 端口号作用
      • 字节序
      • 字节序转换api
      • [套接字 socket](#套接字 socket)
        • [实现网络通讯服务端 逻辑思路](#实现网络通讯服务端 逻辑思路)
        • 满血版双方通讯/残血版多方通讯
          • [服务端 demo](#服务端 demo)
          • [客户端 demo](#客户端 demo)
        • [FTP 项目实现](#FTP 项目实现)
          • [sever demo:](#sever demo:)
          • [client demo:](#client demo:)
        • [局域网多方通讯 配合线程实现](#局域网多方通讯 配合线程实现)
          • [sever demo:](#sever demo:)
          • [client demo:](#client demo:)

TCP/UDP对比

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前 不需要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
    UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

端口号作用

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等

这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。

实际上是通过"IP地址+端口号"来区 分不同的服务的。

端口提供了一种访问通道,

服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。

字节序

Little endian 小端字节序 -低位在前

Big endian 大端字节序 -高位在前

网络字节序 = 大端字节序 -高位在前

字节序转换api

javascript{.line-numbers} 复制代码
#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue);    
//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);    
//返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);     
//返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue);     
//返回主机字节序的值

h代表host,n代表net,s代表short(两个字节),
l代表long(4个字节),[据需求选择即可];
通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。
有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取

套接字 socket

实现网络通讯服务端 逻辑思路
  1. 创建套接字 规定地址族议、TCP/UDP等协议;
  2. 绑定套接字到服务器ip+端口,ip+端口储存在struct sockaddr类型的结构体指针内,可以使用更好用的struct sockaddr_in结构体类型 转成struct sockaddr类型 即可;
  3. 服务器监听客户端接入;
  4. 接收客户端套接字 并选择是否接受信息;
  5. 根据客户端的套接字,进行 读/写;
demo:
javascript{.line-numbers} 复制代码
// 可以使用linux 的 telnet 连接服务器ip,进行测试;
// 因为是测试demo,这里绑定的服务器ip需在代码内修改,
// 后续的双方通讯则在运行时代码时怎加后缀argv参数即可; 
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
int main()
{
    //int socket(int domain, int type, int protocol);
    //01 创建套接字,返回套接字ID;
    int s_fd = socket(AF_INET,SOCK_STREAM,0);
    if(s_fd < 0){
        perror("socket error:");
        exit(-1);
    }
/*
    struct sockaddr_in 是一个用于存储 IPv4 地址和端口号的结构体,其原型定义如下:
    struct sockaddr_in {
    sa_family_t sin_family;     // 地址族,一般为 AF_INET
    in_port_t sin_port;         // 16 位 TCP/UDP 端口号,网络字节序
    struct in_addr sin_addr;    // 32 位 IPv4 地址,网络字节序
            ? 原型
            struct in_addr {
                in_addr_t s_addr;    // 32 位 IPv4 地址,网络字节序
            };
    char sin_zero[8];           // 未使用,填充 0
    };
    在网络编程中,可以使用 struct sockaddr_in 结构体来表示套接字的地址信息,
    例如在客户端与服务器之间建立 TCP 连接时,需要指定服务器的 IP 地址和端口号,
    可以将这些信息存储在一个 struct sockaddr_in 类型的结构体中,
    并传递给 connect() 函数或者 bind() 函数。

    需要注意的是,在使用 struct sockaddr_in 结构体时,
    需要将其中的端口号和 IP 地址转换为网络字节序(big-endian),
    可以使用 htons() 函数和 inet_aton() 函数来进行转换。
    同时,在使用 struct sockaddr_in 结构体时,也需要注意其大小和对齐方式,
    以确保在不同的系统上都能正确地解析套接字地址信息。
*/
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(8989);
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton("192.168.1.211",&s_addr.sin_addr);


    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

    //int listen(int sockfd, int backlog);
    //03 监听,最多听几次,这里是10次
    listen(s_fd,10);

    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //04 在服务端套接字,接收客户端地址信息,并创建一个新的套接字来处理这些信息;
    //这里不在乎客户端的信息,直接NULL,拿了新的套接字空间len也NULL;
    int c_fd = accept(s_fd,NULL,NULL);

    //05 read

    //06 write

    printf("new one connect!\n");

    while(1);

    return 0;
}
满血版双方通讯/残血版多方通讯

满血双方通讯:服务器与客户端 流畅沟通

残血版多方通讯:多运行几个客户端,多个客户端争抢服务端的消息,谁抢到谁显示;

(解决思路:

1.有几个客户端接入,就mark++,根据mark的数量,创建线程给每个客户端发消息;

)

服务端 demo
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char** argv)
{
    if(argc < 3){
        printf("please input two argv : ip and port\n");
    }

    //int socket(int domain, int type, int protocol);
    //01 创建套接字,返回套接字ID;
    int s_fd = socket(AF_INET,SOCK_STREAM,0);
    if(s_fd < 0){
        perror("socket error:");
        exit(-1);
    }

    struct sockaddr_in c_addr;
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    s_addr.sin_port = htons(atoi(argv[2]));
    
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&s_addr.sin_addr);

    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

    //int listen(int sockfd, int backlog);
    //03 监听,最多听几次,这里是10次
    listen(s_fd,100);

    int mark =0;

    while(1){
        //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        //04 在服务端套接字,接收客户端地址信息,并创建一个新的套接字来处理这些信息;
        //如不在乎客户端的信息,直接NULL,拿了新的套接字空间len也NULL;
        int nsize = sizeof(struct sockaddr_in);

        int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&nsize);
            if(c_fd < 0){
            perror("accept client error");
        }else{
            mark++;
            }


        //inet_ntoa() 把网络字节序转成int;
        printf("new connect:%s \n",inet_ntoa(c_addr.sin_addr));

        //06 write
        if(fork() == 0){ 
            if(fork() == 0){                    
                //06 write
                while(1){
                    char dataStr[1024*5] = {0};                        
                    gets(dataStr);
                    int n_write = write(c_fd,dataStr,strlen(dataStr)+1); 
                }  
            }                
            //05 read
            char buf[1024] = {0};
            char buf1[1024] = {0};
            while(1){
                int n_read = read(c_fd,buf,1024);
                if(strcmp(buf,buf1) != 0){
                    printf("client massage:no.%d -- %s\n",n_read,buf); 
                    memset(buf1,0,1024);
                    strcpy(buf1,buf);
                    memset(buf,0,1024);
                }
            }
        } 
    }
   
    return 0;

}
客户端 demo
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char** argv)
{
    if(argc < 3){
        printf("please input two argv : ip and port\n");
    }

    //int socket(int domain, int type, int protocol);
    //01 创建套接字,返回套接字ID;
    int c_fd = socket(AF_INET,SOCK_STREAM,0);
    if(c_fd < 0){
        perror("socket error:");
        exit(-1);
    }

    struct sockaddr_in c_addr;

    c_addr.sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    c_addr.sin_port = htons(atoi(argv[2]));
    
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&c_addr.sin_addr);

    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 客户端连接服务器的ip和端口;
    connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));

    //inet_ntoa() 把网络字节序转成int;
    printf("connect sever:%s \n",inet_ntoa(c_addr.sin_addr)); 
    while(1){         
                if(fork() == 0){                    
                    //06 write
                    while(1){
                        char dataStr[1024*5] = {0};                        
                        gets(dataStr);
                        int n_write = write(c_fd,dataStr,strlen(dataStr)+1); 
                    }
                }                
                //05 read
                char buf[1024] = {0};
                char buf1[1024] = {0};
                while(1){
                    int n_read = read(c_fd,buf,1024);
                    if(strcmp(buf,buf1) != 0){
                        printf("server massage:%s  (size:%d)\n",buf,n_read); 
                        memset(buf1,0,1024);
                        strcpy(buf1,buf);
                        memset(buf,0,1024);
                    }
                }                 
    }
    return 0;
}
FTP 项目实现

功能:get put ls pwd cd lls lpwd

sever demo:
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LS      0
#define GET     1
#define PWD     2
#define LPWD    3
#define LCD     4
#define LLS     5
#define CD      6
#define PUT     7
#define QUIT    8
#define DOFILE  9
struct sockaddr_in c_addr;
struct sockaddr_in s_addr;

struct MASSAGE 
{
    int type;
    char data[1024*5];
    char secondBuf[128];
}Msg;

char* getdir(char* comdata)
{
    char* a = strtok(comdata, " ");
    if (a == NULL) {
        return NULL;
    }
    char* b = strtok(NULL, " ");
    if (b == NULL) {
        return a;  // 如果没有空格,直接返回第一个字符串
    } else {
        return b;  // 如果有空格,返回第二个字符串
    }
}


int getNCOM(char* comdata) 
{
    if (strcmp(comdata, "ls") == 0)         return 0;
    if (strncmp(comdata, "get", 3) == 0)    return 1;
    if (strcmp(comdata, "pwd") == 0)        return 2;
    if (strcmp(comdata, "lpwd") == 0)       return 3;
    if (strncmp(comdata, "lcd", 3) == 0)    return 4;
    if (strcmp(comdata, "lls") == 0)        return 5;
    if (strncmp(comdata, "cd", 2) == 0)     return 6;
    if (strncmp(comdata, "put", 3) == 0)    return 7;
    if (strcmp(comdata, "quit") == 0)       return 8;
    if (strncmp(comdata, "dofile", 6) == 0) return 9;
    /* 提示命令不存在 错误 */               return 10; 
}

void doCom(char* comdata,int c_fd)
{
    char dir[1024] = {0};
    //获取空格后的部分命令,没有空格则返回原命令
    strcpy(dir,getdir(comdata));
    if(*dir == 0){
        perror("why");
    }    
    //将命令转成int宏;
    int nCom = getNCOM(comdata);
    int op_fd = 0;
    //定义一个1个文件流指针放popen的文件流;
    FILE * r;
    char dir1[1024*2] = {0};
    char dir2[1024*2] = {0};
    switch(nCom){
        case LS :
        case PWD:
            r = popen(dir,"r");
            fread(dir1,sizeof(dir1),1,r);
            write(c_fd,dir1,sizeof(dir1));
            break;
        case CD:  
            if(strcmp("cd",dir) >= 0){
                chdir("/");                
            } 
            else if(chdir(dir) < 0){
                perror("chdir 路径切换失败!");
            }
            break;

        case GET:
            sprintf(dir1,"./%s",dir);
            printf("file path:%s\n",dir1);
            op_fd = open(dir1,O_RDWR,0666);
            if(op_fd < 0){
                perror("open error"); 
                write(c_fd,"open error",100);   
                break;            
            }
            read(op_fd,dir1,sizeof(dir1));
            write(c_fd,dir1,strlen(dir1));
            close(op_fd);
            break;
        case PUT:
            sleep(1);
            sprintf(dir1,"./%s",dir);
            op_fd = open(dir1,O_RDWR|O_CREAT|O_TRUNC,0666);
            printf("dir1:%s\n",dir1);
            if(op_fd < 0){
                perror("open error");
            }       
            //memset(dir1,0,sizeof(dir1));
            if((recv(c_fd, dir2, sizeof(dir2), 0)) <= 0){
                perror("recv error");
            } 
            printf("--:\n%s\n",dir2);
            write(op_fd,dir2,strlen(dir2));
            close(op_fd);
            break;
        case 10:
            sprintf(dir1,"error : commomd never!\n"); 
            write(c_fd,dir1,sizeof(dir1));
            break;   
    }
    memset(dir,0,sizeof(dir));
} 

void initNetSocket(int argc,int* s_fd,char** argv)
{
    //判断main函数-参数数量
    if(argc < 3){
        printf("please input two argv!!\n");
    }
    //01 创建套接字,返回套接字ID;
    *s_fd = socket(AF_INET,SOCK_STREAM,0);
    if(*s_fd < 0){
        perror("socket error:");
        exit(-1);
    }   
    //设为IPV4 即AF_INET;
    s_addr.sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    s_addr.sin_port = htons(atoi(argv[2]));    
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&s_addr.sin_addr);
    //把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    bind(*s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    //int listen(int sockfd, int backlog);
    //监听,最多听几次,这里是10次,最多连10个客户端
    listen(*s_fd,10);       
}

int main(int argc,char** argv)
{    
    int c_fd = 0;  
    int s_fd = 0;
    int nsize = sizeof(struct sockaddr_in);

    //初始化套接字,绑定服务端ip;检测main参数,设置监听最大数量;
    initNetSocket(argc,&s_fd,argv);

    while(1){
        //接收客户端连接
        c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&nsize);
        if(c_fd < 0){
            perror("accept client error");
            break;
        }

        // 显示新客户端连接,inet_ntoa() 把网络字节序转成int;
        printf("new connect:%s \n",inet_ntoa(c_addr.sin_addr));        
        
        if(fork() == 0){ 
            //read
            while(1){                
                memset(Msg.data,0,sizeof(Msg.data));
                int n_read = read(c_fd,Msg.data,sizeof(Msg.data));
                if(n_read == 0){
                    printf("client out!\n");
                    break;
                }else if(n_read > 0){
                    printf("client data:%s\n",Msg.data);
                    doCom(Msg.data,c_fd);                      
                }    
            }
        }      
    }
    close(c_fd);  
    close(s_fd);  
    return 0;
}
client demo:
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LS      0
#define GET     1
#define PWD     2
#define LPWD    3
#define LCD     4
#define LLS     5
#define CD      6
#define PUT     7
#define QUIT    8
#define DOFILE  9

char* getdir(char* comdata)
{
    char* a = strtok(comdata, " ");
    if (a == NULL) {
        return NULL;
    }
    char* b = strtok(NULL, " ");
    if (b == NULL) {
        return a;  // 如果没有空格,直接返回第一个字符串
    } else {
        return b;  // 如果有空格,返回第二个字符串
    }
}

int getNCOM(char* comdata) 
{
    if (strcmp(comdata, "ls") == 0)         return 0;
    if (strncmp(comdata, "get", 3) == 0)    return 1;
    if (strcmp(comdata, "pwd") == 0)        return 2;
    if (strcmp(comdata, "lpwd") == 0)       return 3;
    if (strncmp(comdata, "lcd", 3) == 0)    return 4;
    if (strcmp(comdata, "lls") == 0)        return 5;
    if (strncmp(comdata, "cd", 2) == 0)     return 6;
    if (strncmp(comdata, "put", 3) == 0)    return 7;
    if (strcmp(comdata, "quit") == 0)       return 8;
    if (strncmp(comdata, "dofile", 6) == 0) return 9;
    /* 提示命令不存在 错误 */               return 10; 
}

void doCom(char* comdata,int c_fd,int* fd)
{
    char dir[1024] = {0};    
    strcpy(dir,getdir(comdata));
    if(*dir == 0){
        perror("why");
    }
    int op_fd = 0;
    int nCom = getNCOM(comdata);
    FILE * r;
    char dir1[1024*2] = {0};
    switch(nCom){
        case LPWD:
            r = popen("pwd","r");
            fread(dir1,sizeof(dir1),1,r);
            printf("---------------------\n");
            printf("%s",dir1); 
            printf("---------------------\n");
            break;
        case LLS:
            r = popen("ls","r");
            fread(dir1,sizeof(dir1),1,r);
            printf("---------------------\n");
            printf("%s",dir1); 
            printf("---------------------\n");
            break;
        case GET:
            sprintf(dir1,"./%s",dir);
            op_fd = open(dir1,O_RDWR|O_CREAT|O_TRUNC,0666);  
            printf("creat file:%s\n",dir1);          
            if(op_fd < 0){
                perror("open error");
                break;
            }  
            //memset(dir1,0,sizeof(dir1));
            lseek(c_fd,0,SEEK_SET);
            if(read (c_fd,dir1,sizeof(dir1)) <= 0){
                perror("read why");
            }; 
            printf("file data:\n%s\n",dir1);
            write(op_fd,dir1,strlen(dir1));
            close(op_fd);
            break;
        case PUT:
            sprintf(dir1,"./%s",dir);
            printf("dir1:%s\n",dir1);   
            op_fd = open(dir1,O_RDWR,0666);            
            if(op_fd < 0){
                perror("open error");
            }
            memset(dir1,0,sizeof(dir1));
            read(op_fd,dir1,sizeof(dir1)); 
            printf("--:\n%s\n",dir1);       
            if(send(c_fd,dir1,sizeof(dir1),0) < 0){
                perror("send error");
            }   
            close(op_fd); 
            break;
        case QUIT:
            printf("---------------------\n");
            printf("client quit!\n"); 
            printf("---------------------\n");
            //如果pipe无名管道写入失败就提示;
            close(fd[0]);
            if (write(fd[1], "QU", 3) == -1) {
                perror("Failed to write to pipe");
            }
            close(fd[1]);
            break;   
    }

    memset(dir,0,sizeof(dir));
} 
void readx(int c_fd)
{
    char buf[1024*2] = {0};
    while(1){
        lseek(c_fd,0,SEEK_SET);
        int n_read = read(c_fd,buf,sizeof(buf));     
        if(n_read <= 0){
            printf("read:%d\n",n_read);
            exit(1);
        }
        printf("---------------------\n");
        printf("%s",buf); 
        printf("---------------------\n");

        memset(buf,0,sizeof(buf));      
    }
}

int checkQuit(int* fd)
{
    char data1[128] = {0};        
    close(fd[1]);
    read(fd[0],data1,128);
    if(strcmp("QU",data1) == 0){
        memset(data1,0,sizeof(data1));
        return 1;     
    } else{
        return 0;
    }
}

void checkArgv(int argc)
{
    if(argc < 3){
        printf("please input two argv : ip and port\n");
    }
}

void initNetSocket(struct sockaddr_in* c_addr,char** argv,int c_fd)
{
    c_addr->sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    c_addr->sin_port = htons(atoi(argv[2]));    
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&c_addr->sin_addr);
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 客户端连接服务器的ip和端口--在c_addr结构体里配置;
    connect(c_fd,(struct sockaddr *)c_addr,sizeof(struct sockaddr_in));
    //显示要连接的服务器 ip
    //inet_ntoa() 把网络字节序转成int;
    printf("connect sever:%s \n",inet_ntoa(c_addr->sin_addr));
}

void checkSocket(int c_fd)
{
    if(c_fd < 0){
        perror("socket error");
       }
}

void initClientSocket(int argc,int* c_fd,char **argv)
{
    //检测此程序参数数量
    checkArgv(argc);
    //创建套接字,返回套接字ID;
    *c_fd = socket(AF_INET,SOCK_STREAM,0);
    //检测套接字创建
    checkSocket(*c_fd); 
    //定义储存服务器ip和端口的结构体;
    struct sockaddr_in c_addr; 
    //通过套接字,初始化连接,并显示结构体内要连接的服务器ip;
    initNetSocket(&c_addr,argv,*c_fd);
} 

int main(int argc,char** argv)
{   
    int c_fd;
    //init客户端 连接服务端ip;检测main参数,打印当前输入的服务器ip;
    initClientSocket(argc,&c_fd,argv);
    while(1){ 
        //创建管道检测 quit 执行退出父进程;
        int fd[2];
        int n_pipe = pipe(fd); 
        if(n_pipe < 0){
        printf("error: can not creat pipe!\n");
        perror("why");
        } 
        //创建子进程;               
        if(fork() == 0){  
            if(fork() == 0){
                //循环读服务器执行命令的内容
                readx(c_fd);
            }
            //06 write
            while(1){
                char dataStr[1024*5] = {0};                                       
                gets(dataStr);
                int n_write = write(c_fd,dataStr,strlen(dataStr)+1);                
                doCom(dataStr,c_fd,fd);                
            }
        }  
        printf("open Q check -----\n"); 
        //检测quit,一旦无名管道的fd[0]有"QU",就结束父进程;
        if(checkQuit(fd)){                
            break;
        }
    }
    return 0;
}
局域网多方通讯 配合线程实现

进程开辟多 容易崩亏,所以能用线程就用线程;

sever demo:
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
//创建客户端套接字数组
int c_fd[10] = {0};
//创建线程数组;
pthread_t t1[10];
//read用的字符串变量
char buf[1024] = {0};
char buf1[1024] = {0};
int mark = 0;
int count = 0;

void *func2(void* marknum)
{
    int num2 = *((int*)marknum);
    printf("marknum:%d\n",num2);    
    //06 write
    while(1){
        char dataStr[1024*5] = {0};                        
        fgets(dataStr, sizeof(dataStr), stdin);        
        printf("now have :%d\n-----------------\n",count);
        for(int i = 0; i < count; i++){
            write(c_fd[i],dataStr,strlen(dataStr)+1);
        }
    }      
}
void *func1(void* marknum)
{
    int num = *((int*)marknum);
    pthread_create(&t1[*((int*)marknum)+1],NULL,func2,marknum);                
    //05 read
    while(1){
        int n_read = read(c_fd[num],buf,1024);
        if (n_read == 0) {
            // 客户端关闭连接,清空c_fd数组相应位置
            close(c_fd[num]);
            c_fd[num] = 0;
            printf("client %d closed\n", num);
            pthread_cancel(t1[num]); // 结束相应线程
            break;
        }
        if(strcmp(buf,buf1) != 0){
            printf("client massage --no.%d: %s\n",num,buf); 
            memset(buf1,0,1024);
            strcpy(buf1,buf);
            memset(buf,0,1024);
        }
    }
}

int main(int argc,char** argv)
{
    if(argc < 3){
        printf("please input two argv : ip and port\n");
    }
    //int socket(int domain, int type, int protocol);
    //01 创建套接字,返回套接字ID;
    int s_fd = socket(AF_INET,SOCK_STREAM,0);
    if(s_fd < 0){
        perror("socket error:");
        exit(-1);
    }
    struct sockaddr_in c_addr;
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    s_addr.sin_port = htons(atoi(argv[2]));    
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&s_addr.sin_addr);
    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    //int listen(int sockfd, int backlog);
    //03 监听,最多听几次,这里是10次
    listen(s_fd,10);    
    while(1){
        //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        //04 在服务端套接字,接收客户端地址信息,并创建一个新的套接字来处理这些信息;
        //如不在乎客户端的信息,直接NULL,拿了新的套接字空间len也NULL;
        int nsize = sizeof(struct sockaddr_in);
        c_fd[count] = accept(s_fd,(struct sockaddr *)&c_addr,&nsize);
            if(c_fd < 0){
            perror("accept client error");
        }else{
            count++;
            mark++;
            }
        int marknum = mark-1;
        printf("****mark:%d\n",marknum);
        //inet_ntoa() 把网络字节序转成int;
        printf("new connect:%s \n",inet_ntoa(c_addr.sin_addr));
        pthread_create(&t1[marknum],NULL,func1, (void*)&marknum); 
        // if(fork() == 0){
        //     func1((void*)&marknum);
        // }
    }   
    return 0;

}
client demo:
javascript{.line-numbers} 复制代码
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char** argv)
{
    if(argc < 3){
        printf("please input two argv : ip and port\n");
    }
    //int socket(int domain, int type, int protocol);
    //01 创建套接字,返回套接字ID;
    int c_fd = socket(AF_INET,SOCK_STREAM,0);
    if(c_fd < 0){
        perror("socket error:");
        exit(-1);
    }
    struct sockaddr_in c_addr;
    c_addr.sin_family = AF_INET;
    //htons(8989) 把数字转成网络字节序 即大端字节序
    c_addr.sin_port = htons(atoi(argv[2]));    
    //int inet_aton(const char *cp, struct in_addr *inp);
    //这里的ip是服务器本机的地址 ifconfig查看即可
    inet_aton(argv[1],&c_addr.sin_addr);
    //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 把结构体addr里的 tcp+ip+端口,与套接字绑定在一起;
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //02 客户端连接服务器的ip和端口;
    connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));
    //inet_ntoa() 把网络字节序转成int;
    printf("connect sever:%s \n",inet_ntoa(c_addr.sin_addr)); 
    while(1){
         
                if(fork() == 0){                    
                    //06 write
                    while(1){
                        char dataStr[1024*5] = {0};                        
                        gets(dataStr);
                        int n_write = write(c_fd,dataStr,strlen(dataStr)+1); 
                    }
                }                
                //05 read
                char buf[1024] = {0};
                char buf1[1024] = {0};
                while(1){
                    int n_read = read(c_fd,buf,1024);
                    if(strcmp(buf,buf1) != 0){
                        printf("server massage:%s  (size:%d)\n",buf,n_read); 
                        memset(buf1,0,1024);
                        strcpy(buf1,buf);
                        memset(buf,0,1024);
                    }
                }                 
    }
    return 0;
}
相关推荐
律宏阔5 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔5 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴5 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千5 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei375 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei375 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei375 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
GeW5 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
GeW6 小时前
工业场景中的Linux与数据库选型:高端化转型的技术底座
linux
yunwei376 小时前
eBPF 开发者教程: 简单的 XDP 负载均衡器
linux·后端·性能优化