[unix网络编程] 第一个最简单的服务器程序(TCP)

时间:2021-10-02 17:51:18

源代码阅读与分析

TCP三次握手的流程图

为了进一步明晰地看到客户端、服务器通信方式,还是先要把这个三次握手流程放在这里,作为阅读源码的比较分析
[unix网络编程] 第一个最简单的服务器程序(TCP)

TCP回射程序

服务器

#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;    //Ipv4
    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);//连接。一般没有的话阻塞等待,类似一个循环。
    //返回指向该套接字的文件描述符,如果套接字被标记为Non-blocking,
    //队列中也没有等待的连接,accept()返回错误EAGAIN或EWOULDBLOCK。这里没处理,后面应该会有的。处理比如返回信号。

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

void
str_echo(int sockfd)
{
    ssize_t     n;
    char        buf[MAXLINE];

again://标签
//read()函数返回值为0,表示正常结束,已读完文件;
//返回值为-1,如果全局变量errono = EINT表示中断,需要重新再读。否则读取错误,结束
    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");
}

客户端

#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);
    //argv[1]是127.0.0.1之类的表达式,通过这个函数转化为需要的二进制数。
    Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

    Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));//客户端两部曲②

    str_cli(stdin, sockfd);     /* do it all */

    exit(0);
}
#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);
    }
}

配置运行

网上博客很多都不对,至少对我的很不适用。
首先先按照这篇博客进行配置 http://blog.sina.com.cn/s/blog_beb8b5d70101dq1a.html
如果编译中出现inet_ntop的size不匹配,就打开相关文件把size_t 改成socklen_t。
然后在看这篇博客:http://blog.sina.com.cn/s/blog_a43aba560101a7gf.html
因为我们的客户端、服务器的程序都在一个目录下的,
所以只要看这几句就可以了:

    gcc -o tcpserv01 tcpserv01.c  -lunp   //(在终端1中输入,因为客户端服务器是两个进程)
    gcc -o  tcpcli01  tcpcli01.c   -lunp  //(在终端2输入)
   ./tcpserv01                            //(在终端1)
   ./tcpcli01  127.0.0.1                  //(在终端2)