课上测试:网络编程
一、实验要求
在 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 255
int 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);
// 创建socket
listener_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 255
int 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);
// 创建socket
listener_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