1. 概览
客户/服务器,交互过程包含下列3个操作:
服务器设立服务
客户链接到服务器
服务器和客户处理事务
1.1 操作1,建立服务器端socket
创建一个socket
socket = socket(PF_INET,SOCK_STREAN,0);
给socket绑定一个地址
bind(sock,&addr,sizeof(addr))
监听接入请求
listen(sock,queue_size);
将上述三个步骤合成一个函数: make_serve_socket
,调用该函数就可以创建一个服务器端socket: sock = make_serve_socket(int portnum);
1.2 建立到服务器端的链接
创建一个socket
socket = socket(PF_INET,SOCK_STREAM,0)
使用socket连接到服务器
connect(sock,&serv_addr,sizeof(serv_addr));
将上述两步抽象为一个函数,connetc_to_server
,只要调用该函数就可以建立到服务器的链接: fd = connect_to_server(hostname,portnum);
1.3 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#define HOSTLEN 256
#define BACKLOG 1
int make_server_socket_q(int,int);
int make_server_socket(int portnum){
return make_server_socket_q(portnum,BACKLOG);
}
int make_server_socket_q(int portnum,int backlog){
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id;
sock_id = socket(PF_INET,SOCK_STREAM,0);
if (sock_id == -1)
return -1;
bzero((void *)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp = gethostbyname(hostname);
bcopy((void *)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
saddr.sin_port = htons(portnum); // why ?
saddr.sin_family = AF_INET; // AF_INET IPv4 Internet protocols ip(7)
if (bind(sock_id,(struct sockaddr *)&saddr, sizeof(saddr)) != 0)
return -1;
if (listen(sock_id,backlog) != 0)
return -1;
return sock_id;
}
int connect_to_server(char *host,int portnum){
int sock;
struct sockaddr_in servadd;
struct hostent *hp;
sock = socket(AF_INET,SOCK_STREAM,0);
if (sock == -1)
return -1;
bzero(&servadd,sizeof(servadd));
hp = gethostbyname(host);
if (hp == NULL)
return -1;
bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
servadd.sin_port = htons(portnum);
servadd.sin_family = AF_INET;
if (connect(sock,(struct sockaddr*)&servadd, sizeof(servadd)) != 0)
return -1;
return sock;
}
1.4 操作3: 客户/服务器的会话
一般的客户端
1
2
3
4
5
6
7
8
main(){
int fd;
fd = connect_to_server(host,port);
if (fd == -1)
exit(1);
talk_with_server(fd);
closed(fd);
}
一般的服务器端
1
2
3
4
5
6
7
8
9
10
11
12
13
main(){
int sock,fd;
sock = make_server_socket(port);
if (sock == -1)
exit(1);
while(1){
fd = accept(sock,NULL,NULL);
if (fd == -1)
break;
process_request(fd);
close(fd);
}
}
1.5 利用客户/服务器框架,重写 timeserv/timeclnt
1
2
3
4
5
6
7
talk_with_server(fd){
char buf[LEN];
int n;
n = read(fd,buf,LEN);
write(1,buf,n);
}
1
2
3
4
5
6
7
process_request(fd){
time_t now;
char *cp;
time(&now);
cp = ctime(&now);
write(fd,cp,strlen(cp));
}
调用fork实现服务器端
1
2
3
4
5
6
7
8
9
10
process_request(fd){
int pid = fork();
switch(pid){
case -1:return;
case 0: dup2(fd,1);
close(fd);
execl("/bin/date","date",NULL);
default:wait();
}
}
思路: 子进程处理,父进程等待。 问题: 父进程的wait() 等待有什么意义? 下一章中说明。
1.7 服务器的设计问题: DIY or 代理
自己做 服务器接受请求,然后自己处理工作 适合于简单的任务。
代理 服务器接受请求,然后创建一个新的进程来处理工作。 适合于复杂的任务。
1.8 代理详解
看到这里,终于有点明白,springboot 等网络框架,是怎么并发处理多个请求的。
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
main(){
int sock,fd;
void child_waiter(int),process_request(int);
signal(SIGCHED,child_waiter);
if ((sock = make_server_socket(PROTNUM)) == -1)
perror("sock error);
while(1){
if (fd == -1)
break;
process_request(fd);
close(fd);
}
}
void child_waiter(int signum){
wait(NULL);
}
void process_request(int fd){
if (fork() == 0){
dup2(fd,1);
close(fd);
execlp("data","date",NULL);
oops("execlp date");
}
}
问题
程序运行到信号处理函数跳转时候会终中断系统调用accept 额。。。 这个没太想明白
Unix 如何处理多信号 这里先说一下wait(),wait() 为什么可以将僵尸子进程删除。 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 那如果几乎同时,有三个子进程的
SIGCHED
信号到达,如何处理? 信号处理函数处理一个SIGCHED
信号,阻塞一个SIGCHED
信号,但是还是会丢失一个SIGCHED
信号,即会产生一个僵尸进程。 调用waitpid()
解决。1 2 3 4
waitpid(-1,NULL,WNOHANG) //-1 : 等待所有子进程 //第二个值: 获取状态 //WNOHANG:没有僵尸进程,则不必等待
该循环直到所有退出的子进程都被等待了才停止。
2. web 服务器
2.1 设计
建立服务器
接受请求
读取请求
处理请求
发送应答
2.2 web服务器协议
请求和应答的格式在超文本传输协议(HTTP)中有定义。
HTTP 请求:GET telnet 创建了一个socket,并调用了connect来链接到服务器。接下来输入请求:
GET /index.html/1.0
参数1:命令。 参数2:参数。 参数3:协议的版本号。HTTP应答
HTTP/1.1 200 OK
第一串是协议的版本,第二串是返回码。 这里请求的文件是/info.html.而服务器给出的应答是可以找到该文件,如果服务器没有该文件名,返回码将会是404。 接下来服务器会返回附加信息,包括服务器名字,应答时间,服务器所发送的数据类型以及应答链接的类型。
3. 编写web服务器
略