Linux网络编程

1、使用网络编程的原因

之前我们学习的进程通信是依赖于内核,我们只能在本机上进行通信,无法进行多机通信。而我们的网络编程就实现了多机通信。

网络编程:通过ip地址和端口号来确定设备地址(例:ip告诉你是那台电脑,端口号告诉你是那个软件)

 2、协议(http、tcp、udp)

  • tcp协议:面向连接、是可靠连接、适合精细操作
  • udp协议:面向报文、连接不可靠、适合传输数据大

2.1 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则是不可靠信道

提示:Linux应用使用端口号一般用5000-10000

3、字节序

3.1 字节序的介绍

字节序(Byte Order)指的是在多字节数据类型(如整数、浮点数)在存储或传输时字节的顺序。在计算机中,常见的字节序有两种:大端序(Big-Endian)和小端序(Little-Endian)。

  1. 大端序(Big-Endian):

    • 在大端序中,数据的高位字节存储在低地址(也就是存储在前面),低位字节存储在高地址。
    • 例如,十六进制数 0x1234 在大端序中存储为 0x12 0x34
  2. 小端序(Little-Endian):

    • 在小端序中,数据的低位字节存储在低地址,高位字节存储在高地址。
    • 例如,十六进制数 0x1234 在小端序中存储为 0x34 0x12

网络字节序一般是大端字节序

例:在内存中双字0x01020304(DWORD)的存储方式 

 3.2 字节序转换api

#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指定地址让操作系统自己获取

在使用这些函数时,通常在发送数据之前使用 htonlhtons 进行转换,而在接收数据时使用 ntohlntohs 进行转换,以确保在不同主机之间的正确数据传输

 4、socket服务器和客户端的开发步骤

注意这里读写是可以相互的 

 4.1 api介绍

1.创建套接字函数socket

用于创建套接字(socket),建立网络连接,进行数据传输等

int socket(int domain, int type, int protocol);

 2. 添加IP地址和端口号信息函数bind

 

struct sockaddr_in 结构体的位置在

cd /usr/include/

grep "struct sockaddr_in {" * -nir    //查找结构体的位置,n显示行号,i不区分大小写,r递归查找 

 注意在使用这个函数时IP地址和端口号都要进行转换

端口号一般情况要进行字节序转换,IP地址要用inet_aton转换

3.地址转换api

int inet_aton(const char* straddr,struct in_addr *addrp);

把字符串形式的“192.168.1.123”转为网络能识别的格式

char* inet_ntoa(struct in_addr inaddr);

把网络格式的ip地址转为字符串形式

4.监听函数listen

5.连接函数accept

 6.数据的收发

 常用的第二套api

 7.客户端的connect函数

8. 示例:实现客户端与服务端的功能

分为3个文件进行写

1.收发的数据结构
#define SET_LS   1
#define SET_CD   2
#define SET_PUT  3
#define SET_GET  4
#define SET_PWD  5
#define SET_QUIT 6
#define SET_LPUT 7
#define SET_LLS  8
#define SET_LCD  9
#define SET_LPWD 10

struct  send_data {
        int  data_type;
        char data_zl[128];
        char data[1024];
};
 2.server服务器端
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include "cnashu.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>

// 函数声明
char *cmd_strtok(char *cmd);
void handle_client(int accept_fd);

