TCP客户/服务器程序示例

1.TCP回射服务器程序:main函数

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
56
57
#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);//9877

Bind(listenfd,(SA *)&servaddr,sizeof(servaddr));

Listen(listenfd,LISTENQ);//系统默认的LISTENQ
Signal(SIGCHLD,sig_chld); //处理僵死进程
for(;;)
{
clilen=sizeof(cliaddr);
connfd=Accept(listenfd,(SA *)&cliaddr,&clilen);
if((childpid=Fork())==0)//子进程
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);
}

////处理被中断的系统调用,因为有些被中断的慢系统调用不会重启,例如accept
/*最终正确版本
for(;;)
{
clilen=sizeof(cliaddr);
if((connfd=accept(listenfd,(SA *)&cliaddr,&clilen))<0)//不调用包裹函数,自己处理错误
{
if(errno==EINTR)
continue;
else
err_sys("accept error");
}
if((childpid=Fork())==0)//子进程
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);

}
*/


}

2.TCP回射服务器程序:str_echo函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];

again:
while((n=read(sockfd,buf,MAXLINE))>0) //不断调用,因为一次调用read会导致缓冲区问题,具体看缓冲那
//收到客户的FIN将导致服务器的read返回0,从而返回tr_echo
Writen(sockfd,buf,n);

if(n<0 && errno==EINTR) //系统被一个捕获的信号中断,继续执行read和Writen
goto again;
else if(n<0)
err_sys("str_echo:read error");
}

3.TCP回射客户程序:main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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);//9877
Inet_pton(AF_INET,argv[1],&servaddr.sin_addr);

Connect(sockfd,(SA *)&servaddr,sizeof(servaddr));

str_cli(stdin,sockfd);

exit(0);
}

4.TCP回射客户程序:str_cli函数

1
2
3
4
5
6
7
8
9
10
11
12
#include "unp.h"
void str_cli(FILE *fp,int sockfd)
{
char sevdline[MAXLINE],recvline[MAXLINE];
while(Fgets(sendline,MAXLINE,fp)!=NULL) //驱动是fgets
{
Writen(sockfd,sendline,strlen(sendline));
if(Readline(sockfd,recvline,MAXLINE)==0)
err_quit("str_cli:server terminated prematurely");
Fputs(recvline,stdout);
}
}

注意:

使用netstat,ps观察端口状态

5.使用select改进str_cli

因为上面的版本为问题:当套接字发生某些事情,客户可能阻塞于fgets调用,使用select可以避免这个问题。

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
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;//读集合
char sendline[MAXLINE], recvline[MAXLINE];

FD_ZERO(&rset);
for ( ; ; ) {
FD_SET(fileno(fp), &rset);//fileno()用来取得参数stream指定的文件流所使用的文件描述符
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);//驱动是select

if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}

if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}

1.如果对端TCP发送数据,该套接字变为可读,并且read返回一个大于0的值(即读入数据的字节数);

2.如果对端TCP发送一个FIN(对端进程终止),该套接字变为可读,并且read返回0(EOF);

3.如果对端TCP发送一个RST(对端主机崩溃并重新启动),该套接字变为可读,并且read返回-1,errno错误码;

6.信号处理signal函数

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
#include	"unp.h"

Sigfunc * //void (*signal(int signo,void(*func)(int)))(int);
signal(int signo, Sigfunc *func) //typedef void Sigfunc(int);
{
struct sigaction act, oact;

act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */

Sigfunc * //包裹函数
Signal(int signo, Sigfunc *func) /* for our signal() function */
{
Sigfunc *sigfunc;

if ( (sigfunc = signal(signo, func)) == SIG_ERR)
err_sys("signal error");
return(sigfunc);
}

处理僵死进程

1
2
3
4
5
6
7
8
9
#include "unp.h"
void sig_chld(int signo)
{
pid_t pid;
int stat;

pid=wait(&stat); //返回已终止进程的ID,stat保存的终止状态
printf("child %d terminated\n",pid);
}

7.str_cli函数再修改(shutdown)

上一个版本只要服务器关闭它那一端的连接就会通知我们,接下来的版本允许我们正确处理批量输出。

还废弃的以文本行为中心的代码,改为针对缓冲区。

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
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;

stdineof = 0;
FD_ZERO(&rset);
for ( ; ; ) {
if (stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
Select(maxfdp1, &rset, NULL, NULL, NULL);

if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1)
return; //正常终止
else
err_quit("str_cli: server terminated prematurely"); //服务器过早终止
}

Write(fileno(stdout), buf, n);
}

if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
Shutdown(sockfd, SHUT_WR); // send FIN,处理批量输入,处理缓冲区问题
FD_CLR(fileno(fp), &rset);
continue;
}

Writen(sockfd, buf, n);
}
}
}

8.TCP回射服务器程序再修改(select)

上一个服务器程序版本(停等版本),是为客户派生一个子进程,处理完再重复为客户派生一个子进程;

接下来的版本使用select来处理任意个客户的单进程程序;

缺点:容易造成拒绝服务型攻击问题,即一个恶意客户发送一个字节数据后进入睡眠,服务器调用read读入该字节后阻塞下一个read调用);

解决方法:

1.使用非阻塞式I/O;

2.每个客户分配一个单独进程;

3.对I/O设置一个超时;

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* include fig01 */
#include "unp.h"

int
main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
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);

maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; //初始化client数组为-1,clien数组放描述符的数组
FD_ZERO(&allset); //
FD_SET(listenfd, &allset);
/* end fig01 */

/* include fig02 */
for ( ; ; ) {
rset = allset; /* structure assignment */
nready = Select(maxfd+1, &rset, NULL, NULL, NULL);

if (FD_ISSET(listenfd, &rset)) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s, port %d\n",
Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
ntohs(cliaddr.sin_port));
#endif

for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE)
err_quit("too many clients");

FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */

if (--nready <= 0)
continue; /* no more readable descriptors */
}

for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
/*4connection closed by client */
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
Writen(sockfd, buf, n);

if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */

9.TCP回射服务器程序再修改(poll)

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/* include fig01 */
#include "unp.h"
#include <limits.h> /* for OPEN_MAX */

int
main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
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);

client[0].fd = listenfd;//第一项用于监听套接字
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* -1 indicates available entry */
maxi = 0; /* max index into client[] array */
/* end fig01 */

/* include fig02 */
for ( ; ; ) {
nready = Poll(client, maxi+1, INFTIM);

if (client[0].revents & POLLRDNORM) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif

for (i = 1; i < OPEN_MAX; i++)
if (client[i].fd < 0) {
client[i].fd = connfd; /* save descriptor */
break;
}
if (i == OPEN_MAX)
err_quit("too many clients");

client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i; /* max index in client[] array */

if (--nready <= 0)
continue; /* no more readable descriptors */
}

for (i = 1; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
/*4connection reset by client */
#ifdef NOTDEF
printf("client[%d] aborted connection\n", i);
#endif
Close(sockfd);
client[i].fd = -1;
} else
err_sys("read error");
} else if (n == 0) {
/*4connection closed by client */
#ifdef NOTDEF
printf("client[%d] closed connection\n", i);
#endif
Close(sockfd);
client[i].fd = -1;
} else
Writen(sockfd, buf, n);

if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */