课上测试:网络编程
一、实验要求
-
在 Ubuntu 或 openEuler 中完成任务(推荐openEuler)
-
参考《head first C》实现knock knock服务器,提交代码knock.c,编译运行过程(3分)
-
使用多线程实现knock knock服务器,提交代码knockmt.c,编译运行过程,至少两个客户端测试,服务器运行结果中要打印线程id(10分)
-
提交git log结果(1分)
二、实验过程
任务一:单线程Knock-Knock服务器实现
knock.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>#define PORT 30000
#define BUFFER_SIZE 255int listener_d;void error(const char *msg) {perror(msg);exit(1);
}void handle_shutdown(int sig) {if (listener_d) close(listener_d);fprintf(stderr, "\nServer shutdown.\n");exit(0);
}int read_in(int socket, char *buf, int len) {char *s = buf;int slen = len;int c = recv(socket, s, slen, 0);while ((c > 0) && (s[c-1] != '\n')) {s += c;slen -= c;c = recv(socket, s, slen, 0);}if (c < 0) return c;else if (c == 0) buf[0] = '\0';else s[c-1] = '\0';return len - slen;
}int main() {signal(SIGINT, handle_shutdown);// 创建socketlistener_d = socket(PF_INET, SOCK_STREAM, 0);if (listener_d == -1) error("Can't open socket");// 绑定端口struct sockaddr_in name;name.sin_family = PF_INET;name.sin_port = htons(PORT);name.sin_addr.s_addr = htonl(INADDR_ANY);int reuse = 1;if (setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) error("Can't set reuse");if (bind(listener_d, (struct sockaddr *)&name, sizeof(name)) == -1)error("Can't bind");// 监听if (listen(listener_d, 10) == -1) error("Can't listen");printf("Server running on port %d...\n", PORT);while (1) {struct sockaddr_storage client_addr;unsigned int address_size = sizeof(client_addr);int connect_d = accept(listener_d, (struct sockaddr *)&client_addr, &address_size);if (connect_d == -1) error("Can't accept connection");// 处理客户端请求char buf[BUFFER_SIZE];send(connect_d, "Knock! Knock!\n> ", 16, 0);read_in(connect_d, buf, BUFFER_SIZE);if (strncasecmp("Who's there?", buf, 12)) {send(connect_d, "Protocol error: Expected 'Who's there?'\n", 40, 0);} else {send(connect_d, "Oscar\n> ", 8, 0);read_in(connect_d, buf, BUFFER_SIZE);if (strncasecmp("Oscar who?", buf, 10)) {send(connect_d, "Protocol error: Expected 'Oscar who?'\n", 38, 0);} else {send(connect_d, "Oscar silly question, you get a silly answer!\n", 46, 0);}}close(connect_d);}return 0;
}
编译运行结果:
服务器运行:
客户端输入:
任务二:多线程Knock-Knock服务器实现
knockmt.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>#define PORT 30000
#define BUFFER_SIZE 255int listener_d;void error(const char *msg) {perror(msg);exit(1);
}void handle_shutdown(int sig) {if (listener_d) close(listener_d);fprintf(stderr, "\nServer shutdown.\n");exit(0);
}int read_in(int socket, char *buf, int len) {char *s = buf;int slen = len;int c = recv(socket, s, slen, 0);while ((c > 0) && (s[c-1] != '\n')) {s += c;slen -= c;c = recv(socket, s, slen, 0);}if (c < 0) return c;else if (c == 0) buf[0] = '\0';else s[c-1] = '\0';return len - slen;
}void* handle_client(void *arg) {int connect_d = *(int *)arg;char buf[BUFFER_SIZE];pthread_t tid = pthread_self();printf("Thread %lu handling client\n", tid);send(connect_d, "Knock! Knock!\n> ", 16, 0);read_in(connect_d, buf, BUFFER_SIZE);if (strncasecmp("Who's there?", buf, 12)) {send(connect_d, "Protocol error: Expected 'Who's there?'\n", 40, 0);} else {send(connect_d, "Oscar\n> ", 8, 0);read_in(connect_d, buf, BUFFER_SIZE);if (strncasecmp("Oscar who?", buf, 10)) {send(connect_d, "Protocol error: Expected 'Oscar who?'\n", 38, 0);} else {send(connect_d, "Oscar silly question, you get a silly answer!\n", 46, 0);}}close(connect_d);free(arg);return NULL;
}int main() {signal(SIGINT, handle_shutdown);// 创建socketlistener_d = socket(PF_INET, SOCK_STREAM, 0);if (listener_d == -1) error("Can't open socket");// 绑定端口struct sockaddr_in name;name.sin_family = PF_INET;name.sin_port = htons(PORT);name.sin_addr.s_addr = htonl(INADDR_ANY);int reuse = 1;if (setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)))error("Can't set reuse");if (bind(listener_d, (struct sockaddr *)&name, sizeof(name)) == -1)error("Can't bind");// 监听if (listen(listener_d, 10) == -1) error("Can't listen");printf("Multithreaded server running on port %d...\n", PORT);while (1) {struct sockaddr_storage client_addr;unsigned int address_size = sizeof(client_addr);int *connect_d = malloc(sizeof(int));*connect_d = accept(listener_d, (struct sockaddr *)&client_addr, &address_size);if (*connect_d == -1) error("Can't accept connection");pthread_t thread;if (pthread_create(&thread, NULL, handle_client, connect_d) != 0) {perror("Failed to create thread");close(*connect_d);free(connect_d);}}return 0;
}
编译运行结果
运行knockmt,打开新的terminal去访问server,进行如下命令及对话:
telnet 127.0.0.1 30000
> Knock! Knock!
> Who's there?
> Oscar
> Oscar who?
> Oscar silly question, you get a silly answer!
客户端得到服务器应答
多运行几个客户端之后,查看服务器界面,发现服务器端打印了线程id。每个客户端连接由独立线程处理,通过 pthread_self() 打印线程ID,支持并发访问。
任务三:Gitee仓库管理
仓库链接:https://gitee.com/li-zhen1215/homework/tree/master/Week8/keshang4