| 高士涛 - 学习日报 | |||||||
|---|---|---|---|---|---|---|---|
| 姓名 | 高士涛 | 日期 | 2023/05/16 | 部门 | 云服务业务部 | 导师 | 王晓明 |
| 学习工作内容 | |||||||
| 基于多进程SERVER的TCP连接 | |||||||
① 服务器 server 运行区;
② 客户端 client(1) 运行区;
③ 客户端 client(2) 运行区;
④ 各进程状态观测与控制区。

尝试使用多台client同时与server连接,可以看到一直只有一个server进程在运行。

(1)客户端client程序(保持不变):
xxxxxxxxxx// for sockaddr_in// for socket// for socket// for printf// for exit// for bzero
int main(int argc, char **argv){ if (argc != 2) // 判断有没有输入服务器ip { printf("注意使用方法: %s ServerIPAddress\n", argv[0]); exit(1); }
// 设置一个socket地址结构client_addr, 代表客户机的ip地址和端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; // internet协议族IPv4 client_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); // auto allocated, 让系统自动分配一个空闲端口
// 创建用于internet的流协议(TCP)类型socket,用client_socket代表客户端socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } else printf("Create Socket Success.\n");
// 把客户端的socket和客户端的socket地址结构绑定 if (bind(client_socket, (struct sockaddr *)&client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); } else printf("Client Bind Port Success.\n");
// 设置一个socket地址结构server_addr,代表服务器的internet地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET;
// 服务器的IP地址来自程序的参数 if (inet_aton(argv[1], &server_addr.sin_addr) == 0) { printf("Server IP Address Error!\n"); exit(1); }
server_addr.sin_port = htons(SERVER_PORT); int server_addr_length = sizeof(server_addr);
// 向服务器发起连接请求,连接成功后client_socket代表客户端和服务器端的一个socket连接 if (connect(client_socket, (struct sockaddr *)&server_addr, server_addr_length) < 0) { printf("Can Not Connect To %s!\n", argv[1]); exit(1); } else printf("Alreadly Connect To %s.\n", argv[1]);
char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); printf("Please Input File Name On Server: "); scanf("%s", file_name);
char buffer[BUFFER_SIZE]; // 缓存区 bzero(buffer, sizeof(buffer)); strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name)); // 向服务器发送buffer中的数据,此时buffer中存放的是客户端需要接收的文件的名字 send(client_socket, buffer, BUFFER_SIZE, 0); // send , sendto(), recv(),recvfrom() FILE *fp = fopen(file_name, "w"); if (fp == NULL) { printf("File: %s Can Not Open To Write!\n", file_name); exit(1); }
// 从服务器端接收数据到buffer中 bzero(buffer, sizeof(buffer)); int length = 0; while (length = recv(client_socket, buffer, BUFFER_SIZE, 0)) { if (length < 0) { printf("Recieve Data From Server %s Failed!\n", argv[1]); break; }
int write_length = fwrite(buffer, sizeof(char), length, fp); if (write_length < length) { printf("File:\t%s Write Failed!\n", file_name); break; } bzero(buffer, BUFFER_SIZE); }
printf("Recieve File: %s From Server[%s] Finished!\n", file_name, argv[1]);
// 传输完毕,关闭socket fclose(fp); close(client_socket); return 0;}
(2)服务端server程序(调用fork函数):
xxxxxxxxxx
// 端口号
int main(int argc, char **argv){ // 设置一个socket地址结构server_addr,代表服务器ip地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); // 每个字节都用0填充 server_addr.sin_family = AF_INET; // 使用IPv4地址 server_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。表示监听所有的IP地址 server_addr.sin_port = htons(SERVER_PORT); // 端口
// 创建用于流协议(TCP)socket,用server_socket代表服务器向客户端提供服务的接口 int server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } else printf("Create Socket Success.\n");
// 把socket和socket地址结构绑定 if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) { printf("Server Bind Port: %d Failed!\n", SERVER_PORT); exit(1); } else printf("Client Bind Port Success.\n");
// server_socket用于监听,进入监听状态,等待用户发起请求 if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE)) { printf("Server Listen Failed!\n"); exit(1); } else printf("Listening....\n");
// 服务器始终监听 while (1) { // 定义客户端的socket地址结构client_addr,当收到来自客户端的请求后,调用accept // 接受此请求,同时将client端的地址和端口等信息写入client_addr中 struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr);
// 接受一个从client端到达server端的连接请求,将客户端的信息保存在client_addr中 // 如果没有连接请求,则一直等待直到有连接请求为止,这是accept函数的特性 // accpet返回一个新的socket,这个socket用来与此次连接到server的client进行通信 // 这里的new_server_socket代表了这个通信通道 int new_server_socket = accept(server_socket, (struct sockaddr *)&client_addr, &length); if (new_server_socket < 0) { printf("Server Accept Failed!\n"); break; } else printf("Server Accept Success.\n");
// 使用fork()函数创建一个子进程,用于与客户端进行文件传输 int pid = fork(); if (pid < 0) { perror("创建子进程失败!\n"); return -1; } else if (pid == 0) { // 子进程中 close(server_socket); // 关闭监听套接字 char buffer[BUFFER_SIZE]; bzero(buffer, sizeof(buffer)); length = recv(new_server_socket, buffer, BUFFER_SIZE, 0); if (length < 0) { printf("Server Recieve Data Failed!\n"); break; } else printf("Server Recieve Data Success.\n");
char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, sizeof(file_name)); strncpy(file_name, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));
FILE *fp = fopen(file_name, "r"); // 获取文件操作符 if (fp == NULL) { printf("File:\t%s Not Found!\n", file_name); } else { bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while ((file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { // 发送buffer中的字符串到new_server_socket,实际上就是发送给客户端 if (send(new_server_socket, buffer, file_block_length, 0) < 0) { printf("Send File:\t%s Failed!\n", file_name); break; }
bzero(buffer, sizeof(buffer)); } fclose(fp); printf("File:\t%s Transfer Finished!\n", file_name); } // printf("----------------------------------------------------------------\n"); // printf("成功创建子进程。(这是子进程的代码区) \t\tpid = %d, ppid = %d\n", getpid(), getppid()); // printf("----------------------------------------------------------------\n\n"); close(new_server_socket); // 关闭与客户端的连接套接字 exit(0); // 子进程退出 } else { // 父进程中 close(new_server_socket); // 关闭与客户端的连接套接字,继续监听其他客户端的连接请求 // printf("================================================================\n"); // printf("这是父进程。(这是父进程的代码区)。cpid = %d, pid = %d, ppid = %d\n", pid, getpid(), getppid()); // printf("================================================================\n\n"); } }
close(server_socket);
return 0;}
(1)可以看到,两台客户端分别连接到服务端的不同进程:

(2)查看server父进程与子进程的进程号关系:

