1.使用多进程并发处理多个client请求
网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。
但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程。
如果每次连上一个客户端,都创建一个新进程,然后操作完了,就结束子进程,实在太麻烦,而且系统开销很大。
因此我们可以使用线程,相比于进程,要轻量级的多。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
#define SERV_PORT 8000
#define MAXLINE 80
#define prrexit(msg) perror(msg); exit(1);
void *up_server(void *arg) {
int connfd = (int)arg;
char buf[MAXLINE];
pthread_detach(pthread_self());
//多线程
pthread_t tid;
pthread_create(&tid, NULL, up_server, (void *)connfd);
printf("new thread is %#lx\n", tid);
while (1) {
int n = read(connfd, buf, MAXLINE);
if (!strncmp(buf, "quit", 4)) break;
write(1, buf, n);
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
}
close(connfd);
return (void *)0;
}
int main(void) {
struct sockaddr_in serveraddr, clientaddr;
int listenfd, connfd;
socklen_t clientaddr_len;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) prrexit("socket");
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERV_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) prrexit("bind");
if (listen(listenfd, 3) < 0) prrexit("listend");
printf("Accepting connections...\n");
while (1) {
clientaddr_len = sizeof(clientaddr);
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
if (connfd < 0) prrexit("accept");
printf("Received from %s:%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)), ntohs(clientaddr.sin_port));
/*pid_t pid = fork();
if (pid < 0) prrexit("fork");
if (pid > 0) {
close(connfd);
while (waitpid(-1, NULL, WNOHANG) > 0) {}
continue;
}
close(listenfd);*/
while (1) {
int n = read(connfd, buf, MAXLINE);
if (!strncmp(buf, "quit", 4)) break;
write(1, buf, n);
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
}
close(connfd);
}
return 0;
}
2.基于UDP协议的网络程序
UDP客户端/服务器通讯过程:
server:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
#define SERV_PORT 8000
#define MAXLINE 80
#define prrexit(msg) perror(msg); exit(1);
int main(void) {
int sockfd;
struct sockaddr_in servaddr, cliaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("udpserver ready~\n");
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
while (1) {
int n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (n < 0) prrexit("recvfrom");
printf("receive from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (int i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
}
sendto(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
}
close(sockfd);
return 0;
}
client:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#define SERV_PORT 8000
#define MAXLINE 80
int main(void) {
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
int n;
while(n = read(sockfd, buf, MAXLINE)) {
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
write(1, buf, n);
}
close(sockfd);
return 0;
}
3.使用线程池并发处理
可以创建很多线程池竞争得到客户端服务任务并之执行操作。
线程池:所有线程去抢夺资源。
4.使用epoll+线程池并发处理多个
epoll:通过注册callback函数方式,当某个文件描述符发送变化时,就会主动通知。
server:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/epoll.h>
#define SERV_PORT 8000
#define MAXLINE 80
#define prrexit(msg) perror(msg); exit(1);
typedef struct Task
{
int fd;
struct Task *next;
}Task;
typedef struct Task_pool {
Task *head;
Task *tail;
pthread_mutex_t lock;
pthread_cond_t havetask;
}Task_pool;
Task_pool *task_pool_init() {
Task_pool *tp = (Task_pool *)malloc(sizeof(Task_pool));
tp->head = NULL;
tp->tail = NULL;
pthread_mutex_init(&tp->lock, NULL);
pthread_cond_init(&tp->havetask, NULL);
return tp;
}
void task_pool_push(Task_pool *tp, int fd) {
pthread_mutex_lock(&tp->lock);
Task *t = (Task *)malloc(sizeof(Task));
t->fd = fd;
t->next = NULL;
if (!tp->tail) {
tp->head = tp->tail = t;
} else {
tp->tail->next = t;
tp->tail = t;
}
pthread_cond_broadcast(&tp->havetask);
pthread_mutex_unlock(&tp->lock);
}
Task task_pool_pop(Task_pool *tp) {
pthread_mutex_lock(&tp->lock);
while (tp->head == NULL) {
pthread_cond_wait(&tp->havetask, &tp->lock);
}
Task tmp, *k;
k = tp->head;
tmp = *k;
tp->head = tp->head->next;
if (!tp->head) tp->tail = NULL;
free(k);
pthread_mutex_unlock(&tp->lock);
return tmp;
}
void task_pool_free(Task_pool *tp) {
pthread_mutex_lock(&tp->lock);
Task *p = tp->head, *k;
while (p) {
k = p;
p = p->next;
free(k);
}
tp->head = NULL;
pthread_mutex_unlock(&tp->lock);
pthread_mutex_destroy(&tp->lock);
pthread_cond_destroy(&tp->havetask);
free(tp);
return;
}
void *up_server(void *arg) {
pthread_detach(pthread_self());
char buf[MAXLINE];
int n, i;
Task_pool *tp = arg;
while (1) {
Task tmp = task_pool_pop(tp);
int connfd = tmp.fd;
printf("get task fd=%d\n", connfd);
if (1) {
n = read(connfd, buf, MAXLINE);
write(1, buf, n);
for (i = 0; i <n; i++) {
buf[i] = toupper(buf[i]);
}
write(connfd, buf, n);
}
printf("finish task fd=%d\n", connfd);
if (!strncmp(buf, "quit", 4)) {
close(connfd);
}
}
return (void *)0;
}
int main(void) {
int sockfd;
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
Task_pool *tp = task_pool_init();
//多线程
pthread_t tid;
for (int i = 0; i < 4; i++) {
pthread_create(&tid, NULL, up_server, (void *)tp);
printf("new thread is %#lx\n", tid);
}
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) prrexit("socket");
int epfd = epoll_create(256);
struct epoll_event ev, events[256];
//服务器IP地址:端口初始化
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) prrexit("bind");
if (listen(listenfd, 2) < 0) prrexit("listen");
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
printf("Accepting connections...\n");
while (1) {
int nfds = epoll_wait(epfd, events, 256, -1);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == listenfd) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (connfd < 0) prrexit("accept");
printf("receive from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
//task_pool_push(tp, connfd);
} else if (events[i].events & EPOLLIN) {
int clifd = events[i].data.fd;
if (clifd < 3) continue;
task_pool_push(tp, clifd);
}
}
}
task_pool_free(tp);
return 0;
}
5.HTTP协议
超文本传输协议(HyperText Trasnsfer Protocol)是一种用于分布式、协作式和超媒体信息系统的应用层协议。
HTTP是万维网的数据通信的基础。
①HTTP协议采用了请求/响应模型。
②无状态保存
HTTP是一种不保存状态,无状态协议。
HTTP协议自身不对请求和响应之间的通信状态进行报错。在HTTP这个级别,协议对于发送过的请求或响应都不做持久化处理。
③无连接
限制每次连接只处理一个请求。
服务器处理完客户的请求,并收到客户的应答后,即断开连接。
采用这种方式可节省传输时间并提高并发性能,不能和每个用户建立长久连接,请求一次响应一次,服务端和客户端就中断了。
6.HTTP请求方法
HTTP/1.1协议中共定义8种方法(动作)来以不同方式操作指定的资源:
①GET
向指定的资源发出“显示”请求。
②POST
向指定资源提交数据,请求服务器进行处理(如:提交表单或者上传文件)。
③HEAD
与GET一样,都是向服务器发出指定资源的请求。但服务器将不传回资源的文本部分。
④PUT
向指定资源位置上传其最新内容。
⑤DELETE
请求服务器删除Request-URI所标识的资源。
⑥TRACE
回显服务器收到的请求,主要用于测试或诊断。
⑦OPTIONS
可使服务器传回该资源所支持的所有HTTP请求方法。
⑧CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
7.HTTP状态码
所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码以及描述状态的短语,彼此由空格分隔。
8.URL
超文本传输协议HTTP的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:
9.HTTP请求格式
10.执行CGI程序
如果浏览器请求的是一个可执行文件(不管是什么样的可执行文件,即使shell脚本也一样),服务器并不把这个文件本身发给浏览器,而是把它执行结果标准输出发给浏览器。
web浏览器工作流程:
①解析浏览器的请求,在服务目录中查找相应文件,找不到该文件就返回404错误页面
②如果找到浏览器请求的文件,用stat(2)检查它是否可执行
③如果该文件可执行:
a.发送HTTP/1.1 200 OK给客户端
b.fork(2),用dup2(2)重定向子进程的标准输出到客户端socket
c.在子进程中exec(3)该CGI程序
d.关闭连接
④如果改文件不可执行:
a.发送HTTP/1.1 200 OK给客户端
b.如果是一个图片文件,根据图片的扩展名发送相应的Content-type
c.如果不是图片文件,做简化处理,都当作Content-Type:text
d.简单的HTTP协议头有这两行就足够,再发一个空行表示结束
e.读取文件的内容发送到客户端
f.关闭连接
|