int main(int argc, char **argv) {
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <IP> <Port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sockfd, bind_ret, listen_ret, accept_fd;
    struct sockaddr_in s_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址结构
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1], &s_addr.sin_addr);

    // 将套接字与地址绑定
    bind_ret = bind(sockfd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr));
    if (bind_ret == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    listen_ret = listen(sockfd, 10);
    if (listen_ret == -1) {
        perror("listen");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    int count = 0;
    int leng = sizeof(struct sockaddr);

    while (1) {
        // 接受客户端连接
        accept_fd = accept(sockfd, (struct sockaddr *)&s_addr, &leng);
        if (accept_fd == -1) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        count++;
        printf("Client %d Connected. IP: %s\n", count, inet_ntoa(s_addr.sin_addr));

        // 处理客户端请求的逻辑
        handle_client(accept_fd);
    }

    // 注意:这里未关闭 sockfd,因为服务器一般在无法继续服务时才关闭
    return 0;
}

// 函数:处理客户端请求的逻辑
void handle_client(int accept_fd) {
    pid_t fork_fd;
    int order;
    FILE *fd = NULL;
    char *cmd = NULL;
    struct send_data data;

    while (1) {
        memset(&data, 0, sizeof(struct send_data));
        // 读取客户端发送的数据
        ssize_t read_ret = read(accept_fd, &data, sizeof(struct send_data));
        if (read_ret == -1) {
            perror("read");
            // 处理错误逻辑,如关闭连接等
        }

        // 解析命令
        if (strcmp(data.data_zl, "ls") == 0) {
            order = SET_LS;
        } else if (strcmp(data.data_zl, "pwd") == 0) {
            order = SET_PWD;
        } else if (strstr(data.data_zl, "cd") != NULL) {
            order = SET_CD;
        } else if (strstr(data.data_zl, "put") != NULL || data.data_type == 2) {
            order = SET_PUT;
        } else if (strstr(data.data_zl, "get") != NULL) {
            order = SET_GET;
        } else if (strstr(data.data_zl, "quit") != NULL) {
            order = SET_QUIT;
        } else {
            order = SET_UNKNOWN;  // 未知命令
        }

        switch (order) {
            case SET_LS:
            case SET_PWD:
                // 执行命令并返回结果给客户端
                fd = popen(data.data_zl, "r");
                fread(data.data, sizeof(data.data), 1, fd);
                write(accept_fd, &data, sizeof(struct send_data));
                break;
            case SET_CD:
                // 执行 cd 命令
                cmd = cmd_strtok(data.data_zl);
                if (chdir(cmd) == -1) {
                    perror("chdir");
                }
                strcpy(data.data, "Directory changed successfully.\n");
                write(accept_fd, &data, sizeof(struct send_data));
                break;
            case SET_GET:
                // 处理客户端请求文件的逻辑
                cmd = cmd_strtok(data.data_zl);
                int open_fd = open(cmd, O_RDWR);
                if (open_fd == -1) {
                    perror("open");
                    // 处理文件不存在等错误,向客户端发送错误信息
                    strcpy(data.data, "File not found.\n");
                    write(accept_fd, &data, sizeof(struct send_data));
                } else {
                    // 读取文件内容并发送给客户端
                    if (read(open_fd, data.data, sizeof(data.data)) == -1) {
                        perror("read");
                        // 处理读取文件错误,向客户端发送错误信息
                        strcpy(data.data, "Error reading file.\n");
                        write(accept_fd, &data, sizeof(struct send_data));
                    } else {
                        // 发送文件内容给客户端
                        write(accept_fd, &data, sizeof(struct send_data));
                    }
                    close(open_fd);
                }
                break;
            case SET_PUT:
                // 处理客户端上传文件的逻辑
                cmd = cmd_strtok(data.data_zl);
                int exist_check_fd = open(cmd, O_RDONLY);
                if (exist_check_fd != -1) {
                    // 文件已存在,向客户端发送错误信息
                    close(exist_check_fd);
                    strcpy(data.data, "File already exists.\n");
                    write(accept_fd, &data, sizeof(struct send_data));
                } else {
                    // 打开文件并写入客户端上传的数据
                    int write_fd = open(cmd, O_WRONLY | O_CREAT, 0666);
                    write(write_fd, data.data, sizeof(data.data));
                    close(write_fd);
                    strcpy(data.data, "File uploaded successfully.\n");
                    write(accept_fd, &data, sizeof(struct send_data));
                }
                break;
            case SET_QUIT:
                // 处理客户端退出请求的逻辑
                printf("Client %d Disconnected.\n", accept_fd);
                close(accept_fd);
                exit(EXIT_SUCCESS);
                break;
            case SET_UNKNOWN:
                // 未知命令,向客户端发送错误信息
                strcpy(data.data, "Unknown command.\n");
                write(accept_fd, &data, sizeof(struct send_data));
                break;
            default:
                // 其他错误情况,向客户端发送错误信息
                strcpy(data.data, "Error.\n");
                write(accept_fd, &data, sizeof(struct send_data));
        }
    }
}

// 函数:解析命令中的参数
char *cmd_strtok(char *cmd) {
    char *p = NULL;
    p = strtok(cmd, " ");
    p = strtok(NULL, " ");
    return p;
}
3. client客户端代码
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "cnashu.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>

// 函数声明
int sock_cmd_type(char *cmd);
char *cmd_strtok(char *cmd);

int main(int argc, char **argv) {
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <IP> <Port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sockfd, connect_ret, order;
    struct sockaddr_in s_addr;
    struct send_data w_data, r_data;
    char *file = NULL;
    char buf[128] = {0};
    int open_fd;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址结构
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));
    inet_aton(argv[1], &s_addr.sin_addr);

    // 连接服务器
    connect_ret = connect(sockfd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr));
    if (connect_ret == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    while (1) {
        // 初始化发送和接收数据的结构
        memset(&w_data, 0, sizeof(struct send_data));
        memset(&r_data, 0, sizeof(struct send_data));

        printf(">");
        gets(w_data.data_zl);

        // 获取命令类型
        order = sock_cmd_type(w_data.data_zl);

        switch (order) {
            case SET_LS:
            case SET_PWD:
            case SET_CD:
            case SET_GET:
                // 向服务器发送命令
                write(sockfd, &w_data, sizeof(struct send_data));

                // 接收服务器的响应
                read(sockfd, &r_data, sizeof(struct send_data));

                // 输出服务器响应的数据
                printf("\n--------------server-------------\n");
                printf("%s", r_data.data);
                printf("--------------------------------\n");
                break;
            case SET_PUT:
                memset(buf, 0, 128);
                strcpy(buf, w_data.data_zl);
                file = cmd_strtok(buf);

                // 检查文件是否存在
                if (access(file, F_OK) == -1) {
                    printf("\n--------------server-------------\n");
                    printf("File does not exist.\n");
                    printf("--------------------------------\n");
                    break;
                }

                open_fd = open(file, O_RDWR | O_EXCL);
                if (open_fd == -1) {
                    printf("\n--------------server-------------\n");
                    printf("File is already open.\n");
                    printf("--------------------------------\n");
                    break;
                }

                // 读取文件内容并发送给服务器
                if (read(open_fd, w_data.data, sizeof(w_data.data)) == -1) {
                    perror("read");
                }
                w_data.data_type = 2;
                write(sockfd, &w_data, sizeof(struct send_data));
                close(open_fd);

                // 接收服务器的响应
                read(sockfd, &r_data, sizeof(struct send_data));

                // 输出服务器响应的数据
                printf("\n--------------server-------------\n");
                printf("%s", r_data.data);
                printf("--------------------------------\n");
                break;
            case SET_LCD:
            case SET_LLS:
            case SET_LPWD:
                // 向服务器发送命令
                write(sockfd, &w_data, sizeof(struct send_data));
                break;
            case SET_QUIT:
                exit(0);
                break;
            default:
                printf("Unknown command.\n");
        }
    }

    close(sockfd);
    return 0;
}

// 函数:获取命令类型
int sock_cmd_type(char *cmd) {
    if (strcmp(cmd, "lls") == 0) return SET_LLS;
    if (strcmp(cmd, "lpwd") == 0) return SET_LPWD;
    if (strcmp(cmd, "quit") == 0) return SET_QUIT;
    if (strcmp(cmd, "ls") == 0) return SET_LS;
    if (strcmp(cmd, "pwd") == 0) return SET_PWD;
    if (strstr(cmd, "lcd") != NULL) return SET_LCD;
    if (strstr(cmd, "cd") != NULL) return SET_CD;
    if (strstr(cmd, "get") != NULL) return SET_GET;
    if (strstr(cmd, "put") != NULL) return SET_PUT;

    return 100; // 未知命令
}

// 函数:解析命令中的参数
char *cmd_strtok(char *cmd) {
    char *p = NULL;
    p = strtok(cmd, " ");
    p = strtok(NULL, " ");
    return p;
}
  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值