正在加载
请稍等

菜单

红楼飞雪 梦

15526773247

文章

Home Socket编程 unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法
Home Socket编程 unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法

unix网络编程各种TCP客户-服务器程序设计实例附环境搭建和编译方法

Socket编程 by

 

 

 

 

 

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"  
  
int  
main(int argc, char **argv)  
{  
    int                 sockfd, n;  
    char                recvline[MAXLINE + 1];  
    struct sockaddr_in  servaddr;  
  
    if(argc != 2)  
        err_quit("usage: a.out <IPaddress>");  
  
    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  
        err_sys("socket error");  
  
    bzero(&servaddr, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_port   = htons(13);    /* daytime server */
    if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)  
        err_quit("inet_pton error for %s", argv[1]);  
  
    if(connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)  
        err_sys("connect error");  
  
    while( (n = read(sockfd, recvline, MAXLINE)) > 0) {  
        recvline[n] = 0;    /* null terminate */
        if(fputs(recvline, stdout) == EOF)  
            err_sys("fputs error");  
    }  
    if(n < 0)  
        err_sys("read error");  
  
    exit(0);  
}  

服务器程序:daytimetcpsrv.c

 

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
#include    "unp.h"  
#include    <time.h>  
  
int  
main(int argc, char **argv)  
{  
    int                 listenfd, connfd;  
    struct sockaddr_in  servaddr;  
    char                buff[MAXLINE];  
    time_t              ticks;  
  
    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(13);   /* daytime server */
  
    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));  
  
    Listen(listenfd, LISTENQ);  
  
    for ( ; ; ) {  
        connfd = Accept(listenfd, (SA *) NULL, NULL);  
  
        ticks = time(NULL);  
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));  
        Write(connfd, buff, strlen(buff));  
  
        Close(connfd);  
    }  
}  

 

好,现在我们接着上面的步骤,将这两个源文件放到我们喜欢的目录下,再复制unp.h和config.h两个文件到同一个目录下;

执行下面的命令:

生成了server和client程序,运行server和client程序如下图,顺利成功:

第二种:TCP并发服务器程序,每个客户一个子进程

每个客户一个子进程:传统上,并发服务器调用fork派生一个子进程来处理每个客户。这使得服务器可在同一时间为多个客户提供服务。

回射服务器:

1, 客户从标准输入读一行文本,写到服务器上;

2, 服务器从网络输入读此行,并回射给客户;

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
#include    "unp.h"  
  
void  
sig_chld(int signo)  
{  
    pid_t   pid;  
    int     stat;  
  
    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)  
        printf("child %d terminated\n", pid);  
    return;  
}  
  
int  
main(int argc, char **argv)  
{  
    int                 listenfd, connfd;  
    pid_t               childpid;  
    socklen_t           clilen;  
    struct sockaddr_in  cliaddr, servaddr;  
    void                sig_chld(int);  
  
    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);  
  
    Signal(SIGCHLD, sig_chld);  /* must call waitpid() */
  
    for ( ; ; ) {  
        clilen = sizeof(cliaddr);  
        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {  
            if (errno == EINTR)  
                continue;       /* back to for() */
            else
                err_sys("accept error");  
        }  
  
        if ( (childpid = Fork()) == 0) {    /* child process */
            Close(listenfd);    /* close listening socket */
            str_echo(connfd);   /* process the request */
            exit(0);  
        }  
        Close(connfd);          /* parent closes connected socket */
    }  
}  

str_echo.c:

 

1
2
3
4
5
6
7
8
9
10
11
#include "unp.h"
void  str_echo(int sockfd)  
{  
 ssize_t  n;  
 char  buf[MAXLINE]; 
 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");  
}

 

注意:上面的Signal(SIGCHLD, sig_chld);是为了捕捉信号SIGCHLD并处理僵尸进程,while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0),这里我们不能用wait,由于wait会阻塞于没有子进程终止的情况所有不能用while循环每个终止信号。

客户端程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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);     /* do it all */
  
    exit(0);  
}  

str_cli.c:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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);  
    }  
}  

 

最后像上面一样编译后运行即可,效果:

 

第三种:使用单进程和select/poll的TCP服务器程序

这种方法是用select来处理任意数目的客户的单进程程序,而不是像上面为每个客户派生一个子进程,在给出具体代码之前,让我们介绍一下背景知识,以及对用以跟踪客户的数据结构进行仔细的分析;

