如何使用select函数的TCP和UDP回射服务器程序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用select函数的TCP和UDP回射服务器程序相关的知识,希望对你有一定的参考价值。

服务器程序

/* include udpservselect01 */
#include    "unp.h"

int
main(int argc, char **argv)

    int                    listenfd, connfd, udpfd, nready, maxfdp1;
    char                mesg[MAXLINE];
    pid_t                childpid;
    fd_set                rset;
    ssize_t                n;
    socklen_t            len;
    const int            on = 1;
    struct sockaddr_in    cliaddr, servaddr;
    void                sig_chld(int);

        /* 4create listening TCP socket */
    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);

    Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

        /* 4create UDP socket */
    udpfd = 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(udpfd, (SA *) &servaddr, sizeof(servaddr));
/* end udpservselect01 */

/* include udpservselect02 */
    Signal(SIGCHLD, sig_chld);    /* must call waitpid() */

    FD_ZERO(&rset);
    maxfdp1 = max(listenfd, udpfd) + 1;
    for ( ; ; ) 
        FD_SET(listenfd, &rset);
        FD_SET(udpfd, &rset);
        if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) 
            if (errno == EINTR)
                continue;        /* back to for() */
            else
                err_sys("select error");
        

        if (FD_ISSET(listenfd, &rset)) 
            len = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &len);
    
            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 */
        

        if (FD_ISSET(udpfd, &rset)) 
            len = sizeof(cliaddr);
            n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len);

            Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len);
        
    

/* end udpservselect02 */

创建TCP套接字

17~28 创建一个监听套接字并捆绑服务器到总所周知的端口,设置 SO_REUSEADDR套接字选项防止该端口上已有连接存在。

创建UDP套接字

30~38 还创建了一个UDP套接字捆绑于TCP套接字相同的端口,设置 SO_REUSEADDR套接字选项,因为TCP端口是独立于UDP端口的。

给SIGCHLD建立信号处理程序

42 因为TCP连接将由某个子程序处理。给出这个信号处理函数见下面。

准备调用select

44~45 我们给select初始化一个描述符集,并计算出我们等待的两个描述符的较大者。

调用select

47~54 我们调用select只是为了等待监听TCP套接字的可读条件或UDP套接字的可读条件。 既然我们的sig_chld信号处理函数可能中断我们对select的调用,我们于是需要处理EINTR错误。

处理新的客户端连接

56-66 当监听的TCP套接字可读时,我们accept一个新的客户连接,fork一个子程序,并在子程序进程中调用str_echo函数。

处理数据报的到达

68~74 如果UDP套接字可读,那么已有一个数据报到达。我们使用recvfrom读入它,再使用sendto把它发回客户端。

客户端程序(tcp和udp)

#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);

UDP客户端程序

#include    "unp.h"

int
main(int argc, char **argv)

    int                    sockfd;
    struct sockaddr_in    servaddr;

    if (argc != 2)
        err_quit("usage: udpcli <IPaddress>");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

    dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));

    exit(0);

信号处理程序

#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;

UDP的回射服务器程序:dg_echo函数

#include <unp.h>

void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)

    int     n;
    socklen_t    len;
    char mesg[MAXLINE];

    for(;;)
    
        len = clilen;
        n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

        Sendto(sockfd, mesg, n, 0, pcliaddr, len);
    

UDP的回射客户程序:dg_cli函数

#include <unp.h>

void dg_cli(FILE *fp, int sockfd, const SA* pservaddr, socklen_t servlen)

    int     n;
    char    sendline[MAXLINE], recvline[MAXLINE+1];
    while(Fgets(sendline, MAXLINE, fp) != NULL)
    
        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = \'\\0\';
        Fputs(recvline, stdout);
    
参考技术A 方法如下。
1.创建一个监听TCP套接字并捆绑服务器的众所周知的端口,设置SO_REUSEADDR套接字选项以防止该端口上已有连接存在。
2.还创建一个UDP套接字并捆绑与TCP套接字相同的端口。这里无需在调用bind之前设置SO_REUSEADDR套接字选项,因为TCP端口是独立于UDP端口的。
3. 给SIGCHLD建立信号处理程序,因为TCP连接将由某个子进程处理。
4.调用select只是为了等待监听TCP套接字的可读条件或UDP套接字的可读条件。既然sig_chld信号处理函数可能中断对select的调用,于是需要处理EINTR错误。

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

TCP回射并发服务器

1.创建套接字,绑定服务器的众所周知端口

创建一个TCP套接字,在待绑定到该TCP套接字的网际网套接字地址结构中填入通配地址(INADDR_ANY)

和服务器的众所知周(SERV_PORT,在头文件中unp.h中其定义为9877)

绑定通配地址是在告知系统:

要是系统是多宿主机,我们将接受目的地地址为任何本地接口的连接

我们对TCP端口号的选择应该比1023大,比5000大,比49152小,而且不和任何注册的端口冲突

listen把该套接字地址转换成一个监听套接字

2.等待完成客户连接

服务器阻塞于accept调用,等待客户连接的完成

3.并发服务器

fork为每个客户派生一个处理它们的子进程

子进程关闭监听套接字,父进程关闭已连接套接字,子进程接着调用str_echo

 

#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) {	/* child process */
			Close(listenfd);	/* close listening socket */
			str_echo(connfd);	/* process the request */
			exit(0);
		}
		Close(connfd);			/* parent closes connected socket */
	}
}

  

以上是关于如何使用select函数的TCP和UDP回射服务器程序的主要内容,如果未能解决你的问题,请参考以下文章

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

TCP回射客户程序:main函数

TCP回射客户程序:str_cli函数

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

TCP回射服务器修订版(ubuntu 18.04)

《UNIX网络编程 卷1:套接字联网API》学习笔记——TCP客户/服务器程序示例