【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;
}
相关推荐
速盾cdn2 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211232 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin2 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
搬砖的小码农_Sky6 小时前
C语言:数组
c语言·数据结构
朝九晚五ฺ6 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream6 小时前
Linux的桌面
linux
xiaozhiwise7 小时前
Makefile 之 自动化变量
linux
意疏9 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker