前面的文章中介绍的服务器程序称为迭代服务器,在Unix中,编写并发服务器最简单的方法是fork一个子进程来服务每一个客户。
本文地址:http://wuyudong.com/2016/10/25/2913.html,转载请注明出处。
下面所示的是一个典型的并发服务器的框架:
pid_t pid; int listenfd, connfd; listenfd = Socket(...); bind(listenfd,...); Listen(listenfd, LISTENQ); for (;;) { connfd = Accept(listenfd,...); //阻塞 if ((pid = Fork()) == 0) { Close(listenfd); //子进程关闭监听套接字 doit(connfd); //处理请求 Close(connfd); //关闭连接套接字 exit(0); //子进程终止 } Close(connfd); //父进程关闭连接套接字 }
基本TCP套接字编程 给出了TCP网络编程的函数,本文使用那些基本函数编写一个完成的TCP客户/服务器程序示例。
该例子执行的步骤如下 :
1、客户从标准输入读入一行文本,并写给服务器。
2、服务器从网络输入读入这行文本,并回射给客户。
3、客户从网络输入读入这行回射文本,并显示在标准输出上。
TCP服务器程序:main函数
#include "unp.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 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(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = Fork()) == 0) {
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);
}
}
代码基本上与上述并发服务器的框架中的一致,处理请求部分通过str_echo函数实现
TCP服务器程序:str_echo函数
#include "unp.h" void str_echo(int sockfd) { ssize_t n; char buf[MAXLINE]; again: while ( (n = read(sockfd, buf, MAXLINE)) > 0) Writen(sockfd, buf, n); if (n < 0 && errno == EINTR) goto again; else if (n < 0) err_sys("str_echo: read error"); }
TCP客户程序:main函数
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}
TCP服务器程序:str_cli函数
#include "unp.h" void str_cli(FILE *fp, int sockfd) { char sendline[MAXLINE], recvline[MAXLINE]; while (Fgets(sendline, MAXLINE, fp) != NULL) { Writen(sockfd, sendline, strlen(sendline)); if (Readline(sockfd, recvline, MAXLINE) == 0) err_quit("str_cli: server terminated prematurely"); Fputs(recvline, stdout); } }
程序通过Fgets函数将标准输入写入sendline数组,接着使用write函数将sendline中的数据写入套接字,接着使用Readline函数将套接字中的数据写入recvline数组,最后使用fputs函数将recvline数组中的数据写到终端
正常启动
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpserv01&
[1] 3436
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ lsof -Pnl +M -i4
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tcpserv01 3436 1000 3u IPv4 42050 0t0 TCP *:9877 (LISTEN)
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpcli01 127.0.0.1
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ lsof -Pnl +M -i4
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tcpserv01 3436 1000 3u IPv4 42050 0t0 TCP *:9877 (LISTEN)
tcpcli01 3441 1000 3u IPv4 42146 0t0 TCP 127.0.0.1:42751->127.0.0.1:9877 (CLOSE_WAIT)
正常终止
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ ./tcpcli01 127.0.0.1
hello,wuyudong
hello,wuyudong
goodbye
goodbye
^D <Ctrl+D>是我们的终端EOF字符
wu@ubuntu:~/opt/unpv13e/tcpcliserv$ netstat -a|grep 9877
tcp 0 0 *:9877 *:* LISTEN
tcp 0 0 localhost:42758 localhost:9877 TIME_WAIT
当前连接的客户端(本地端口号为42758)进入 TIME_WAIT 状态,而监听服务器仍在等待下一个客户连接
正常终止客户和服务器的步骤:
(1) 当我们键入EOF字符时,fgets返回一个空指针,于是str_cli函数返回。
(2) 当str_cli返回到客户的main函数时,main通过调用exit终止。
(3) 进程终止处理的部分工作是关闭所有打开的描述符,因此客户打开的套接字由内核
关闭。这导致客户TCP发送一个FIN给服务器,服务器TCP则以ACK响应,这就是TCP连接终止序列的前半部分。因此服务器套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2状态。
(4) 当服务器TCP接收FIN时,服务器子进程阻塞与readline调用,于是readline返回0,
这导致str_echo函数返回服务器子进程的main函数。
(5) 服务器子进程通过调用exit来终止。
(6) 服务器子进程中打开的所有描述符随之关闭,由子进程来关闭已连接套接字会引发TCP连接终止序列的最后两个分节:一个从服务器到客户的FIN和一个从客户到服务器的ACK。至此,连接完全终止,客户套接字进入TIME_WAIT状态。
(7) 进程终止处理的另一部分内容是:在服务器子进程终止时,给父进程发送一个SIGCHLD信号。
Comments