UNIX网络编程学习笔记(代码超详细解析)(持续更新)

本文介绍了如何使用C语言实现TCP回射服务器(str_echo)和客户端(str_cli),以及一个展示客户IP地址和端口号的时间获取服务器。通过Socket编程,展示了套接字创建、连接、数据传输和异常处理。同时涵盖了信号处理、select/poll/pselect函数和网络编程中的相关概念.
摘要由CSDN通过智能技术生成

1. 其他函数准备

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

#include “unp.h”

void str_echo(int sockfd)
{
    ssize_t n;
    char    buf[MAXLINE];
    
again:
	/*
	write()
函数定义:ssize_t write (int fd, const void * buf, size_t count); 
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
*/
	/*
read()

函数定义:ssize_t read(int fd, void * buf, size_t count);

函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。

返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。另外,以下情况返回值小于count:
*/
    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");
}

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

#include "unp.h"

void str_cli(FILE *fp, int sockfd)
{
    char    sendline[MAXLINE], recvline[MAXLINE];
    
    while(Fgets(sendline, NAXLINE, fp) != NULL)
    {
        Writen(sockfd, sendline, strlen(sendline));
        
        if(Readline(sockfd, recvline, MALINE) == 0)
            err_quit("str_cli: server terminated prematurely");
        Fputs(recvline, stdout);
        
    }
    
}

显示客户IP地址和端口号的时间获取服务器程序

#include	"unp.h"
#include	<time.h>


#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop" //这是防止IDE输出警告的,不用管。
int
main(int argc, char **argv)
{
    int listenfd, connfd;// 
    //len是一个 值——结果 变量
    socklen_t len;
    
    struct sockaddr_in servaddr,cliaddr;//cliaddr存放客户的协议地址
    //缓冲区存放转换结果
    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);




	//本地协议地址赋予套接字
	//参数一:套接字描述符,用于标识套接字
	//参数二:指定特定于协议的地址结构的指针
	//参数三:地址结构的长度
	/*详细解释下为什么要传如地址结构的长度:由于该结构体的传递方向为从进程到内核,所以内核需要知道有多大的内容复制进来,所以需要一个参数告知内核传入地址的大小,这种变量叫做“值——结果”变量*/
    Bind(listenfd,(SA*) &servaddr,sizeof (servaddr));

	//使用socket创建的套接字最初为主动套接字,listen将其转换为一个被动套接字,指示内核应该接受指向该套接字的连接请求
    Listen(listenfd,LISTENQ);

    while (1)
    {
        len =sizeof (cliaddr);
        //accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。
        //如果accept成功,那么它的返回值是由内核生成的全新的描述符,代表与客户之间的 已成功建立 的TCP链接(已连接套接字描述符)
        //参数一:监听套接字描述符
        //参数二:客户进程的协议地址
        //参数三:该地址的大小
        //监听套接字与已连接套接字的区别:监听套接字在该服务的生命周期内一直存在,而每个已完成三次握手的TCP链接都会分配一个已连接套接字
        connfd = Accept(listenfd,(SA*) &cliaddr,&len);
        
        printf("connection from %s , port %d\n",
               inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof (buff)),//打印IP地址
               ntohs(cliaddr.sin_port));//打印端口号

		//获取时间
        ticks = time(NULL);
        //为什么不使用printf:因为printf的缓冲区不可控制,对我们来说难以控制
        snprintf(buff,sizeof (buff),"%.24s\r\n",ctime(&ticks));
        Write(connfd,buff,strlen(buff));

        Close(connfd);

    }


}

#pragma clang diagnostic pop

4.2作业


    #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");
    struct sockaddr_in ss;
    char buff[MAXLINE];
    socklen_t  len;
    len =sizeof (ss);
    if (getsockname(sockfd,(SA*) &ss,&len) < 0) printf("错误喽!");
    else
        {
            printf("connection from %s,port %d\n",inet_ntop(AF_INET,&ss.sin_addr,buff,sizeof (buff)),
                   ntohs(ss.sin_port));

    }

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

5.2

#include	"unp.h"

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
int
main(int argc, char **argv)
{
    int listenfd,confd;
    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);

    while (1)
    {
        clilen =sizeof (cliaddr);
        confd = Accept(listenfd,(SA*) &cliaddr,&clilen);

        if((childpid = Fork()) == 0)
        //fork为每个客户派生一个处理它们的子进程
        //紫禁城关闭监听套接字,父进程关闭已连接套接字,紫禁城接着调用str_echo处理客户
      
        {
            Close(listenfd);
            str_echo(listenfd); 
            exit(0);

        }
        Close(confd);


    }


}
#pragma clang diagnostic pop

TCP回射客户程序

