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)。
-
大端序(Big-Endian):
- 在大端序中,数据的高位字节存储在低地址(也就是存储在前面),低位字节存储在高地址。
- 例如,十六进制数
0x1234
在大端序中存储为0x12 0x34
。
-
小端序(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指定地址让操作系统自己获取
在使用这些函数时,通常在发送数据之前使用
htonl
、htons
进行转换,而在接收数据时使用ntohl
、ntohs
进行转换,以确保在不同主机之间的正确数据传输
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;
}