如果一个或多个I/O条件满足(例如,输入已准备好被读,或者描述字可以承接更多的输出)时,我们就被通知到。这个能力被称为I/O复用,是由函数select和poll支持的。

 

先介绍一下各种I/O模型:

阻塞I/O,非阻塞I/O,I/O复用,信号驱动I/O(SIGIO),异步I/O

对于一个套接口上的输入操作,第一步一般是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用缓冲区。

select函数:

这个函数运行进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒。

作为一个例子,我们可以调用函数select并通知内核仅在下列情况发生时才返回:

集合{1,4,5}中的任何描述字准备好读,或

集合{2,7}中任何描述字准备好些,或

集合{1,4}中的任何描述字有异常条件待处理,或

已经过了10.2秒

也就是说通知内核我们对哪些描述字感兴趣以及等待多长时间我们所关心的描述字不受限于套接口任何描述字都可用select来测试.

数据结构图示:

其中client[]用来揭露已连接描述字,而reset为读描述字集。

服务器程序:

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;         /* -1 indicates available entry */
    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 */

使用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 */

客服端程序:

 

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
#include    "unp.h"  
  
void
str_cli1(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;     /* normal termination */
                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);  
        }  
    }  
}  
  
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_cli1(stdin, sockfd);        /* do it all */
  
    exit(0);  
}  

 

这里客服端程序用了shutdown关闭套接口来代替close;下面就来介绍一下两者的区别:

shutdown可以分别关闭读写或者同时关闭读写

如果关闭读,则接受缓冲区的未读出的所有数据都将丢失,以后不会再接受任何数据

如果关闭写,如果输出缓冲区内有数据,则所有的数据将发送出去后将发送一个FIN信号

而close则是关闭该socket,马上发送FIN信号,所有的未完成发送或者接受的数据都将被丢失

对于慢速网络,应该先进行shutdown,然后一定的时间延迟,再close该socket.

(在你已经发送成功后,而对方如果没有接受完毕,你此时如果关闭socket则对方将收到FIN信号,将不能在接收数据,因此容易出现数据丢失的问题,这是 我以前写聊天室时遇到的一个错误,客户端经常报一个与服务器的连接被重置,后来加了一个时间延迟就好了)

#include<sys/socket.h>

int shutdown(int sockfd,int how);

how 的方式有三种分别是

SHUT_RD(0):关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。

SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。

SHUT_RDWR(2):关闭sockfd的读写功能。

成 功则返回0,错误返回-1,错误码errno:EBADF表示sockfd不是一个有效描述符;ENOTCONN表示sockfd未连 接;ENOTSOCK表示sockfd是一个文件描述符而不是socket描述符。

close的定义如下:

#include<unistd.h>

int close(int fd);

关闭读写。

成功则返回0,错误返回-1,错误码errno:EBADF表示fd不是一个有效 描述符;EINTR表示close函数被信号中断;EIO表示一个IO错误。

下面摘用网上的一段话来说明二者的区别:

close—– 关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id

shutdown– 则破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号.

1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。

2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会影响到其它进程

 

拒绝服务性攻击:

当一个服务器正在处理多个客户时,服务器决不能阻塞于只与单个客户相 关函数调用。如果这样的话,服务器悬挂并拒绝为所有其他客户提供服务,这称为拒绝服务攻击,如果一个恶意客户连接到服务器上,发送一个字节的数据(而不是 一行数据)后就睡眠。服务器将调用readline,它从客户上读到单个字节的数据,然后就阻塞与下一个read调用以等待客户的其他数据。接着,服务器 就阻塞与(挂起)此单个客户,不能为任何其他客户提供服务。这种状况要一直持续到此恶意客户发出一个换行符或是终止为止。可能解决的办法有:使用非阻塞 I/O模型;让每个客户由单独的控制线程提供服务;对I/O操作设置超时;

非阻塞I/O

缺省状态下,套接口是阻塞方式的。这意味着当一个套接口调用不能立即完成时,进程进入睡眠状态,等待操作的完成。我们将可能阻塞的套接口调用分成四种。

非阻塞connect:

在一个TCP套接口被设置为非阻塞后调用connect,connect会立即返回一个EINPROCESS错误,但TCP的三路握手继续进行。在这之后我们可以用select检查这个链接是否建立成功。

 

30 2015-05

 

我要 分享

 

 

本文 作者

 

相关 文章