#include	"unp.h"

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in servaddr;

    if (argc !=2 )
        err_quit("请输入IP地址。");
    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);//将IP地址转换为二进制机器可读
    Connect(sockfd,(SA*) &servaddr,sizeof (servaddr));
    
    str_cli(stdin,sockfd);//回射函数
    
    exit(0);
}
#pragma clang diagnostic pop

僵死进程:

   (1)概念:   进程实体已经释放,但进程对应的PCB进程控制块(进程描述符)还在.

   (2)产生的条件:  子进程比父进程结束的早,且父进程没有调用 wait() 获取子进程的退出码,这时子进程就变为僵死进程.

   (3)若子进程比父进程结束的晚,则在父进程结束后,子进程的父进程会变成pid为1 的进程

最终版本:

#include	"unp.h"

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

    /*
     按我现阶段的理解:
     (1)Signal捕获信号SIGCHLD,并交给sig_chld处理
     (2)Signal第二个参数实际上是一个函数指针,将sig_chld传入*/
    /*
     * sig_chld()函数解析:
     * 使用waitpid函数,并指定WNOHANG参数(有尚未终止的子进程是不要阻塞)
     * 为什么不适用wait函数,防止产生僵死进程,因为没有办法防止wait在正在运行的子进程尚有未终止阻塞(具体参见《unix网络编程》110页)
     * 阻塞:此处的阻塞不同于此前使用的同名词语,这里的阻塞是指阻塞信号或某个信号集,防止他们在阻塞期间提交。
     * 本节的目的是示范网络编程中可能遇到的情况:
     * (1)当fork某个进程时,必须捕获SIGCHLD信号
     * (2)当捕获信号时,必须处理被中断的系统调用
     * (3)SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程
     */


    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 */
    }
}

select()函数

#include "unp.h"

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"

void str_cli(FILE *fp, int sockfd) {
    //需要检查的文件描述符个数
    int maxfdp1;

    /* fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,
     * 每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,
     * 当调用select()时,由内核根据IO状态修改fd_set的内容,
     * 由此来通知执行了select()的进程哪个句柄可读。*/
    fd_set rset;

    char sendline[MAXLINE], recvline[MAXLINE];

    FD_ZERO(&rset);
    while (1) {
        //将fd加入set集合
        /*
         * FD_CLR(int fd, fd_set *fdset);       //将fd从set集合中清除
         *FD_ISSET(int fd, fd_set *fdset);     //检测fd是否在set集合中,不在则返回0
         *FD_ZERO(fd_set *fdset);              //将set清零使集合中不含任何fd
         */
        //fileno()用来取得参数stream 指定的文件流所使用的文件描述词
        FD_SET(fileno(fp), &rset);

        //将套接字描述符加入集合
        FD_SET(sockfd, &rset);
        
        //为什么要加1呢:我们需要描述符的个数,而不是最大值,而描述符是从0开始的(妙吧!)
        maxfdp1 = max(fileno(fp), sockfd )+ 1;
        
        /*int select(int nfds,  fd_set* readset,  fd_set* writeset,  fe_set* exceptset,  struct timeval* timeout);
         * 参数:
                   nfds           需要检查的文件描述字个数
                   readset     用来检查可读性的一组文件描述字。
                   writeset     用来检查可写性的一组文件描述字。
                   exceptset  用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
                   timeout      超时,填NULL为阻塞,填0为非阻塞,其他为一段超时时间
           返回值:
                   返回fd的总数,错误时返回SOCKET_ERROR*/
        Select(maxfdp1, &rset, NULL, NULL, NULL);

        //FD_ISSET(int fd, fd_set *fdset);     //检测fd是否在set集合中,不在则返回0
        if (FD_ISSET(sockfd, &rset)) {
            if (Readline(sockfd, recvline, MAXLINE) == 0)
                err_quit("服务器超时");
            Fputs(recvline, stdout);
        }
        if (FD_ISSET(fileno(fp), &rset)) {
            if (Fgets(sendline, MAXLINE, fp) == NULL)
                return;
            Write(sockfd, sendline, strlen(sendline));

        }


    }

}

#pragma clang diagnostic pop
/* include fig01 */
#include	"unp.h"

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
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;			/* 初始化 */
    maxi = -1;					/* index into client[] array */
    //将所有描述符初始化为-1
    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;	/* 保存描述符 */
                    break;/*保存了就跳出*/
                }
            if (i == FD_SETSIZE)
                err_quit("too many clients");

            FD_SET(connfd, &allset);	/*添加新的描述符来设置*/
            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 */
#pragma clang diagnostic pop

pselect()函数

函数select()是用一种超时轮循的方式来查看文件的读写错误可操作性。在Linux下,还有一个相似的函数pselect()。

poll()函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:
监视并等待多个文件描述符的属性变化
参数:

fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件

struct pollfd{
	int fd;			//文件描述符
	short events;	//等待的事件
	short revents;	//实际发生的事件
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值